From de60139053fa7c561858c5c5556d61c82f361dd9 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Mon, 27 Feb 2023 21:02:42 +0100 Subject: Update to ruby/mspec@dc2eb26 --- spec/mspec/lib/mspec/helpers/ruby_exe.rb | 54 ++++++++++++++-------- spec/mspec/lib/mspec/runner/actions/timeout.rb | 64 ++++++++++++++++++++------ spec/mspec/lib/mspec/runner/mspec.rb | 3 +- spec/mspec/spec/helpers/ruby_exe_spec.rb | 10 ++-- spec/mspec/tool/check_require_spec_helper.rb | 34 ++++++++++++++ 5 files changed, 126 insertions(+), 39 deletions(-) create mode 100755 spec/mspec/tool/check_require_spec_helper.rb (limited to 'spec/mspec') diff --git a/spec/mspec/lib/mspec/helpers/ruby_exe.rb b/spec/mspec/lib/mspec/helpers/ruby_exe.rb index 7fde001cda..2e499d6f9a 100644 --- a/spec/mspec/lib/mspec/helpers/ruby_exe.rb +++ b/spec/mspec/lib/mspec/helpers/ruby_exe.rb @@ -140,28 +140,44 @@ def ruby_exe(code = :not_given, opts = {}) expected_status = opts.fetch(:exit_status, 0) begin - platform_is_not :opal do - command = ruby_cmd(code, opts) - output = `#{command}` - status = Process.last_status - - exit_status = if status.exited? - status.exitstatus - elsif status.signaled? - signame = Signal.signame status.termsig - raise "No signal name?" unless signame - :"SIG#{signame}" - else - raise SpecExpectationNotMetError, "#{exit_status.inspect} is neither exited? nor signaled?" - end - if exit_status != expected_status - formatted_output = output.lines.map { |line| " #{line}" }.join - raise SpecExpectationNotMetError, - "Expected exit status is #{expected_status.inspect} but actual is #{exit_status.inspect} for command ruby_exe(#{command.inspect})\nOutput:\n#{formatted_output}" + command = ruby_cmd(code, opts) + + # Try to avoid the extra shell for 2>&1 + # This is notably useful for TimeoutAction which can then signal the ruby subprocess and not the shell + popen_options = [] + if command.end_with?(' 2>&1') + command = command[0...-5] + popen_options = [{ err: [:child, :out] }] + end + + output = IO.popen(command, *popen_options) do |io| + pid = io.pid + MSpec.subprocesses << pid + begin + io.read + ensure + MSpec.subprocesses.delete(pid) end + end - output + status = Process.last_status + + exit_status = if status.exited? + status.exitstatus + elsif status.signaled? + signame = Signal.signame status.termsig + raise "No signal name?" unless signame + :"SIG#{signame}" + else + raise SpecExpectationNotMetError, "#{exit_status.inspect} is neither exited? nor signaled?" + end + if exit_status != expected_status + formatted_output = output.lines.map { |line| " #{line}" }.join + raise SpecExpectationNotMetError, + "Expected exit status is #{expected_status.inspect} but actual is #{exit_status.inspect} for command ruby_exe(#{command.inspect})\nOutput:\n#{formatted_output}" end + + output ensure saved_env.each { |key, value| ENV[key] = value } env.keys.each do |key| diff --git a/spec/mspec/lib/mspec/runner/actions/timeout.rb b/spec/mspec/lib/mspec/runner/actions/timeout.rb index 543b7366d7..499001c952 100644 --- a/spec/mspec/lib/mspec/runner/actions/timeout.rb +++ b/spec/mspec/lib/mspec/runner/actions/timeout.rb @@ -3,6 +3,8 @@ class TimeoutAction @timeout = timeout @queue = Queue.new @started = now + @fail = false + @error_message = "took longer than the configured timeout of #{@timeout}s" end def register @@ -37,15 +39,25 @@ class TimeoutAction elapsed = now - @started if elapsed > @timeout if @current_state - STDERR.puts "\nExample took longer than the configured timeout of #{@timeout}s:" + STDERR.puts "\nExample #{@error_message}:" STDERR.puts "#{@current_state.description}" else - STDERR.puts "\nSome code outside an example took longer than the configured timeout of #{@timeout}s" + STDERR.puts "\nSome code outside an example #{@error_message}" end STDERR.flush show_backtraces - exit 2 + if MSpec.subprocesses.empty? + exit 2 + else + # Do not exit but signal the subprocess so we can get their output + MSpec.subprocesses.each do |pid| + Process.kill :SIGTERM, pid + end + @fail = true + @current_state = nil + break # stop this thread, will fail in #after + end end end end @@ -65,6 +77,11 @@ class TimeoutAction @queue << -> do @current_state = nil end + + if @fail + STDERR.puts "\n\nThe last example #{@error_message}. See above for the subprocess stacktrace." + exit 2 + end end def finish @@ -73,19 +90,38 @@ class TimeoutAction end private def show_backtraces - if RUBY_ENGINE == 'truffleruby' - STDERR.puts 'Java stacktraces:' - Process.kill :SIGQUIT, Process.pid - sleep 1 - end + java_stacktraces = -> pid { + if RUBY_ENGINE == 'truffleruby' || RUBY_ENGINE == 'jruby' + STDERR.puts 'Java stacktraces:' + Process.kill :SIGQUIT, pid + sleep 1 + end + } - STDERR.puts "\nRuby backtraces:" - if defined?(Truffle::Debug.show_backtraces) - Truffle::Debug.show_backtraces + if MSpec.subprocesses.empty? + java_stacktraces.call Process.pid + + STDERR.puts "\nRuby backtraces:" + if defined?(Truffle::Debug.show_backtraces) + Truffle::Debug.show_backtraces + else + Thread.list.each do |thread| + unless thread == Thread.current + STDERR.puts thread.inspect, thread.backtrace, '' + end + end + end else - Thread.list.each do |thread| - unless thread == Thread.current - STDERR.puts thread.inspect, thread.backtrace, '' + MSpec.subprocesses.each do |pid| + STDERR.puts "\nFor subprocess #{pid}" + java_stacktraces.call pid + + if RUBY_ENGINE == 'truffleruby' + STDERR.puts "\nRuby backtraces:" + Process.kill :SIGALRM, pid + sleep 1 + else + STDERR.puts "Don't know how to print backtraces of a subprocess on #{RUBY_ENGINE}" end end end diff --git a/spec/mspec/lib/mspec/runner/mspec.rb b/spec/mspec/lib/mspec/runner/mspec.rb index 889e085175..bfe783ab2b 100644 --- a/spec/mspec/lib/mspec/runner/mspec.rb +++ b/spec/mspec/lib/mspec/runner/mspec.rb @@ -38,9 +38,10 @@ module MSpec @expectation = nil @expectations = false @skips = [] + @subprocesses = [] class << self - attr_reader :file, :include, :exclude, :skips + attr_reader :file, :include, :exclude, :skips, :subprocesses attr_writer :repeat, :randomize attr_accessor :formatter end diff --git a/spec/mspec/spec/helpers/ruby_exe_spec.rb b/spec/mspec/spec/helpers/ruby_exe_spec.rb index 61225a2756..56bade1ba9 100644 --- a/spec/mspec/spec/helpers/ruby_exe_spec.rb +++ b/spec/mspec/spec/helpers/ruby_exe_spec.rb @@ -145,7 +145,7 @@ RSpec.describe Object, "#ruby_exe" do stub_const 'RUBY_EXE', 'ruby_spec_exe -w -Q' @script = RubyExeSpecs.new - allow(@script).to receive(:`).and_return('OUTPUT') + allow(IO).to receive(:popen).and_return('OUTPUT') status_successful = double(Process::Status, exited?: true, exitstatus: 0) allow(Process).to receive(:last_status).and_return(status_successful) @@ -155,7 +155,7 @@ RSpec.describe Object, "#ruby_exe" do code = "code" options = {} output = "output" - allow(@script).to receive(:`).and_return(output) + expect(IO).to receive(:popen).and_return(output) expect(@script.ruby_exe(code, options)).to eq output end @@ -168,7 +168,7 @@ RSpec.describe Object, "#ruby_exe" do code = "code" options = {} expect(@script).to receive(:ruby_cmd).and_return("ruby_cmd") - expect(@script).to receive(:`).with("ruby_cmd") + expect(IO).to receive(:popen).with("ruby_cmd") @script.ruby_exe(code, options) end @@ -227,7 +227,7 @@ RSpec.describe Object, "#ruby_exe" do expect(ENV).to receive(:[]=).with("ABC", "xyz") expect(ENV).to receive(:[]=).with("ABC", "123") - expect(@script).to receive(:`).and_raise(Exception) + expect(IO).to receive(:popen).and_raise(Exception) expect do @script.ruby_exe nil, :env => { :ABC => "xyz" } end.to raise_error(Exception) @@ -248,7 +248,7 @@ RSpec.describe Object, "#ruby_exe" do it "does not raise exception when command ends with expected status" do output = "output" - allow(@script).to receive(:`).and_return(output) + expect(IO).to receive(:popen).and_return(output) expect(@script.ruby_exe("path", exit_status: 4)).to eq output end diff --git a/spec/mspec/tool/check_require_spec_helper.rb b/spec/mspec/tool/check_require_spec_helper.rb new file mode 100755 index 0000000000..07126e68dc --- /dev/null +++ b/spec/mspec/tool/check_require_spec_helper.rb @@ -0,0 +1,34 @@ +#!/usr/bin/env ruby + +# This script is used to check that each *_spec.rb file has +# a relative_require for spec_helper which should live higher +# up in the ruby/spec repo directory tree. +# +# Prints errors to $stderr and returns a non-zero exit code when +# errors are found. +# +# Related to https://github.com/ruby/spec/pull/992 + +def check_file(fn) + File.foreach(fn) do |line| + return $1 if line =~ /^\s*require_relative\s*['"](.*spec_helper)['"]/ + end + nil +end + +rootdir = ARGV[0] || "." +fglob = File.join(rootdir, "**", "*_spec.rb") +specfiles = Dir.glob(fglob) +raise "No spec files found in #{fglob.inspect}. Give an argument to specify the root-directory of ruby/spec" if specfiles.empty? + +errors = 0 +specfiles.sort.each do |fn| + result = check_file(fn) + if result.nil? + warn "Missing require_relative for *spec_helper for file: #{fn}" + errors += 1 + end +end + +puts "# Found #{errors} files with require_relative spec_helper issues." +exit 1 if errors > 0 -- cgit v1.2.1