summaryrefslogtreecommitdiff
path: root/spec/mspec/lib/mspec/runner
diff options
context:
space:
mode:
Diffstat (limited to 'spec/mspec/lib/mspec/runner')
-rw-r--r--spec/mspec/lib/mspec/runner/actions/leakchecker.rb61
-rw-r--r--spec/mspec/lib/mspec/runner/actions/timeout.rb89
-rw-r--r--spec/mspec/lib/mspec/runner/context.rb1
-rw-r--r--spec/mspec/lib/mspec/runner/exception.rb2
-rw-r--r--spec/mspec/lib/mspec/runner/formatters/base.rb34
-rw-r--r--spec/mspec/lib/mspec/runner/mspec.rb11
-rw-r--r--spec/mspec/lib/mspec/runner/shared.rb8
7 files changed, 191 insertions, 15 deletions
diff --git a/spec/mspec/lib/mspec/runner/actions/leakchecker.rb b/spec/mspec/lib/mspec/runner/actions/leakchecker.rb
index 596b120d9f..71797b9815 100644
--- a/spec/mspec/lib/mspec/runner/actions/leakchecker.rb
+++ b/spec/mspec/lib/mspec/runner/actions/leakchecker.rb
@@ -173,7 +173,8 @@ class LeakChecker
def find_threads
Thread.list.find_all {|t|
- t != Thread.current && t.alive?
+ t != Thread.current && t.alive? &&
+ !(t.thread_variable?(:"\0__detached_thread__") && t.thread_variable_get(:"\0__detached_thread__"))
}
end
@@ -300,6 +301,7 @@ class LeakCheckerAction
end
def start
+ disable_nss_modules
@checker = LeakChecker.new
end
@@ -315,4 +317,61 @@ class LeakCheckerAction
end
end
end
+
+ private
+
+ # This function is intended to disable all NSS modules when ruby is compiled
+ # against glibc. NSS modules allow the system administrator to load custom
+ # shared objects into all processes using glibc, and use them to customise
+ # the behaviour of username, groupname, hostname, etc lookups. This is
+ # normally configured in the file /etc/nsswitch.conf.
+ # These modules often do things like open cache files or connect to system
+ # daemons like sssd or dbus, which of course means they have open file
+ # descriptors of their own. This can cause the leak-checking functionality
+ # in this file to report that such descriptors have been leaked, and fail
+ # the test suite.
+ # This function uses glibc's __nss_configure_lookup function to override any
+ # configuration in /etc/nsswitch.conf, and just use the built in files/dns
+ # name lookup functionality (which is of course perfectly sufficient for
+ # running ruby/spec).
+ def disable_nss_modules
+ begin
+ require 'fiddle'
+ rescue LoadError
+ # Make sure it's possible to run the test suite on a ruby implementation
+ # which does not (yet?) have Fiddle.
+ return
+ end
+
+ begin
+ libc = Fiddle.dlopen(nil)
+ # Older versions of fiddle don't have Fiddle::Type (and instead rely on Fiddle::TYPE_)
+ # Even older versions of fiddle don't have CONST_STRING,
+ string_type = defined?(Fiddle::TYPE_CONST_STRING) ? Fiddle::TYPE_CONST_STRING : Fiddle::TYPE_VOIDP
+ nss_configure_lookup = Fiddle::Function.new(
+ libc['__nss_configure_lookup'],
+ [string_type, string_type],
+ Fiddle::TYPE_INT
+ )
+ rescue Fiddle::DLError
+ # We're not running with glibc - no need to do this.
+ return
+ end
+
+ nss_configure_lookup.call 'passwd', 'files'
+ nss_configure_lookup.call 'shadow', 'files'
+ nss_configure_lookup.call 'group', 'files'
+ nss_configure_lookup.call 'hosts', 'files dns'
+ nss_configure_lookup.call 'services', 'files'
+ nss_configure_lookup.call 'netgroup', 'files'
+ nss_configure_lookup.call 'automount', 'files'
+ nss_configure_lookup.call 'aliases', 'files'
+ nss_configure_lookup.call 'ethers', 'files'
+ nss_configure_lookup.call 'gshadow', 'files'
+ nss_configure_lookup.call 'initgroups', 'files'
+ nss_configure_lookup.call 'networks', 'files dns'
+ nss_configure_lookup.call 'protocols', 'files'
+ nss_configure_lookup.call 'publickey', 'files'
+ nss_configure_lookup.call 'rpc', 'files'
+ end
end
diff --git a/spec/mspec/lib/mspec/runner/actions/timeout.rb b/spec/mspec/lib/mspec/runner/actions/timeout.rb
index c85bf49ad3..1200926872 100644
--- a/spec/mspec/lib/mspec/runner/actions/timeout.rb
+++ b/spec/mspec/lib/mspec/runner/actions/timeout.rb
@@ -3,11 +3,14 @@ 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
MSpec.register :start, self
MSpec.register :before, self
+ MSpec.register :after, self
MSpec.register :finish, self
end
@@ -35,9 +38,27 @@ class TimeoutAction
if @queue.empty?
elapsed = now - @started
if elapsed > @timeout
- STDERR.puts "\n#{@current_state.description}"
+ if @current_state
+ STDERR.puts "\nExample #{@error_message}:"
+ STDERR.puts "#{@current_state.description}"
+ else
+ STDERR.puts "\nSome code outside an example #{@error_message}"
+ end
STDERR.flush
- abort "Example took longer than the configured timeout of #{@timeout}s"
+
+ show_backtraces
+ 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|
+ kill_wait_one_second :SIGTERM, pid
+ hard_kill :SIGKILL, pid
+ end
+ @fail = true
+ @current_state = nil
+ break # stop this thread, will fail in #after
+ end
end
end
end
@@ -53,8 +74,72 @@ class TimeoutAction
end
end
+ def after(state = nil)
+ @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
@thread.kill
@thread.join
end
+
+ private def hard_kill(signal, pid)
+ begin
+ Process.kill signal, pid
+ rescue Errno::ESRCH
+ # Process already terminated
+ end
+ end
+
+ private def kill_wait_one_second(signal, pid)
+ begin
+ Process.kill signal, pid
+ sleep 1
+ rescue Errno::ESRCH
+ # Process already terminated
+ end
+ end
+
+ private def show_backtraces
+ java_stacktraces = -> pid {
+ if RUBY_ENGINE == 'truffleruby' || RUBY_ENGINE == 'jruby'
+ STDERR.puts 'Java stacktraces:'
+ kill_wait_one_second :SIGQUIT, pid
+ end
+ }
+
+ 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
+ MSpec.subprocesses.each do |pid|
+ STDERR.puts "\nFor subprocess #{pid}"
+ java_stacktraces.call pid
+
+ if RUBY_ENGINE == 'truffleruby'
+ STDERR.puts "\nRuby backtraces:"
+ kill_wait_one_second :SIGALRM, pid
+ else
+ STDERR.puts "Don't know how to print backtraces of a subprocess on #{RUBY_ENGINE}"
+ end
+ end
+ end
+ end
end
diff --git a/spec/mspec/lib/mspec/runner/context.rb b/spec/mspec/lib/mspec/runner/context.rb
index 62483590bb..bcd83b2465 100644
--- a/spec/mspec/lib/mspec/runner/context.rb
+++ b/spec/mspec/lib/mspec/runner/context.rb
@@ -210,6 +210,7 @@ class ContextState
MSpec.clear_expectations
if example
passed = protect nil, example
+ passed = protect nil, -> { MSpec.actions :passed, state, example } if passed
MSpec.actions :example, state, example
protect nil, EXPECTATION_MISSING if !MSpec.expectation? and passed
end
diff --git a/spec/mspec/lib/mspec/runner/exception.rb b/spec/mspec/lib/mspec/runner/exception.rb
index e07f02f684..23375733e6 100644
--- a/spec/mspec/lib/mspec/runner/exception.rb
+++ b/spec/mspec/lib/mspec/runner/exception.rb
@@ -29,7 +29,7 @@ class ExceptionState
if @failure
message
- elsif raise_error_message = @exception.instance_variable_get(:@mspec_raise_error_message)
+ elsif raise_error_message = RaiseErrorMatcher::FAILURE_MESSAGE_FOR_EXCEPTION[@exception]
raise_error_message.join("\n")
else
"#{@exception.class}: #{message}"
diff --git a/spec/mspec/lib/mspec/runner/formatters/base.rb b/spec/mspec/lib/mspec/runner/formatters/base.rb
index 6c075c4d0a..e3b5bb23e0 100644
--- a/spec/mspec/lib/mspec/runner/formatters/base.rb
+++ b/spec/mspec/lib/mspec/runner/formatters/base.rb
@@ -1,9 +1,13 @@
require 'mspec/expectations/expectations'
require 'mspec/runner/actions/timer'
require 'mspec/runner/actions/tally'
+require 'mspec/utils/options'
if ENV['CHECK_LEAKS']
require 'mspec/runner/actions/leakchecker'
+end
+
+if ENV['CHECK_LEAKS'] || ENV['CHECK_CONSTANT_LEAKS']
require 'mspec/runner/actions/constants_leak_checker'
end
@@ -18,10 +22,17 @@ class BaseFormatter
@count = 0 # For subclasses
- if out.nil?
+ if out
+ @out = File.open out, "w"
+ else
@out = $stdout
+ end
+
+ err = MSpecOptions.latest && MSpecOptions.latest.config[:error_output]
+ if err
+ @err = (err == 'stderr') ? $stderr : File.open(err, "w")
else
- @out = File.open out, "w"
+ @err = @out
end
end
@@ -32,8 +43,11 @@ class BaseFormatter
@counter = @tally.counter
if ENV['CHECK_LEAKS']
- save = ENV['CHECK_LEAKS'] == 'save'
LeakCheckerAction.new.register
+ end
+
+ if ENV['CHECK_LEAKS'] || ENV['CHECK_CONSTANT_LEAKS']
+ save = ENV['CHECK_LEAKS'] == 'save' || ENV['CHECK_CONSTANT_LEAKS'] == 'save'
ConstantsLeakCheckerAction.new(save).register
end
@@ -105,6 +119,14 @@ class BaseFormatter
# evaluating the examples.
def finish
print "\n"
+
+ if MSpecOptions.latest && MSpecOptions.latest.config[:print_skips]
+ print "\nSkips:\n" unless MSpec.skips.empty?
+ MSpec.skips.each do |skip, block|
+ print "#{skip.message} in #{(block.source_location || ['?']).join(':')}\n"
+ end
+ end
+
count = 0
@exceptions.each do |exc|
count += 1
@@ -115,9 +137,9 @@ class BaseFormatter
def print_exception(exc, count)
outcome = exc.failure? ? "FAILED" : "ERROR"
- print "\n#{count})\n#{exc.description} #{outcome}\n"
- print exc.message, "\n"
- print exc.backtrace, "\n"
+ @err.print "\n#{count})\n#{exc.description} #{outcome}\n"
+ @err.print exc.message, "\n"
+ @err.print exc.backtrace, "\n"
end
# A convenience method to allow printing to different outputs.
diff --git a/spec/mspec/lib/mspec/runner/mspec.rb b/spec/mspec/lib/mspec/runner/mspec.rb
index 8331086196..97c3f365bc 100644
--- a/spec/mspec/lib/mspec/runner/mspec.rb
+++ b/spec/mspec/lib/mspec/runner/mspec.rb
@@ -26,6 +26,7 @@ module MSpec
@unload = nil
@tagged = nil
@current = nil
+ @passed = nil
@example = nil
@modes = []
@shared = {}
@@ -36,9 +37,11 @@ module MSpec
@repeat = 1
@expectation = nil
@expectations = false
+ @skips = []
+ @subprocesses = []
class << self
- attr_reader :file, :include, :exclude
+ attr_reader :file, :include, :exclude, :skips, :subprocesses
attr_writer :repeat, :randomize
attr_accessor :formatter
end
@@ -116,8 +119,9 @@ module MSpec
rescue SystemExit => e
raise e
rescue SkippedSpecError => e
+ @skips << [e, block]
return false
- rescue Exception => exc
+ rescue Object => exc
register_exit 1
actions :exception, ExceptionState.new(current && current.state, location, exc)
return false
@@ -241,6 +245,7 @@ module MSpec
# :before before a single spec is run
# :add while a describe block is adding examples to run later
# :expectation before a 'should', 'should_receive', etc.
+ # :passed after an example block is run and passes, passed the block, run before :example action
# :example after an example block is run, passed the block
# :exception after an exception is rescued
# :after after a single spec is run
@@ -391,7 +396,7 @@ module MSpec
desc = tag.escape(tag.description)
file = tags_file
if File.exist? file
- lines = IO.readlines(file)
+ lines = File.readlines(file)
File.open(file, "w:utf-8") do |f|
lines.each do |line|
line = line.chomp
diff --git a/spec/mspec/lib/mspec/runner/shared.rb b/spec/mspec/lib/mspec/runner/shared.rb
index 1d68365474..283711c1d7 100644
--- a/spec/mspec/lib/mspec/runner/shared.rb
+++ b/spec/mspec/lib/mspec/runner/shared.rb
@@ -1,10 +1,14 @@
require 'mspec/runner/mspec'
def it_behaves_like(desc, meth, obj = nil)
- send :before, :all do
+ before :all do
@method = meth
@object = obj
end
+ after :all do
+ @method = nil
+ @object = nil
+ end
- send :it_should_behave_like, desc.to_s
+ it_should_behave_like desc.to_s
end