1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@64843 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
eregon 2018-09-25 18:47:17 +00:00
parent 2466288d27
commit e2d74af38b
8 changed files with 150 additions and 77 deletions

View file

@ -89,72 +89,13 @@ class MSpecMain < MSpecScript
def register; end def register; end
def multi_exec(argv) def multi_exec(argv)
MSpec.register_files @files
require 'mspec/runner/formatters/multi' require 'mspec/runner/formatters/multi'
formatter = MultiFormatter.new formatter = MultiFormatter.new
if config[:formatter] warn "formatter options is ignored due to multi option" if config[:formatter]
warn "formatter options is ignored due to multi option"
end
output_files = [] require 'mspec/runner/parallel'
processes = cores(@files.size) processes = cores(@files.size)
children = processes.times.map { |i| ParallelRunner.new(@files, processes, formatter, argv).run
name = tmp "mspec-multi-#{i}"
output_files << name
env = {
"SPEC_TEMP_DIR" => "rubyspec_temp_#{i}",
"MSPEC_MULTI" => i.to_s
}
command = argv + ["-fy", "-o", name]
$stderr.puts "$ #{command.join(' ')}" if $MSPEC_DEBUG
IO.popen([env, *command, close_others: false], "rb+")
}
puts children.map { |child| child.gets }.uniq
formatter.start
last_files = {}
until @files.empty?
IO.select(children)[0].each { |io|
reply = io.read(1)
case reply
when '.'
formatter.unload
when nil
raise "Worker died!"
else
while chunk = (io.read_nonblock(4096) rescue nil)
reply += chunk
end
reply.chomp!('.')
msg = "A child mspec-run process printed unexpected output on STDOUT"
if last_file = last_files[io]
msg += " while running #{last_file}"
end
abort "\n#{msg}: #{reply.inspect}"
end
unless @files.empty?
file = @files.shift
last_files[io] = file
io.puts file
end
}
end
success = true
children.each { |child|
child.puts "QUIT"
_pid, status = Process.wait2(child.pid)
success &&= status.success?
child.close
}
formatter.aggregate_results(output_files)
formatter.finish
success
end end
def run def run

View file

@ -15,15 +15,18 @@ class MultiFormatter < SpinnerFormatter
@exceptions = [] @exceptions = []
files.each do |file| files.each do |file|
d = File.open(file, "r") { |f| YAML.load f } contents = File.read(file)
d = YAML.load(contents)
File.delete file File.delete file
@exceptions += Array(d['exceptions']) if d # The file might be empty if the child process died
@tally.files! d['files'] @exceptions += Array(d['exceptions'])
@tally.examples! d['examples'] @tally.files! d['files']
@tally.expectations! d['expectations'] @tally.examples! d['examples']
@tally.errors! d['errors'] @tally.expectations! d['expectations']
@tally.failures! d['failures'] @tally.errors! d['errors']
@tally.failures! d['failures']
end
end end
end end

View file

@ -50,6 +50,7 @@ module MSpec
def self.process def self.process
STDOUT.puts RUBY_DESCRIPTION STDOUT.puts RUBY_DESCRIPTION
STDOUT.flush
actions :start actions :start
files files
@ -58,9 +59,8 @@ module MSpec
def self.each_file(&block) def self.each_file(&block)
if ENV["MSPEC_MULTI"] if ENV["MSPEC_MULTI"]
STDOUT.print "." while file = STDIN.gets
STDOUT.flush file = file.chomp
while file = STDIN.gets and file = file.chomp
return if file == "QUIT" return if file == "QUIT"
yield file yield file
begin begin

View file

@ -0,0 +1,98 @@
class ParallelRunner
def initialize(files, processes, formatter, argv)
@files = files
@processes = processes
@formatter = formatter
@argv = argv
@last_files = {}
@output_files = []
@success = true
end
def launch_children
@children = @processes.times.map { |i|
name = tmp "mspec-multi-#{i}"
@output_files << name
env = {
"SPEC_TEMP_DIR" => "rubyspec_temp_#{i}",
"MSPEC_MULTI" => i.to_s
}
command = @argv + ["-fy", "-o", name]
$stderr.puts "$ #{command.join(' ')}" if $MSPEC_DEBUG
IO.popen([env, *command, close_others: false], "rb+")
}
end
def handle(child, message)
case message
when '.'
@formatter.unload
send_new_file_or_quit(child)
else
if message == nil
msg = "A child mspec-run process died unexpectedly"
else
msg = "A child mspec-run process printed unexpected output on STDOUT"
while chunk = (child.read_nonblock(4096) rescue nil)
message += chunk
end
message.chomp!('.')
msg += ": #{message.inspect}"
end
if last_file = @last_files[child]
msg += " while running #{last_file}"
end
@success = false
quit(child)
abort "\n#{msg}"
end
end
def quit(child)
begin
child.puts "QUIT"
rescue Errno::EPIPE
# The child process already died
end
_pid, status = Process.wait2(child.pid)
@success &&= status.success?
child.close
@children.delete(child)
end
def send_new_file_or_quit(child)
if @files.empty?
quit(child)
else
file = @files.shift
@last_files[child] = file
child.puts file
end
end
def run
MSpec.register_files @files
launch_children
puts @children.map { |child| child.gets }.uniq
@formatter.start
begin
@children.each { |child| send_new_file_or_quit(child) }
until @children.empty?
IO.select(@children)[0].each { |child|
handle(child, child.read(1))
}
end
ensure
@children.dup.each { |child| quit(child) }
@formatter.aggregate_results(@output_files)
@formatter.finish
end
@success
end
end

