[前][次][番号順一覧][スレッド一覧]

ruby-changes:71092

From: Koichi <ko1@a...>
Date: Sun, 6 Feb 2022 03:06:14 +0900 (JST)
Subject: [ruby-changes:71092] 603ab70961 (master): support concurrent btest execution

https://git.ruby-lang.org/ruby.git/commit/?id=603ab70961

From 603ab709615dd35fa8ebe53087c27631f5b07812 Mon Sep 17 00:00:00 2001
From: Koichi Sasada <ko1@a...>
Date: Sat, 5 Feb 2022 03:10:15 +0900
Subject: support concurrent btest execution

* `-j` option for concurrent test with threads
  * `-jN` uses N threads
  * `-j` uses nproc/2 threads
* Introduce `BT` struct to manage configurations
* Introduce `Assertion` to manage all assertions
* Remove all toplevel instance variables
* Show elapsed seconds at last

```
$ time make btest
...
real    0m37.319s
user    0m26.221s
sys     0m16.534s

$ time make btest TESTOPTS=-j
...
real    0m11.812s
user    0m36.667s
sys     0m21.872s
```
---
 bootstraptest/runner.rb        | 763 +++++++++++++++++++++++++----------------
 bootstraptest/test_autoload.rb |  30 +-
 bootstraptest/test_fiber.rb    |   2 +-
 bootstraptest/test_method.rb   |   2 +-
 bootstraptest/test_thread.rb   |   8 +-
 5 files changed, 480 insertions(+), 325 deletions(-)

diff --git a/bootstraptest/runner.rb b/bootstraptest/runner.rb
index a4101594f1..1be0d677c0 100755
--- a/bootstraptest/runner.rb
+++ b/bootstraptest/runner.rb
@@ -8,6 +8,8 @@ https://github.com/ruby/ruby/blob/trunk/bootstraptest/runner.rb#L8
 # Never use Ruby extensions in this file.
 # Maintain Ruby 1.8 compatibility for now
 
+$start_time = Time.now
+
 begin
   require 'fileutils'
   require 'tmpdir'
@@ -58,24 +60,44 @@ if !Dir.respond_to?(:mktmpdir) https://github.com/ruby/ruby/blob/trunk/bootstraptest/runner.rb#L60
   end
 end
 
+# Configuration
+BT = Struct.new(:ruby,
+                :verbose,
+                :color,
+                :tty,
+                :quiet,
+                :wn,
+                :progress,
+                :progress_bs,
+                :passed,
+                :failed,
+                :reset,
+                :columns,
+                :width,
+                ).new
+
+BT_STATE = Struct.new(:count, :error).new
+
 def main
-  @ruby = File.expand_path('miniruby')
-  @verbose = false
+  BT.ruby = File.expand_path('miniruby')
+  BT.verbose = false
   $VERBOSE = false
   $stress = false
