=begin
パッチが完成したのでチケットを作成しました。
まつもとさんは[ruby-dev:43224] で「バグがとれたら入れちゃったら?」と言っていました。
一部テストについては改変していますが、それについては取りこまなくてもFailになることは無いと思います。
(workerで失敗したテストは並列では無く実行をしなおす為。ただし若干速度に影響がでるかと)
では、コミットをよろしくお願いします。
--patch (git diff --no-prefix)--
diff --git lib/test/unit.rb lib/test/unit.rb
index 76e9fdd..00d7e69 100644
--- lib/test/unit.rb
+++ lib/test/unit.rb
@@ -51,6 +51,11 @@ module Test
non_options(args, options)
@help = orig_args.map { |s| s =~ /[\s|&<>$()]/ ? s.inspect : s }.join " "
@options = options
@opts = @options = options
@@ -75,9 +80,35 @@ module Test
opts.on '-n', '--name PATTERN', "Filter test names on pattern." do |a|
options[:filter] = a
end
+
opts.on '--jobs-status [TYPE]', "Show status of jobs every file; Disabled when --jobs isn't specified." do |type|
options[:job_status] = true
options[:job_status_type] = type.to_sym if type
opts.on '-j N', '--jobs N', "Allow run tests with N jobs at once" do |a|
options[:parallel] = a.to_i
opts.on '--no-retry', "Don't retry running testcase when --jobs specified" do
options[:no_retry] = true
opts.on '--ruby VAL', "Path to ruby; It'll have used at -j option" do |a|
end
end
def non_options(files, options)
warn "#{caller(1)[0]}: warning: Parallel running disabled because can't get path to ruby; run specify with --ruby argument"
options[:ruby] = RbConfig.ruby
end
@@ -175,7 +206,7 @@ module Test
$: << d
end
begin
require path unless options[:parallel]
result = true
rescue LoadError
puts "#{f}: #{$!}"
@@ -186,32 +217,301 @@ module Test
end
class Runner < MiniTest::Unit
include Test::Unit::Options
include Test::Unit::RequireFiles
include Test::Unit::GlobOption
include Test::Unit::LoadPathOption
include Test::Unit::GCStressOption
include Test::Unit::RunCount
class << self; undef autorun; end
alias orig_run_anything _run_anything
if @opts[:parallel] && @warnings
r = ary.include?(w[1].message)
warn "#{w[0]}: #{w[1].message} (#{w[1].class})"
@@stop_auto_run = false
def self.autorun
at_exit {
Test::Unit::RunCount.run_once {
exit(Test::Unit::Runner.new.run(ARGV) || true)
} unless @@stop_auto_run
} unless @@installed_at_exit
@@installed_at_exit = true
end
def after_worker_down(worker, e=nil, c=1)
return unless @opts[:parallel]
warn "#{b.shift}: #{e.message} (#{e.class})"
STDERR.print b.map{|s| "\tfrom #{s}"}.join("\n")
warn "Some worker was crashed. It seems ruby interpreter's bug"
warn "or, a bug of test/unit/parallel.rb. try again without -j"
return unless @opts[:job_status]
puts "" unless @opts[:verbose]
a = "#{x[:pid]}:#{x[:status].to_s.ljust(7)}"
if @opts[:job_status_type] == :replace
a = "#{x[:pid]}=#{x[:file]}"
if a.size > x[:file].size
b << x[:file].ljust(a.size)
a << " "*(x[:file].size-a.size)
if @opts[:job_status_type] == :replace
@terminal_width ||= %x{stty size 2>/dev/null}.split[1].to_i.nonzero? \
|| %x{tput cols 2>/dev/null}.to_i.nonzero? \
print str[0...@terminal_width]
@jstr_size = str.size > @terminal_width ? @terminal_width : str.size
return unless @opts[:job_status_type] == :replace && @jstr_size
print "\r"+" "*@jstr_size+"\r"
def after_worker_dead(worker)
return unless @opts[:parallel]
@ios = @workers.map{|w| w[:out] }
def _run_suites suites, type
@interrupt = nil
result = []
if @opts[:parallel]
begin
result << _run_suite(suite, type)
# Require needed things for parallel running
@tasks = @files.dup # Array of filenames.
@dead_workers = [] # Array of dead workers.
@workers = @opts[:parallel].times.map do
i,o = IO.pipe("ASCII-8BIT") # worker o>|i> master
j,k = IO.pipe("ASCII-8BIT") # worker <j|<k master
pid = spawn(*@opts[:ruby].split(/ /),File.dirname(__FILE__) +
"/unit/parallel.rb", *@args, out: o, in: j)
[o,j].each{|io| io.close }
{in: k, out: i, pid: pid, status: :waiting}
while stat = Process.wait2
break if @interrupt # Break when interrupt
w = (@workers + @dead_workers).find{|x| stat[0] == x[:pid] }.dup
unless w[:status] == :quit
after_worker_down w, nil, stat[1].to_i
@workers_hash = Hash[@workers.map {|w| [w[:out],w] }] # out-IO => worker
@ios = @workers.map{|w| w[:out] } # Array of worker IOs
while _io = IO.select(@ios)[0]
break unless _io.each do |io|
buf = ((a[:status] == :quit) ? io.read : io.gets).chomp
when /^okay$/ # Worker will run task
when /^ready$/ # Worker is ready
break unless @workers.find{|x| x[:status] == :running }
a[:file] = File.basename(task).gsub(/\.rb/,"")
a[:in].puts "loadpath #{[Marshal.dump($:-a[:loadpath])].pack("m").gsub("\n","")}"
a[:in].puts "run #{task} #{type}"
raise unless ["stream closed","closed stream"].include? $!.message
when /^done (.+?)$/ # Worker ran a one of suites in a file
r = Marshal.load($1.unpack("m")[0])
# [result,result,report,$:]
rep << {file: a[:real_file], report: r[2], result: r[3],
errors << [a[:real_file],r[5],r[3][0]]
failures << [a[:real_file],r[5],r[3][1]]
skips << [a[:real_file],r[5],r[3][2]]
jobs_status if @opts[:job_status_type] == :replace
when /^p (.+?)$/ # Worker wanna print to STDOUT
jobs_status if @opts[:job_status_type] == :replace
@warnings << Marshal.load($1.unpack("m")[0])
when /^bye (.+?)$/ # Worker will shutdown
e = Marshal.load($1.unpack("m")[0])
when /^bye$/ # Worker will shutdown
# TODO: Interrupt?
rescue Interrupt => e
@interrupt = e
watchdog.kill if watchdog
timeout(0.2*@workers.size) do
Process.kill(:KILL,w[:pid])
if @interrupt || @opts[:no_retry]
@errors += errors.map(&:last).inject(:+)
@failures += failures.map(&:last).inject(:+)
@skips += skips.map(&:last).inject(:+)
if r[:testcase] && r[:file] && !r[:report].empty?
_run_suite(eval(r[:testcase]),type)
@failures += r[:result][1]
result << _run_suite(suite, type)
@@ -223,10 +523,6 @@ module Test
end
class Runner < Test::Unit::Runner
include Test::Unit::RequireFiles
attr_accessor :to_run, :options
def initialize(force_standalone = false, default_dir = nil, argv = ARGV)
diff --git lib/test/unit/parallel.rb lib/test/unit/parallel.rb
new file mode 100644
index 0000000..acfdc84
--- /dev/null
+++ lib/test/unit/parallel.rb
@@ -0,0 +1,139 @@
+require 'test/unit'
+
+module Test
module Unit
class Worker < Runner
alias orig_run_suite _run_suite
def _run_suites suites, type
result = _run_suite(suite, type)
def _run_suite(suite, type)
orig_stdout = MiniTest::Unit.output
MiniTest::Unit.output = o
th = Thread.new(i.dup) do |io|
while buf = (self.verbose ? io.gets : io.read(5))
stdout.puts "p #{[buf].pack("m").gsub("\n","")}"
e, f, s = @errors, @failures, @skips
result = orig_run_suite(suite, type)
MiniTest::Unit.output = orig_stdout
raise unless ["stream closed","closed stream"].include? $!.message
result << [@errors-e,@failures-f,@skips-s]
result << ($: - @old_loadpath)
STDOUT.puts "done #{[Marshal.dump(result)].pack("m").gsub("\n","")}"
MiniTest::Unit.output = orig_stdout
o.close if o && !o.closed?
i.close if i && !i.closed?
Signal.trap(:INT,"IGNORE")
$:.push(*Marshal.load($1.unpack("m")[0].force_encoding("ASCII-8BIT"))).uniq!
suites = MiniTest::Unit::TestCase.test_suites
STDOUT.puts "after #{[Marshal.dump([$1, $!])].pack("m").gsub("\n","")}"
_run_suites MiniTest::Unit::TestCase.test_suites-suites, $2.to_sym
STDOUT.puts "bye #{[Marshal.dump(e)].pack("m").gsub("\n","")}"
end
end
+end
+Test::Unit::Worker.new.run(ARGV)
diff --git test/csv/test_serialization.rb test/csv/test_serialization.rb
index 0adb972..ba19b7a 100755
--- test/csv/test_serialization.rb
+++ test/csv/test_serialization.rb
@@ -131,7 +131,7 @@ class TestCSV::Serialization < TestCSV
def test_io
test_class_dump
data_file = File.join(File.dirname(FILE ), "temp_test_data.csv")
data_file = File.join(File.dirname(FILE ), "serialization_test_data.csv")
CSV.dump(@names, File.open(data_file, "wb"))
assert(File.exist?(data_file))
diff --git test/net/http/test_https.rb test/net/http/test_https.rb
index 983ba7f..12684f6 100644
--- test/net/http/test_https.rb
+++ test/net/http/test_https.rb
@@ -24,7 +24,7 @@ class TestNetHTTPS < Test::Unit::TestCase
CONFIG = {
'host' => '127.0.0.1',
'port' => 10082, # different from test_http.rb
'port' => 10082,
'proxy_host' => nil,
'proxy_port' => nil,
'ssl_enable' => true,
diff --git test/rake/test_file_task.rb test/rake/test_file_task.rb
index 1b0c0a5..0232ac9 100644
--- test/rake/test_file_task.rb
+++ test/rake/test_file_task.rb
@@ -29,7 +29,9 @@ class Rake::TestFileTask < Test::Unit::TestCase
end
def test_file_times_new_depends_on_old
create_timed_files(OLDFILE, NEWFILE)
until File.exist?(OLDFILE) && File.exist?(NEWFILE)
create_timed_files(OLDFILE, NEWFILE)
end
t1 = Rake.application.intern(FileTask, NEWFILE).enhance([OLDFILE])
t2 = Rake.application.intern(FileTask, OLDFILE)
@@ -38,7 +40,9 @@ class Rake::TestFileTask < Test::Unit::TestCase
end
def test_file_times_old_depends_on_new
create_timed_files(OLDFILE, NEWFILE)
until File.exist?(OLDFILE) && File.exist?(NEWFILE)
create_timed_files(OLDFILE, NEWFILE)
end
t1 = Rake.application.intern(FileTask,OLDFILE).enhance([NEWFILE])
t2 = Rake.application.intern(FileTask, NEWFILE)
@@ -93,46 +97,46 @@ class Rake::TestDirectoryTask < Test::Unit::TestCase
include Rake
rm_rf "testdata", :verbose=>false
rm_rf "testdata2", :verbose=>false
end
rm_rf "testdata", :verbose=>false
rm_rf "testdata2", :verbose=>false
end
def test_directory
desc "DESC"
directory "testdata/a/b/c"
assert_equal FileCreationTask, Task["testdata"].class
assert_equal FileCreationTask, Task["testdata/a"].class
assert_equal FileCreationTask, Task["testdata/a/b/c"].class
assert_nil Task["testdata"].comment
assert_equal "DESC", Task["testdata/a/b/c"].comment
assert_nil Task["testdata/a/b"].comment
directory "testdata2/a/b/c"
assert_equal FileCreationTask, Task["testdata2"].class
assert_equal FileCreationTask, Task["testdata2/a"].class
assert_equal FileCreationTask, Task["testdata2/a/b/c"].class
assert_nil Task["testdata2"].comment
assert_equal "DESC", Task["testdata2/a/b/c"].comment
assert_nil Task["testdata2/a/b"].comment
verbose(false) {
Task['testdata/a/b'].invoke
Task['testdata2/a/b'].invoke
}
assert File.exist?("testdata/a/b")
assert ! File.exist?("testdata/a/b/c")
assert File.exist?("testdata2/a/b")
assert ! File.exist?("testdata2/a/b/c")
end
if Rake::Win32.windows?
def test_directory_win32
desc "WIN32 DESC"
FileUtils.mkdir_p("testdata")
directory 'c:/testdata/a/b/c'
assert_equal FileCreationTask, Task['c:/testdata'].class
assert_equal FileCreationTask, Task['c:/testdata/a'].class
assert_equal FileCreationTask, Task['c:/testdata/a/b/c'].class
assert_nil Task['c:/testdata'].comment
assert_equal "WIN32 DESC", Task['c:/testdata/a/b/c'].comment
assert_nil Task['c:/testdata/a/b'].comment
FileUtils.mkdir_p("testdata2")
Dir.chdir("testdata2") do
directory 'c:/testdata2/a/b/c'
assert_equal FileCreationTask, Task['c:/testdata2'].class
assert_equal FileCreationTask, Task['c:/testdata2/a'].class
assert_equal FileCreationTask, Task['c:/testdata2/a/b/c'].class
assert_nil Task['c:/testdata2'].comment
assert_equal "WIN32 DESC", Task['c:/testdata2/a/b/c'].comment
assert_nil Task['c:/testdata2/a/b'].comment
verbose(false) {
Task['c:/testdata/a/b'].invoke
Task['c:/testdata2/a/b'].invoke
}
assert File.exist?('c:/testdata/a/b')
assert ! File.exist?('c:/testdata/a/b/c')
assert File.exist?('c:/testdata2/a/b')
assert ! File.exist?('c:/testdata2/a/b/c')
end
end
end
=end