ruby-changes:52631
From: eregon <ko1@a...>
Date: Wed, 26 Sep 2018 03:47:22 +0900 (JST)
Subject: [ruby-changes:52631] eregon:r64843 (trunk): Update to ruby/mspec@2bca8cb
eregon 2018-09-26 03:47:17 +0900 (Wed, 26 Sep 2018) New Revision: 64843 https://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=revision&revision=64843 Log: Update to ruby/mspec@2bca8cb Added files: trunk/spec/mspec/lib/mspec/runner/parallel.rb trunk/spec/mspec/spec/fixtures/chatty_spec.rb trunk/spec/mspec/spec/fixtures/die_spec.rb Modified files: trunk/spec/mspec/lib/mspec/commands/mspec.rb trunk/spec/mspec/lib/mspec/runner/formatters/multi.rb trunk/spec/mspec/lib/mspec/runner/mspec.rb trunk/spec/mspec/spec/integration/run_spec.rb trunk/spec/mspec/spec/runner/formatters/multi_spec.rb Index: spec/mspec/spec/fixtures/chatty_spec.rb =================================================================== --- spec/mspec/spec/fixtures/chatty_spec.rb (nonexistent) +++ spec/mspec/spec/fixtures/chatty_spec.rb (revision 64843) @@ -0,0 +1,8 @@ https://github.com/ruby/ruby/blob/trunk/spec/mspec/spec/fixtures/chatty_spec.rb#L1 +unless defined?(RSpec) + describe "Chatty#spec" do + it "prints too much" do + STDOUT.puts "Hello\nIt's me!" + 1.should == 1 + end + end +end Index: spec/mspec/spec/fixtures/die_spec.rb =================================================================== --- spec/mspec/spec/fixtures/die_spec.rb (nonexistent) +++ spec/mspec/spec/fixtures/die_spec.rb (revision 64843) @@ -0,0 +1,7 @@ https://github.com/ruby/ruby/blob/trunk/spec/mspec/spec/fixtures/die_spec.rb#L1 +unless defined?(RSpec) + describe "Deadly#spec" do + it "dies" do + abort "DEAD" + end + end +end Index: spec/mspec/spec/runner/formatters/multi_spec.rb =================================================================== --- spec/mspec/spec/runner/formatters/multi_spec.rb (revision 64842) +++ spec/mspec/spec/runner/formatters/multi_spec.rb (revision 64843) @@ -9,10 +9,10 @@ describe MultiFormatter, "#aggregate_res https://github.com/ruby/ruby/blob/trunk/spec/mspec/spec/runner/formatters/multi_spec.rb#L9 @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") Index: spec/mspec/spec/integration/run_spec.rb =================================================================== --- spec/mspec/spec/integration/run_spec.rb (revision 64842) +++ spec/mspec/spec/integration/run_spec.rb (revision 64843) @@ -24,23 +24,21 @@ EOS https://github.com/ruby/ruby/blob/trunk/spec/mspec/spec/integration/run_spec.rb#L24 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 https://github.com/ruby/ruby/blob/trunk/spec/mspec/spec/integration/run_spec.rb#L47 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 Index: spec/mspec/lib/mspec/commands/mspec.rb =================================================================== --- spec/mspec/lib/mspec/commands/mspec.rb (revision 64842) +++ spec/mspec/lib/mspec/commands/mspec.rb (revision 64843) @@ -89,72 +89,13 @@ class MSpecMain < MSpecScript https://github.com/ruby/ruby/blob/trunk/spec/mspec/lib/mspec/commands/mspec.rb#L89 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 Index: spec/mspec/lib/mspec/runner/formatters/multi.rb =================================================================== --- spec/mspec/lib/mspec/runner/formatters/multi.rb (revision 64842) +++ spec/mspec/lib/mspec/runner/formatters/multi.rb (revision 64843) @@ -15,15 +15,18 @@ class MultiFormatter < SpinnerFormatter https://github.com/ruby/ruby/blob/trunk/spec/mspec/lib/mspec/runner/formatters/multi.rb#L15 @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 Index: spec/mspec/lib/mspec/runner/mspec.rb =================================================================== --- spec/mspec/lib/mspec/runner/mspec.rb (revision 64842) +++ spec/mspec/lib/mspec/runner/mspec.rb (revision 64843) @@ -50,6 +50,7 @@ module MSpec https://github.com/ruby/ruby/blob/trunk/spec/mspec/lib/mspec/runner/mspec.rb#L50 def self.process STDOUT.puts RUBY_DESCRIPTION + STDOUT.flush actions :start files @@ -58,9 +59,8 @@ module MSpec https://github.com/ruby/ruby/blob/trunk/spec/mspec/lib/mspec/runner/mspec.rb#L59 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 Index: spec/mspec/lib/mspec/runner/parallel.rb =================================================================== --- spec/mspec/lib/mspec/runner/parallel.rb (nonexistent) +++ spec/mspec/lib/mspec/runner/parallel.rb (revision 64843) @@ -0,0 +1,98 @@ https://github.com/ruby/ruby/blob/trunk/spec/mspec/lib/mspec/runner/parallel.rb#L1 +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 -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/