View file

@ -0,0 +1,8 @@
unless defined?(RSpec)
describe "Chatty#spec" do
it "prints too much" do
STDOUT.puts "Hello\nIt's me!"
1.should == 1
end
end
end

7
spec/mspec/spec/fixtures/die_spec.rb vendored Normal file
View file

@ -0,0 +1,7 @@
unless defined?(RSpec)
describe "Deadly#spec" do
it "dies" do
abort "DEAD"
end
end
end

View file

@ -24,23 +24,21 @@ EOS
a_stats = "1 file, 3 examples, 2 expectations, 1 failure, 1 error, 0 tagged\n" a_stats = "1 file, 3 examples, 2 expectations, 1 failure, 1 error, 0 tagged\n"
ab_stats = "2 files, 4 examples, 3 expectations, 1 failure, 1 error, 0 tagged\n" ab_stats = "2 files, 4 examples, 3 expectations, 1 failure, 1 error, 0 tagged\n"
fixtures = "spec/fixtures"
it "runs the specs" do it "runs the specs" do
fixtures = "spec/fixtures"
out, ret = run_mspec("run", "#{fixtures}/a_spec.rb") out, ret = run_mspec("run", "#{fixtures}/a_spec.rb")
out.should == "RUBY_DESCRIPTION\n.FE\n#{a_spec_output}\n#{a_stats}" out.should == "RUBY_DESCRIPTION\n.FE\n#{a_spec_output}\n#{a_stats}"
ret.success?.should == false ret.success?.should == false
end end
it "directly with mspec-run runs the specs" do it "directly with mspec-run runs the specs" do
fixtures = "spec/fixtures"
out, ret = run_mspec("-run", "#{fixtures}/a_spec.rb") out, ret = run_mspec("-run", "#{fixtures}/a_spec.rb")
out.should == "RUBY_DESCRIPTION\n.FE\n#{a_spec_output}\n#{a_stats}" out.should == "RUBY_DESCRIPTION\n.FE\n#{a_spec_output}\n#{a_stats}"
ret.success?.should == false ret.success?.should == false
end end
it "runs the specs in parallel with -j" do it "runs the specs in parallel with -j" do
fixtures = "spec/fixtures"
out, ret = run_mspec("run", "-j #{fixtures}/a_spec.rb #{fixtures}/b_spec.rb") out, ret = run_mspec("run", "-j #{fixtures}/a_spec.rb #{fixtures}/b_spec.rb")
progress_bar = progress_bar =
"\r[/ | 0% | 00:00:00] \e[0;32m 0F \e[0;32m 0E\e[0m " + "\r[/ | 0% | 00:00:00] \e[0;32m 0F \e[0;32m 0E\e[0m " +
@ -49,4 +47,22 @@ EOS
out.should == "RUBY_DESCRIPTION\n#{progress_bar}\n#{a_spec_output}\n#{ab_stats}" out.should == "RUBY_DESCRIPTION\n#{progress_bar}\n#{a_spec_output}\n#{ab_stats}"
ret.success?.should == false ret.success?.should == false
end end
it "gives a useful error message when a subprocess dies in parallel mode" do
out, ret = run_mspec("run", "-j #{fixtures}/b_spec.rb #{fixtures}/die_spec.rb")
lines = out.lines
lines.should include "A child mspec-run process died unexpectedly while running CWD/spec/fixtures/die_spec.rb\n"
lines.should include "Finished in D.DDDDDD seconds\n"
lines.last.should =~ /^\d files?, \d examples?, \d expectations?, 0 failures, 0 errors, 0 tagged$/
ret.success?.should == false
end
it "gives a useful error message when a subprocess prints unexpected output on STDOUT in parallel mode" do
out, ret = run_mspec("run", "-j #{fixtures}/b_spec.rb #{fixtures}/chatty_spec.rb")
lines = out.lines
lines.should include "A child mspec-run process printed unexpected output on STDOUT: #{'"Hello\nIt\'s me!\n"'} while running CWD/spec/fixtures/chatty_spec.rb\n"
lines.should include "Finished in D.DDDDDD seconds\n"
lines.last.should == "2 files, 2 examples, 2 expectations, 0 failures, 0 errors, 0 tagged\n"
ret.success?.should == false
end
end end

View file

@ -9,10 +9,10 @@ describe MultiFormatter, "#aggregate_results" do
@file = double("file").as_null_object @file = double("file").as_null_object
File.stub(:delete) File.stub(:delete)
YAML.stub(:load) File.stub(:read)
@hash = { "files"=>1, "examples"=>1, "expectations"=>2, "failures"=>0, "errors"=>0 } @hash = { "files"=>1, "examples"=>1, "expectations"=>2, "failures"=>0, "errors"=>0 }
File.stub(:open).and_yield(@file).and_return(@hash) YAML.stub(:load).and_return(@hash)
@formatter = MultiFormatter.new @formatter = MultiFormatter.new
@formatter.timer.stub(:format).and_return("Finished in 42 seconds") @formatter.timer.stub(:format).and_return("Finished in 42 seconds")