mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
Update to ruby/mspec@2bca8cb
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@64843 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
parent
2466288d27
commit
e2d74af38b
8 changed files with 150 additions and 77 deletions
|
@ -89,72 +89,13 @@ class MSpecMain < MSpecScript
|
|||
def register; end
|
||||
|
||||
def multi_exec(argv)
|
||||
MSpec.register_files @files
|
||||
|
||||
require 'mspec/runner/formatters/multi'
|
||||
formatter = MultiFormatter.new
|
||||
if config[:formatter]
|
||||
warn "formatter options is ignored due to multi option"
|
||||
end
|
||||
warn "formatter options is ignored due to multi option" if config[:formatter]
|
||||
|
||||
output_files = []
|
||||
require 'mspec/runner/parallel'
|
||||
processes = cores(@files.size)
|
||||
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+")
|
||||
}
|
||||
|
||||
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
|
||||
ParallelRunner.new(@files, processes, formatter, argv).run
|
||||
end
|
||||
|
||||
def run
|
||||
|
|
|
@ -15,15 +15,18 @@ class MultiFormatter < SpinnerFormatter
|
|||
@exceptions = []
|
||||
|
||||
files.each do |file|
|
||||
d = File.open(file, "r") { |f| YAML.load f }
|
||||
contents = File.read(file)
|
||||
d = YAML.load(contents)
|
||||
File.delete file
|
||||
|
||||
@exceptions += Array(d['exceptions'])
|
||||
@tally.files! d['files']
|
||||
@tally.examples! d['examples']
|
||||
@tally.expectations! d['expectations']
|
||||
@tally.errors! d['errors']
|
||||
@tally.failures! d['failures']
|
||||
if d # The file might be empty if the child process died
|
||||
@exceptions += Array(d['exceptions'])
|
||||
@tally.files! d['files']
|
||||
@tally.examples! d['examples']
|
||||
@tally.expectations! d['expectations']
|
||||
@tally.errors! d['errors']
|
||||
@tally.failures! d['failures']
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -50,6 +50,7 @@ module MSpec
|
|||
|
||||
def self.process
|
||||
STDOUT.puts RUBY_DESCRIPTION
|
||||
STDOUT.flush
|
||||
|
||||
actions :start
|
||||
files
|
||||
|
@ -58,9 +59,8 @@ module MSpec
|
|||
|
||||
def self.each_file(&block)
|
||||
if ENV["MSPEC_MULTI"]
|
||||
STDOUT.print "."
|
||||
STDOUT.flush
|
||||
while file = STDIN.gets and file = file.chomp
|
||||
while file = STDIN.gets
|
||||
file = file.chomp
|
||||
return if file == "QUIT"
|
||||
yield file
|
||||
begin
|
||||
|
|
98
spec/mspec/lib/mspec/runner/parallel.rb
Normal file
98
spec/mspec/lib/mspec/runner/parallel.rb
Normal 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
|
8
spec/mspec/spec/fixtures/chatty_spec.rb
vendored
Normal file
8
spec/mspec/spec/fixtures/chatty_spec.rb
vendored
Normal 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
7
spec/mspec/spec/fixtures/die_spec.rb
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
unless defined?(RSpec)
|
||||
describe "Deadly#spec" do
|
||||
it "dies" do
|
||||
abort "DEAD"
|
||||
end
|
||||
end
|
||||
end
|
|
@ -24,23 +24,21 @@ EOS
|
|||
|
||||
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"
|
||||
fixtures = "spec/fixtures"
|
||||
|
||||
it "runs the specs" do
|
||||
fixtures = "spec/fixtures"
|
||||
out, ret = run_mspec("run", "#{fixtures}/a_spec.rb")
|
||||
out.should == "RUBY_DESCRIPTION\n.FE\n#{a_spec_output}\n#{a_stats}"
|
||||
ret.success?.should == false
|
||||
end
|
||||
|
||||
it "directly with mspec-run runs the specs" do
|
||||
fixtures = "spec/fixtures"
|
||||
out, ret = run_mspec("-run", "#{fixtures}/a_spec.rb")
|
||||
out.should == "RUBY_DESCRIPTION\n.FE\n#{a_spec_output}\n#{a_stats}"
|
||||
ret.success?.should == false
|
||||
end
|
||||
|
||||
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")
|
||||
progress_bar =
|
||||
"\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}"
|
||||
ret.success?.should == false
|
||||
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
|
||||
|
|
|
@ -9,10 +9,10 @@ describe MultiFormatter, "#aggregate_results" do
|
|||
@file = double("file").as_null_object
|
||||
|
||||
File.stub(:delete)
|
||||
YAML.stub(:load)
|
||||
File.stub(:read)
|
||||
|
||||
@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.timer.stub(:format).and_return("Finished in 42 seconds")
|
||||
|
|
Loading…
Reference in a new issue