This commit is contained in:
Benoit Daloze 2023-02-27 21:02:42 +01:00
Родитель f38c6552f9
Коммит de60139053
5 изменённых файлов: 127 добавлений и 40 удалений

Просмотреть файл

@ -140,28 +140,44 @@ def ruby_exe(code = :not_given, opts = {})
expected_status = opts.fetch(:exit_status, 0) expected_status = opts.fetch(:exit_status, 0)
begin begin
platform_is_not :opal do command = ruby_cmd(code, opts)
command = ruby_cmd(code, opts)
output = `#{command}`
status = Process.last_status
exit_status = if status.exited? # Try to avoid the extra shell for 2>&1
status.exitstatus # This is notably useful for TimeoutAction which can then signal the ruby subprocess and not the shell
elsif status.signaled? popen_options = []
signame = Signal.signame status.termsig if command.end_with?(' 2>&1')
raise "No signal name?" unless signame command = command[0...-5]
:"SIG#{signame}" popen_options = [{ err: [:child, :out] }]
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
end 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
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 ensure
saved_env.each { |key, value| ENV[key] = value } saved_env.each { |key, value| ENV[key] = value }
env.keys.each do |key| env.keys.each do |key|

Просмотреть файл

@ -3,6 +3,8 @@ class TimeoutAction
@timeout = timeout @timeout = timeout
@queue = Queue.new @queue = Queue.new
@started = now @started = now
@fail = false
@error_message = "took longer than the configured timeout of #{@timeout}s"
end end
def register def register
@ -37,15 +39,25 @@ class TimeoutAction
elapsed = now - @started elapsed = now - @started
if elapsed > @timeout if elapsed > @timeout
if @current_state 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}" STDERR.puts "#{@current_state.description}"
else 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 end
STDERR.flush STDERR.flush
show_backtraces 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 end
end end
@ -65,6 +77,11 @@ class TimeoutAction
@queue << -> do @queue << -> do
@current_state = nil @current_state = nil
end end
if @fail
STDERR.puts "\n\nThe last example #{@error_message}. See above for the subprocess stacktrace."
exit 2
end
end end
def finish def finish
@ -73,19 +90,38 @@ class TimeoutAction
end end
private def show_backtraces private def show_backtraces
if RUBY_ENGINE == 'truffleruby' java_stacktraces = -> pid {
STDERR.puts 'Java stacktraces:' if RUBY_ENGINE == 'truffleruby' || RUBY_ENGINE == 'jruby'
Process.kill :SIGQUIT, Process.pid STDERR.puts 'Java stacktraces:'
sleep 1 Process.kill :SIGQUIT, pid
end sleep 1
end
}
STDERR.puts "\nRuby backtraces:" if MSpec.subprocesses.empty?
if defined?(Truffle::Debug.show_backtraces) java_stacktraces.call Process.pid
Truffle::Debug.show_backtraces
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 else
Thread.list.each do |thread| MSpec.subprocesses.each do |pid|
unless thread == Thread.current STDERR.puts "\nFor subprocess #{pid}"
STDERR.puts thread.inspect, thread.backtrace, '' 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 end
end end

Просмотреть файл

@ -38,9 +38,10 @@ module MSpec
@expectation = nil @expectation = nil
@expectations = false @expectations = false
@skips = [] @skips = []
@subprocesses = []
class << self class << self
attr_reader :file, :include, :exclude, :skips attr_reader :file, :include, :exclude, :skips, :subprocesses
attr_writer :repeat, :randomize attr_writer :repeat, :randomize
attr_accessor :formatter attr_accessor :formatter
end end

Просмотреть файл

@ -145,7 +145,7 @@ RSpec.describe Object, "#ruby_exe" do
stub_const 'RUBY_EXE', 'ruby_spec_exe -w -Q' stub_const 'RUBY_EXE', 'ruby_spec_exe -w -Q'
@script = RubyExeSpecs.new @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) status_successful = double(Process::Status, exited?: true, exitstatus: 0)
allow(Process).to receive(:last_status).and_return(status_successful) allow(Process).to receive(:last_status).and_return(status_successful)
@ -155,7 +155,7 @@ RSpec.describe Object, "#ruby_exe" do
code = "code" code = "code"
options = {} options = {}
output = "output" 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 expect(@script.ruby_exe(code, options)).to eq output
end end
@ -168,7 +168,7 @@ RSpec.describe Object, "#ruby_exe" do
code = "code" code = "code"
options = {} options = {}
expect(@script).to receive(:ruby_cmd).and_return("ruby_cmd") 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) @script.ruby_exe(code, options)
end end
@ -227,7 +227,7 @@ RSpec.describe Object, "#ruby_exe" do
expect(ENV).to receive(:[]=).with("ABC", "xyz") expect(ENV).to receive(:[]=).with("ABC", "xyz")
expect(ENV).to receive(:[]=).with("ABC", "123") expect(ENV).to receive(:[]=).with("ABC", "123")
expect(@script).to receive(:`).and_raise(Exception) expect(IO).to receive(:popen).and_raise(Exception)
expect do expect do
@script.ruby_exe nil, :env => { :ABC => "xyz" } @script.ruby_exe nil, :env => { :ABC => "xyz" }
end.to raise_error(Exception) 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 it "does not raise exception when command ends with expected status" do
output = "output" 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 expect(@script.ruby_exe("path", exit_status: 4)).to eq output
end end

Просмотреть файл

@ -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