-  @color = nil
-  @tty = nil
-  @quiet = false
+  BT.color = nil
+  BT.tty = nil
+  BT.quiet = false
+  BT.wn = 1
   dir = nil
   quiet = false
   tests = nil
   ARGV.delete_if {|arg|
     case arg
     when /\A--ruby=(.*)/
-      @ruby = $1
-      @ruby.gsub!(/^([^ ]*)/){File.expand_path($1)}
-      @ruby.gsub!(/(\s+-I\s*)((?!(?:\.\/)*-(?:\s|\z))\S+)/){$1+File.expand_path($2)}
-      @ruby.gsub!(/(\s+-r\s*)(\.\.?\/\S+)/){$1+File.expand_path($2)}
+      ruby = $1
+      ruby.gsub!(/^([^ ]*)/){File.expand_path($1)}
+      ruby.gsub!(/(\s+-I\s*)((?!(?:\.\/)*-(?:\s|\z))\S+)/){$1+File.expand_path($2)}
+      ruby.gsub!(/(\s+-r\s*)(\.\.?\/\S+)/){$1+File.expand_path($2)}
+      BT.ruby = ruby
       true
     when /\A--sets=(.*)/
       tests = Dir.glob("#{File.dirname($0)}/test_{#{$1}}*.rb").sort
@@ -88,18 +110,26 @@ def main https://github.com/ruby/ruby/blob/trunk/bootstraptest/runner.rb#L110
       $stress = true
     when /\A--color(?:=(?:always|(auto)|(never)|(.*)))?\z/
       warn "unknown --color argument: #$3" if $3
-      @color = $1 ? nil : !$2
+      BT.color = color = $1 ? nil : !$2
       true
     when /\A--tty(=(?:yes|(no)|(.*)))?\z/
       warn "unknown --tty argument: #$3" if $3
-      @tty = !$1 || !$2
+      BT.tty = !$1 || !$2
       true
     when /\A(-q|--q(uiet))\z/
       quiet = true
-      @quiet = true
+      BT.quiet = true
+      true
+    when /\A-j(\d+)?/
+      wn = $1.to_i
+      if wn <= 0
+        require 'etc'
+        wn = [Etc.nprocessors / 2, 1].max
+      end
+      BT.wn = wn
       true
     when /\A(-v|--v(erbose))\z/
-      @verbose = true
+      BT.verbose = true
     when /\A(-h|--h(elp)?)\z/
       puts(<<-End)
 Usage: #{File.basename($0, '.*')} --ruby=PATH [--sets=NAME,NAME,...]
@@ -128,15 +158,16 @@ End https://github.com/ruby/ruby/blob/trunk/bootstraptest/runner.rb#L158
   tests = Dir.glob("#{File.dirname($0)}/test_*.rb").sort if tests.empty?
   pathes = tests.map {|path| File.expand_path(path) }
 
-  @progress = %w[- \\ | /]
-  @progress_bs = "\b" * @progress[0].size
-  @tty = $stderr.tty? if @tty.nil?
-  case @color
+  BT.progress = %w[- \\ | /]
+  BT.progress_bs = "\b" * BT.progress[0].size
+  BT.tty = $stderr.tty? if BT.tty.nil?
+
+  case BT.color
   when nil
-    @color = @tty && /dumb/ !~ ENV["TERM"]
+    BT.color = BT.tty && /dumb/ !~ ENV["TERM"]
   end
-  @tty &&= !@verbose
-  if @color
+  BT.tty &&= !BT.verbose
+  if BT.color
     # dircolors-like style
     colors = (colors = ENV['TEST_COLORS']) ? Hash[colors.scan(/(\w+)=([^:\n]*)/)] : {}
     begin
@@ -145,14 +176,14 @@ End https://github.com/ruby/ruby/blob/trunk/bootstraptest/runner.rb#L176
       end
     rescue
     end
-    @passed = "\e[;#{colors["pass"] || "32"}m"
-    @failed = "\e[;#{colors["fail"] || "31"}m"
-    @reset = "\e[m"
+    BT.passed = "\e[;#{colors["pass"] || "32"}m"
+    BT.failed = "\e[;#{colors["fail"] || "31"}m"
+    BT.reset = "\e[m"
   else
-    @passed = @failed = @reset = ""
+    BT.passed = BT.failed = BT.reset = ""
   end
   unless quiet
-    puts Time.now
+    puts $start_time
     if defined?(RUBY_DESCRIPTION)
       puts "Driver is #{RUBY_DESCRIPTION}"
     elsif defined?(RUBY_PATCHLEVEL)
@@ -160,289 +191,473 @@ End https://github.com/ruby/ruby/blob/trunk/bootstraptest/runner.rb#L191
     else
       puts "Driver is ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}]"
     end
-    puts "Target is #{`#{@ruby} -v`.chomp}"
+    puts "Target is #{`#{BT.ruby} -v`.chomp}"
     puts
     $stdout.flush
   end
 
-  in_temporary_working_directory(dir) {
+  in_temporary_working_directory(dir) do
     exec_test pathes
-  }
+  end
 end
 
 def erase(e = true)
-  if e and @columns > 0 and @tty and !@verbose
+  if e and BT.columns > 0 and BT.tty and !BT.verbose
     "\e[1K\r"
   else
     ""
   end
 end
 
-def exec_test(pathes)
-  @count = 0
-  @error = 0
-  @errbuf = []
-  @location = nil
-  @columns = 0
-  @width = pathes.map {|path| File.basename(path).size}.max + 2
+def load_test pathes
   pathes.each do |path|
-    @basename = File.basename(path)
-    unless @quiet
-      $stderr.printf("%s%-*s ", erase(@quiet), @width, @basename)
-      $stderr.flush
-    end
-    @columns = @width + 1
-    $stderr.puts if @verbose
-    count = @count
-    error = @error
     load File.expand_path(path)
-    if @tty
-      if @error == error
-        msg = "PASS #{@count-count}"
-        @columns += msg.size - 1
-        $stderr.print "#{@progress_bs}#{@passed}#{msg}#{@reset}" unless @quiet
+  end
+end
+
+def concurrent_exec_test
+  aq = Queue.new
+  rq = Queue.new
+
+  ts = BT.wn.times.map do
+    Thread.new do
+      while as = aq.pop
+        as.call
+        rq << as
+      end
+    ensure
+      rq << nil
+    end
+  end
+
+  Assertion.all.to_a.shuffle.each do |path, assertions|
+    assertions.each do |as|
+      aq << as
+    end
+  end
+
+  $stderr.print ' ' unless BT.quiet
+  aq.close
+  i = 1
+  term_wn = 0
+  begin
+    while BT.wn != term_wn
+      if r = rq.pop
+        case
+        when BT.quiet
+        when BT.tty
+          $stderr.print "#{BT.progress_bs}#{BT.progress[(i+=1) % BT.progress.size]}"
+        else
+          $stderr.print '.'
+        end
       else
-        msg = "FAIL #{@error-error}/#{@count-count}"
-        $stderr.print "#{@progress_bs}#{@failed}#{msg}#{@reset}"
-        @columns = 0
+        term_wn += 1
+      end
+    end
+  ensure
+    ts.each(&:kill)
+    ts.each(&:join)
+  end
+end
+
+def exec_test(pathes)
+  # setup
+  load_test pathes
+  BT_STATE.count = 0
+  BT_STATE.error = 0
+  BT.columns = 0
+  BT.width = pathes.map {|path| File.basename(path).size}.max + 2
+
+  # execute tests
+  if BT.wn > 1
+    concurrent_exec_test if BT.wn > 1
+  else
+    prev_basename = nil
+    Assertion.all.each do |basename, assertions|
+      if !BT.quiet && basename != prev_basename
+        prev_basename = basename
+        $stderr.printf("%s%-*s ", erase(BT.quiet), BT.width, basename)
+        $stderr.flush
+      end
+      BT.columns = BT.width + 1
+      $stderr.puts if BT.verbose
+      count = BT_STATE.count
+      error = BT_STATE.error
+
+      assertions.each do |assertion|
+        BT_STATE.count += 1
+        assertion.call
+      end
+
+      if BT.tty
+        if BT_STATE.error == error
+          msg = "PASS #{BT_STATE.count-count}"
+          BT.columns += msg.size - 1
+          $stderr.print "#{BT.progress_bs}#{BT.passed}#{msg}#{BT.reset}" unless BT.quiet
+        else
+          msg = "FAIL #{BT_STATE.error-error}/#{BT_STATE.count-count}"
+          $stderr.print "#{BT.progress_bs}#{BT.failed}#{msg}#{BT.reset}"
+          BT.columns = 0
+        end
       end
+      $stderr.puts if !BT.quiet and (BT.tty or BT_STATE.error == error)
     end
-    $stderr.puts if !@quiet and (@tty or @error == error)
   end
-  $stderr.print(erase) if @quiet
-  @errbuf.each do |msg|
+
+  # show results
+  unless BT.quiet
+    $stderr.puts(erase)
+
+    sec = Time.now - $start_time
+    $stderr.puts "Finished in #{'%.2f' % sec} sec\n\n" if Assertion.count > 0
+  end
+
+  Assertion.errbuf.each do |msg|
     $stderr.puts msg
   end
 
-  out = @quiet ? $stdout : $stderr
+  out = BT.quiet ? $stdout : $stderr
 
-  if @error == 0
-    if @count == 0
-      out.puts "No tests, no problem" unless @quiet
+  if BT_STATE.error == 0
+    if Assertion.count == 0
+      out.puts "No tests, no problem" unless BT.quiet
     else
-      out.puts "#{@passed}PASS#{@reset} all #{@count} tests"
+      out.puts "#{BT.passed}PASS#{BT.reset} all #{Assertion.count} tests"
     end
-    exit true
+    true
   else
-    $stderr.puts "#{@failed}FAIL#{@reset} #{@error}/#{@count} tests failed"
-    exit false
+    $stderr.puts "#{BT.failed}FAIL#{BT.reset} #{BT_STATE.error}/#{BT_STATE.count} tests failed"
+    false
   end
 end
 
-def show_progress(message = '')
-  if @quiet
-    # do nothing
-  elsif @verbose
-    $stderr.print "\##{@count} #{@location} "
-  e (... truncated)

--
ML: ruby-changes@q...
Info: http://www.atdot.net/~ko1/quickml/

[前][次][番号順一覧][スレッド一覧]