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)
begin
platform_is_not :opal do
command = ruby_cmd(code, opts)
output = `#{command}`
status = Process.last_status
command = ruby_cmd(code, opts)
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
# 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
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|

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

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

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

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

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

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

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

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