summaryrefslogtreecommitdiff
path: root/spec/mspec
diff options
context:
space:
mode:
Diffstat (limited to 'spec/mspec')
-rwxr-xr-xspec/mspec/bin/mspec2
-rwxr-xr-xspec/mspec/lib/mspec/commands/mkspec.rb16
-rwxr-xr-xspec/mspec/lib/mspec/commands/mspec.rb11
-rw-r--r--spec/mspec/lib/mspec/expectations/expectations.rb4
-rw-r--r--spec/mspec/lib/mspec/guards/platform.rb4
-rw-r--r--spec/mspec/lib/mspec/guards/superuser.rb10
-rw-r--r--spec/mspec/lib/mspec/guards/version.rb28
-rw-r--r--spec/mspec/lib/mspec/helpers/datetime.rb1
-rw-r--r--spec/mspec/lib/mspec/helpers/io.rb4
-rw-r--r--spec/mspec/lib/mspec/helpers/numeric.rb4
-rw-r--r--spec/mspec/lib/mspec/helpers/ruby_exe.rb47
-rw-r--r--spec/mspec/lib/mspec/helpers/tmp.rb3
-rw-r--r--spec/mspec/lib/mspec/matchers/base.rb30
-rw-r--r--spec/mspec/lib/mspec/matchers/complain.rb2
-rw-r--r--spec/mspec/lib/mspec/matchers/output.rb8
-rw-r--r--spec/mspec/lib/mspec/matchers/raise_error.rb6
-rw-r--r--spec/mspec/lib/mspec/mocks/mock.rb25
-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
-rw-r--r--spec/mspec/lib/mspec/utils/name_map.rb7
-rw-r--r--spec/mspec/lib/mspec/utils/options.rb17
-rw-r--r--spec/mspec/lib/mspec/utils/script.rb16
-rw-r--r--spec/mspec/lib/mspec/utils/warnings.rb43
-rw-r--r--spec/mspec/spec/commands/mkspec_spec.rb2
-rw-r--r--spec/mspec/spec/commands/mspec_spec.rb27
-rw-r--r--spec/mspec/spec/helpers/numeric_spec.rb10
-rw-r--r--spec/mspec/spec/helpers/ruby_exe_spec.rb22
-rw-r--r--spec/mspec/spec/integration/run_spec.rb9
-rw-r--r--spec/mspec/spec/integration/tag_spec.rb9
-rw-r--r--spec/mspec/spec/mocks/mock_spec.rb21
-rw-r--r--spec/mspec/spec/runner/context_spec.rb2
-rw-r--r--spec/mspec/spec/spec_helper.rb2
-rw-r--r--spec/mspec/spec/utils/script_spec.rb5
-rwxr-xr-xspec/mspec/tool/check_require_spec_helper.rb34
-rw-r--r--spec/mspec/tool/remove_old_guards.rb63
-rw-r--r--spec/mspec/tool/sync/sync-rubyspec.rb2
-rwxr-xr-xspec/mspec/tool/tag_from_output.rb30
42 files changed, 529 insertions, 203 deletions
diff --git a/spec/mspec/bin/mspec b/spec/mspec/bin/mspec
index f833257bb0..5bd753c06d 100755
--- a/spec/mspec/bin/mspec
+++ b/spec/mspec/bin/mspec
@@ -4,4 +4,4 @@ $:.unshift File.expand_path('../../lib', __FILE__)
require 'mspec/commands/mspec'
-MSpecMain.main
+MSpecMain.main(false)
diff --git a/spec/mspec/lib/mspec/commands/mkspec.rb b/spec/mspec/lib/mspec/commands/mkspec.rb
index d10cc35d18..a31cb2191c 100755
--- a/spec/mspec/lib/mspec/commands/mkspec.rb
+++ b/spec/mspec/lib/mspec/commands/mkspec.rb
@@ -95,7 +95,9 @@ class MkSpec
def write_spec(file, meth, exists)
if exists
- out = `#{ruby} #{MSPEC_HOME}/bin/mspec-run --dry-run --unguarded -fs -e '#{meth}' #{file}`
+ command = "#{RbConfig.ruby} #{MSPEC_HOME}/bin/mspec-run --dry-run --unguarded -fs -e '#{meth}' #{file}"
+ puts "$ #{command}" if $DEBUG
+ out = `#{command}`
return if out.include?(meth)
end
@@ -133,18 +135,6 @@ EOS
end
end
- ##
- # Determine and return the path of the ruby executable.
-
- def ruby
- ruby = File.join(RbConfig::CONFIG['bindir'],
- RbConfig::CONFIG['ruby_install_name'])
-
- ruby.gsub! File::SEPARATOR, File::ALT_SEPARATOR if File::ALT_SEPARATOR
-
- return ruby
- end
-
def self.main
ENV['MSPEC_RUNNER'] = '1'
diff --git a/spec/mspec/lib/mspec/commands/mspec.rb b/spec/mspec/lib/mspec/commands/mspec.rb
index 9d82949ff1..f5341c699d 100755
--- a/spec/mspec/lib/mspec/commands/mspec.rb
+++ b/spec/mspec/lib/mspec/commands/mspec.rb
@@ -25,6 +25,7 @@ class MSpecMain < MSpecScript
config[:command] = argv.shift if ["ci", "run", "tag"].include?(argv[0])
options = MSpecOptions.new "mspec [COMMAND] [options] (FILE|DIRECTORY|GLOB)+", 30, config
+ @options = options
options.doc " The mspec command sets up and invokes the sub-commands"
options.doc " (see below) to enable, for instance, running the specs"
@@ -37,11 +38,6 @@ class MSpecMain < MSpecScript
options.targets
- options.on("--warnings", "Don't suppress warnings") do
- config[:flags] << '-w'
- ENV['OUTPUT_WARNINGS'] = '1'
- end
-
options.on("-j", "--multi", "Run multiple (possibly parallel) subprocesses") do
config[:multi] = true
end
@@ -110,8 +106,9 @@ class MSpecMain < MSpecScript
if config[:multi]
exit multi_exec(argv)
else
- $stderr.puts "$ #{argv.join(' ')}"
- $stderr.flush
+ log = config[:options].include?('--error-output') ? $stdout : $stderr
+ log.puts "$ #{argv.join(' ')}"
+ log.flush
exec(*argv, close_others: false)
end
end
diff --git a/spec/mspec/lib/mspec/expectations/expectations.rb b/spec/mspec/lib/mspec/expectations/expectations.rb
index 8d01dc22ab..09852ab557 100644
--- a/spec/mspec/lib/mspec/expectations/expectations.rb
+++ b/spec/mspec/lib/mspec/expectations/expectations.rb
@@ -32,4 +32,8 @@ class SpecExpectation
result_to_s = MSpec.format(result)
raise SpecExpectationNotMetError, "Expected #{receiver_to_s}#{predicate_to_s}#{args_to_s}\n#{expectation} but was #{result_to_s}"
end
+
+ def self.fail_single_arg_predicate(receiver, predicate, arg, result, expectation)
+ fail_predicate(receiver, predicate, [arg], nil, result, expectation)
+ end
end
diff --git a/spec/mspec/lib/mspec/guards/platform.rb b/spec/mspec/lib/mspec/guards/platform.rb
index 2d5c2de6b6..e87b08a4c1 100644
--- a/spec/mspec/lib/mspec/guards/platform.rb
+++ b/spec/mspec/lib/mspec/guards/platform.rb
@@ -41,6 +41,10 @@ class PlatformGuard < SpecGuard
os?(:windows)
end
+ def self.wasi?
+ os?(:wasi)
+ end
+
def self.wsl?
if defined?(@wsl_p)
@wsl_p
diff --git a/spec/mspec/lib/mspec/guards/superuser.rb b/spec/mspec/lib/mspec/guards/superuser.rb
index e92ea7e862..24daf9b26c 100644
--- a/spec/mspec/lib/mspec/guards/superuser.rb
+++ b/spec/mspec/lib/mspec/guards/superuser.rb
@@ -6,10 +6,20 @@ class SuperUserGuard < SpecGuard
end
end
+class RealSuperUserGuard < SpecGuard
+ def match?
+ Process.uid == 0
+ end
+end
+
def as_superuser(&block)
SuperUserGuard.new.run_if(:as_superuser, &block)
end
+def as_real_superuser(&block)
+ RealSuperUserGuard.new.run_if(:as_real_superuser, &block)
+end
+
def as_user(&block)
SuperUserGuard.new.run_unless(:as_user, &block)
end
diff --git a/spec/mspec/lib/mspec/guards/version.rb b/spec/mspec/lib/mspec/guards/version.rb
index 20f8c06d38..f5ea1988ae 100644
--- a/spec/mspec/lib/mspec/guards/version.rb
+++ b/spec/mspec/lib/mspec/guards/version.rb
@@ -33,6 +33,30 @@ class VersionGuard < SpecGuard
@version >= @requirement
end
end
+
+ @kernel_version = nil
+ def self.kernel_version
+ if @kernel_version
+ @kernel_version
+ else
+ if v = RUBY_PLATFORM[/darwin(\d+)/, 1] # build time version
+ uname = v
+ else
+ begin
+ require 'etc'
+ etc = true
+ rescue LoadError
+ etc = false
+ end
+ if etc and Etc.respond_to?(:uname)
+ uname = Etc.uname.fetch(:release)
+ else
+ uname = `uname -r`.chomp
+ end
+ end
+ @kernel_version = uname
+ end
+ end
end
def version_is(base_version, requirement, &block)
@@ -42,3 +66,7 @@ end
def ruby_version_is(requirement, &block)
VersionGuard.new(VersionGuard::FULL_RUBY_VERSION, requirement).run_if(:ruby_version_is, &block)
end
+
+def kernel_version_is(requirement, &block)
+ VersionGuard.new(VersionGuard.kernel_version, requirement).run_if(:kernel_version_is, &block)
+end
diff --git a/spec/mspec/lib/mspec/helpers/datetime.rb b/spec/mspec/lib/mspec/helpers/datetime.rb
index 3a40cc0902..84ac86b686 100644
--- a/spec/mspec/lib/mspec/helpers/datetime.rb
+++ b/spec/mspec/lib/mspec/helpers/datetime.rb
@@ -25,6 +25,7 @@ def new_datetime(opts = {})
end
def with_timezone(name, offset = nil, daylight_saving_zone = "")
+ skip "WASI doesn't have TZ concept" if PlatformGuard.wasi?
zone = name.dup
if offset
diff --git a/spec/mspec/lib/mspec/helpers/io.rb b/spec/mspec/lib/mspec/helpers/io.rb
index 29c6c37a1a..2ad14f47a1 100644
--- a/spec/mspec/lib/mspec/helpers/io.rb
+++ b/spec/mspec/lib/mspec/helpers/io.rb
@@ -7,7 +7,7 @@ class IOStub
end
def write(*str)
- self << str.join
+ self << str.join('')
end
def << str
@@ -16,7 +16,7 @@ class IOStub
end
def print(*str)
- write(str.join + $\.to_s)
+ write(str.join('') + $\.to_s)
end
def method_missing(name, *args, &block)
diff --git a/spec/mspec/lib/mspec/helpers/numeric.rb b/spec/mspec/lib/mspec/helpers/numeric.rb
index db1fde64d8..c1ed81a233 100644
--- a/spec/mspec/lib/mspec/helpers/numeric.rb
+++ b/spec/mspec/lib/mspec/helpers/numeric.rb
@@ -9,7 +9,9 @@ def infinity_value
end
def bignum_value(plus = 0)
- 0x8000_0000_0000_0000 + plus
+ # Must be >= fixnum_max + 2, so -bignum_value is < fixnum_min
+ # A fixed value has the advantage to be the same numeric value for all Rubies and is much easier to spec
+ (2**64) + plus
end
def max_long
diff --git a/spec/mspec/lib/mspec/helpers/ruby_exe.rb b/spec/mspec/lib/mspec/helpers/ruby_exe.rb
index 4948ec09f1..2e499d6f9a 100644
--- a/spec/mspec/lib/mspec/helpers/ruby_exe.rb
+++ b/spec/mspec/lib/mspec/helpers/ruby_exe.rb
@@ -112,6 +112,8 @@ unless Object.const_defined?(:RUBY_EXE) and RUBY_EXE
end
def ruby_exe(code = :not_given, opts = {})
+ skip "WASI doesn't provide subprocess" if PlatformGuard.wasi?
+
if opts[:dir]
raise "ruby_exe(..., dir: dir) is no longer supported, use Dir.chdir"
end
@@ -138,19 +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}`
-
- exit_status = Process.last_status.exitstatus
- 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/helpers/tmp.rb b/spec/mspec/lib/mspec/helpers/tmp.rb
index b2a38ee983..4c0eddab75 100644
--- a/spec/mspec/lib/mspec/helpers/tmp.rb
+++ b/spec/mspec/lib/mspec/helpers/tmp.rb
@@ -12,7 +12,7 @@ else
end
SPEC_TEMP_DIR = spec_temp_dir
-SPEC_TEMP_UNIQUIFIER = "0"
+SPEC_TEMP_UNIQUIFIER = +"0"
at_exit do
begin
@@ -41,6 +41,7 @@ def tmp(name, uniquify = true)
if uniquify and !name.empty?
slash = name.rindex "/"
index = slash ? slash + 1 : 0
+ name = +name
name.insert index, "#{SPEC_TEMP_UNIQUIFIER.succ!}-"
end
diff --git a/spec/mspec/lib/mspec/matchers/base.rb b/spec/mspec/lib/mspec/matchers/base.rb
index 94d3b71e55..d9d7f6fec0 100644
--- a/spec/mspec/lib/mspec/matchers/base.rb
+++ b/spec/mspec/lib/mspec/matchers/base.rb
@@ -16,15 +16,24 @@ class SpecPositiveOperatorMatcher < BasicObject
end
def ==(expected)
- method_missing(:==, expected)
+ result = @actual == expected
+ unless result
+ ::SpecExpectation.fail_single_arg_predicate(@actual, :==, expected, result, "to be truthy")
+ end
end
def !=(expected)
- method_missing(:!=, expected)
+ result = @actual != expected
+ unless result
+ ::SpecExpectation.fail_single_arg_predicate(@actual, :!=, expected, result, "to be truthy")
+ end
end
def equal?(expected)
- method_missing(:equal?, expected)
+ result = @actual.equal?(expected)
+ unless result
+ ::SpecExpectation.fail_single_arg_predicate(@actual, :equal?, expected, result, "to be truthy")
+ end
end
def method_missing(name, *args, &block)
@@ -41,15 +50,24 @@ class SpecNegativeOperatorMatcher < BasicObject
end
def ==(expected)
- method_missing(:==, expected)
+ result = @actual == expected
+ if result
+ ::SpecExpectation.fail_single_arg_predicate(@actual, :==, expected, result, "to be falsy")
+ end
end
def !=(expected)
- method_missing(:!=, expected)
+ result = @actual != expected
+ if result
+ ::SpecExpectation.fail_single_arg_predicate(@actual, :!=, expected, result, "to be falsy")
+ end
end
def equal?(expected)
- method_missing(:equal?, expected)
+ result = @actual.equal?(expected)
+ if result
+ ::SpecExpectation.fail_single_arg_predicate(@actual, :equal?, expected, result, "to be falsy")
+ end
end
def method_missing(name, *args, &block)
diff --git a/spec/mspec/lib/mspec/matchers/complain.rb b/spec/mspec/lib/mspec/matchers/complain.rb
index 887e72b4b0..19310c0bbb 100644
--- a/spec/mspec/lib/mspec/matchers/complain.rb
+++ b/spec/mspec/lib/mspec/matchers/complain.rb
@@ -19,7 +19,6 @@ class ComplainMatcher
@verbose = $VERBOSE
err = IOStub.new
- Thread.current[:in_mspec_complain_matcher] = true
$stderr = err
$VERBOSE = @options.key?(:verbose) ? @options[:verbose] : false
begin
@@ -27,7 +26,6 @@ class ComplainMatcher
ensure
$VERBOSE = @verbose
$stderr = @saved_err
- Thread.current[:in_mspec_complain_matcher] = false
end
@warning = err.to_s
diff --git a/spec/mspec/lib/mspec/matchers/output.rb b/spec/mspec/lib/mspec/matchers/output.rb
index 20721df743..5bb5d55027 100644
--- a/spec/mspec/lib/mspec/matchers/output.rb
+++ b/spec/mspec/lib/mspec/matchers/output.rb
@@ -42,12 +42,12 @@ class OutputMatcher
expected_out = "\n"
actual_out = "\n"
unless @out.nil?
- expected_out += " $stdout: #{@out.inspect}\n"
- actual_out += " $stdout: #{@stdout.inspect}\n"
+ expected_out += " $stdout: #{MSpec.format(@out)}\n"
+ actual_out += " $stdout: #{MSpec.format(@stdout.to_s)}\n"
end
unless @err.nil?
- expected_out += " $stderr: #{@err.inspect}\n"
- actual_out += " $stderr: #{@stderr.inspect}\n"
+ expected_out += " $stderr: #{MSpec.format(@err)}\n"
+ actual_out += " $stderr: #{MSpec.format(@stderr.to_s)}\n"
end
["Expected:#{expected_out}", " got:#{actual_out}"]
end
diff --git a/spec/mspec/lib/mspec/matchers/raise_error.rb b/spec/mspec/lib/mspec/matchers/raise_error.rb
index 0ee8953519..54378bb34c 100644
--- a/spec/mspec/lib/mspec/matchers/raise_error.rb
+++ b/spec/mspec/lib/mspec/matchers/raise_error.rb
@@ -1,4 +1,6 @@
class RaiseErrorMatcher
+ FAILURE_MESSAGE_FOR_EXCEPTION = {}.compare_by_identity
+
attr_writer :block
def initialize(exception, message, &block)
@@ -15,7 +17,7 @@ class RaiseErrorMatcher
def matches?(proc)
@result = proc.call
return false
- rescue Exception => actual
+ rescue Object => actual
@actual = actual
if matching_exception?(actual)
@@ -23,7 +25,7 @@ class RaiseErrorMatcher
@block[actual] if @block
return true
else
- actual.instance_variable_set(:@mspec_raise_error_message, failure_message)
+ FAILURE_MESSAGE_FOR_EXCEPTION[actual] = failure_message
raise actual
end
end
diff --git a/spec/mspec/lib/mspec/mocks/mock.rb b/spec/mspec/lib/mspec/mocks/mock.rb
index 28a083cc15..c61ba35ea7 100644
--- a/spec/mspec/lib/mspec/mocks/mock.rb
+++ b/spec/mspec/lib/mspec/mocks/mock.rb
@@ -18,20 +18,16 @@ module Mock
@stubs ||= Hash.new { |h,k| h[k] = [] }
end
- def self.replaced_name(obj, sym)
- :"__mspec_#{obj.__id__}_#{sym}__"
+ def self.replaced_name(key)
+ :"__mspec_#{key.last}__"
end
def self.replaced_key(obj, sym)
- [replaced_name(obj, sym), sym]
+ [obj.__id__, sym]
end
- def self.has_key?(keys, sym)
- !!keys.find { |k| k.first == sym }
- end
-
- def self.replaced?(sym)
- has_key?(mocks.keys, sym) or has_key?(stubs.keys, sym)
+ def self.replaced?(key)
+ mocks.include?(key) or stubs.include?(key)
end
def self.clear_replaced(key)
@@ -40,8 +36,9 @@ module Mock
end
def self.mock_respond_to?(obj, sym, include_private = false)
- name = replaced_name(obj, :respond_to?)
- if replaced? name
+ key = replaced_key(obj, :respond_to?)
+ if replaced? key
+ name = replaced_name(key)
obj.__send__ name, sym, include_private
else
obj.respond_to? sym, include_private
@@ -59,8 +56,8 @@ module Mock
return
end
- if (sym == :respond_to? or mock_respond_to?(obj, sym, true)) and !replaced?(key.first)
- meta.__send__ :alias_method, key.first, sym
+ if (sym == :respond_to? or mock_respond_to?(obj, sym, true)) and !replaced?(key)
+ meta.__send__ :alias_method, replaced_name(key), sym
end
suppress_warning {
@@ -191,7 +188,7 @@ module Mock
next
end
- replaced = key.first
+ replaced = replaced_name(key)
sym = key.last
meta = obj.singleton_class
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
diff --git a/spec/mspec/lib/mspec/utils/name_map.rb b/spec/mspec/lib/mspec/utils/name_map.rb
index a389b9d1de..bf70e651a2 100644
--- a/spec/mspec/lib/mspec/utils/name_map.rb
+++ b/spec/mspec/lib/mspec/utils/name_map.rb
@@ -51,6 +51,10 @@ class NameMap
SpecVersion
]
+ ALWAYS_PRIVATE = %w[
+ initialize initialize_copy initialize_clone initialize_dup respond_to_missing?
+ ].map(&:to_sym)
+
def initialize(filter = false)
@seen = {}
@filter = filter
@@ -86,7 +90,8 @@ class NameMap
hash["#{name}."] = ms.sort unless ms.empty?
ms = m.public_instance_methods(false) +
- m.protected_instance_methods(false)
+ m.protected_instance_methods(false) +
+ (m.private_instance_methods(false) & ALWAYS_PRIVATE)
ms.map! { |x| x.to_s }
hash["#{name}#"] = ms.sort unless ms.empty?
diff --git a/spec/mspec/lib/mspec/utils/options.rb b/spec/mspec/lib/mspec/utils/options.rb
index bef1dbdd2e..3b5962dbe6 100644
--- a/spec/mspec/lib/mspec/utils/options.rb
+++ b/spec/mspec/lib/mspec/utils/options.rb
@@ -32,6 +32,10 @@ class MSpecOptions
# Raised if an unrecognized option is encountered.
class ParseError < Exception; end
+ class << self
+ attr_accessor :latest
+ end
+
attr_accessor :config, :banner, :width, :options
def initialize(banner = "", width = 30, config = nil)
@@ -46,7 +50,7 @@ class MSpecOptions
@extra << x
}
- yield self if block_given?
+ MSpecOptions.latest = self
end
# Registers an option. Acceptable formats for arguments are:
@@ -311,6 +315,11 @@ class MSpecOptions
"Write formatter output to FILE") do |f|
config[:output] = f
end
+
+ on("--error-output", "FILE",
+ "Write error output of failing specs to FILE, or $stderr if value is 'stderr'.") do |f|
+ config[:error_output] = f
+ end
end
def filters
@@ -414,6 +423,10 @@ class MSpecOptions
end
MSpec.register :load, obj
end
+
+ on("--print-skips", "Print skips") do
+ config[:print_skips] = true
+ end
end
def interrupt
@@ -464,7 +477,7 @@ class MSpecOptions
def debug
on("-d", "--debug",
- "Set MSpec debugging flag for more verbose output") do
+ "Disable MSpec backtrace filtering") do
$MSPEC_DEBUG = true
end
end
diff --git a/spec/mspec/lib/mspec/utils/script.rb b/spec/mspec/lib/mspec/utils/script.rb
index a77476ee2e..e86beaab86 100644
--- a/spec/mspec/lib/mspec/utils/script.rb
+++ b/spec/mspec/lib/mspec/utils/script.rb
@@ -37,6 +37,17 @@ class MSpecScript
config[key]
end
+ class << self
+ attr_accessor :child_process
+ end
+
+ # True if the current process is the one going to run the specs with `MSpec.process`.
+ # False for e.g. `mspec` which exec's to `mspec-run`.
+ # This is useful in .mspec config files.
+ def self.child_process?
+ MSpecScript.child_process
+ end
+
def initialize
check_version!
@@ -267,10 +278,11 @@ class MSpecScript
# Instantiates an instance and calls the series of methods to
# invoke the script.
- def self.main
+ def self.main(child_process = true)
+ MSpecScript.child_process = child_process
+
script = new
script.load_default
- script.try_load '~/.mspecrc'
script.options
script.signals
script.register
diff --git a/spec/mspec/lib/mspec/utils/warnings.rb b/spec/mspec/lib/mspec/utils/warnings.rb
index 0d3d36fada..23efc696a5 100644
--- a/spec/mspec/lib/mspec/utils/warnings.rb
+++ b/spec/mspec/lib/mspec/utils/warnings.rb
@@ -8,46 +8,3 @@ if Object.const_defined?(:Warning) and Warning.respond_to?(:[]=)
Warning[:deprecated] = true
Warning[:experimental] = false
end
-
-if Object.const_defined?(:Warning) and Warning.respond_to?(:warn)
- def Warning.warn(message, category: nil)
- # Suppress any warning inside the method to prevent recursion
- verbose = $VERBOSE
- $VERBOSE = nil
-
- if Thread.current[:in_mspec_complain_matcher]
- return $stderr.write(message)
- end
-
- case message
- # $VERBOSE = true warnings
- when /(.+\.rb):(\d+):.+possibly useless use of (<|<=|==|>=|>) in void context/
- # Make sure there is a .should otherwise it is missing
- line_nb = Integer($2)
- unless File.exist?($1) and /\.should(_not)? (<|<=|==|>=|>)/ === File.readlines($1)[line_nb-1]
- $stderr.write message
- end
- when /possibly useless use of (\+|-) in void context/
- when /assigned but unused variable/
- when /method redefined/
- when /previous definition of/
- when /instance variable @.+ not initialized/
- when /statement not reached/
- when /shadowing outer local variable/
- when /setting Encoding.default_(in|ex)ternal/
- when /unknown (un)?pack directive/
- when /(un)?trust(ed\?)? is deprecated/
- when /\.exists\? is a deprecated name/
- when /Float .+ out of range/
- when /passing a block to String#(bytes|chars|codepoints|lines) is deprecated/
- when /core\/string\/modulo_spec\.rb:\d+: warning: too many arguments for format string/
- when /regexp\/shared\/new_ascii(_8bit)?\.rb:\d+: warning: Unknown escape .+ is ignored/
- else
- $stderr.write message
- end
- ensure
- $VERBOSE = verbose
- end
-else
- $VERBOSE = nil unless ENV['OUTPUT_WARNINGS']
-end
diff --git a/spec/mspec/spec/commands/mkspec_spec.rb b/spec/mspec/spec/commands/mkspec_spec.rb
index 825add7212..32262723de 100644
--- a/spec/mspec/spec/commands/mkspec_spec.rb
+++ b/spec/mspec/spec/commands/mkspec_spec.rb
@@ -194,7 +194,7 @@ RSpec.describe MkSpec, "#write_spec" do
end
it "checks if specs exist for the method if the spec file exists" do
- name = Regexp.escape(@script.ruby)
+ name = Regexp.escape(RbConfig.ruby)
expect(@script).to receive(:`).with(
%r"#{name} #{MSPEC_HOME}/bin/mspec-run --dry-run --unguarded -fs -e 'Object#inspect' spec/core/tcejbo/inspect_spec.rb")
@script.write_spec("spec/core/tcejbo/inspect_spec.rb", "Object#inspect", true)
diff --git a/spec/mspec/spec/commands/mspec_spec.rb b/spec/mspec/spec/commands/mspec_spec.rb
index 82201c2075..d19bebb2d6 100644
--- a/spec/mspec/spec/commands/mspec_spec.rb
+++ b/spec/mspec/spec/commands/mspec_spec.rb
@@ -92,33 +92,6 @@ RSpec.describe MSpecMain, "#run" do
end
end
-RSpec.describe "The --warnings option" do
- before :each do
- @options, @config = new_option
- allow(MSpecOptions).to receive(:new).and_return(@options)
- @script = MSpecMain.new
- allow(@script).to receive(:config).and_return(@config)
- end
-
- it "is enabled by #options" do
- allow(@options).to receive(:on)
- expect(@options).to receive(:on).with("--warnings", an_instance_of(String))
- @script.options
- end
-
- it "sets flags to -w" do
- @config[:flags] = []
- @script.options ["--warnings"]
- expect(@config[:flags]).to include("-w")
- end
-
- it "set OUTPUT_WARNINGS = '1' in the environment" do
- ENV['OUTPUT_WARNINGS'] = '0'
- @script.options ["--warnings"]
- expect(ENV['OUTPUT_WARNINGS']).to eq('1')
- end
-end
-
RSpec.describe "The -j, --multi option" do
before :each do
@options, @config = new_option
diff --git a/spec/mspec/spec/helpers/numeric_spec.rb b/spec/mspec/spec/helpers/numeric_spec.rb
index e65f3e8610..64495b7276 100644
--- a/spec/mspec/spec/helpers/numeric_spec.rb
+++ b/spec/mspec/spec/helpers/numeric_spec.rb
@@ -4,11 +4,17 @@ require 'mspec/helpers'
RSpec.describe Object, "#bignum_value" do
it "returns a value that is an instance of Bignum on any platform" do
- expect(bignum_value).to eq(0x8000_0000_0000_0000)
+ expect(bignum_value).to be > fixnum_max
end
it "returns the default value incremented by the argument" do
- expect(bignum_value(42)).to eq(0x8000_0000_0000_002a)
+ expect(bignum_value(42)).to eq(bignum_value + 42)
+ end
+end
+
+RSpec.describe Object, "-bignum_value" do
+ it "returns a value that is an instance of Bignum on any platform" do
+ expect(-bignum_value).to be < fixnum_min
end
end
diff --git a/spec/mspec/spec/helpers/ruby_exe_spec.rb b/spec/mspec/spec/helpers/ruby_exe_spec.rb
index 79ce55ca75..56bade1ba9 100644
--- a/spec/mspec/spec/helpers/ruby_exe_spec.rb
+++ b/spec/mspec/spec/helpers/ruby_exe_spec.rb
@@ -145,9 +145,9 @@ 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, exitstatus: 0)
+ status_successful = double(Process::Status, exited?: true, exitstatus: 0)
allow(Process).to receive(:last_status).and_return(status_successful)
end
@@ -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
@@ -176,7 +176,7 @@ RSpec.describe Object, "#ruby_exe" do
code = "code"
options = {}
- status_failed = double(Process::Status, exitstatus: 4)
+ status_failed = double(Process::Status, exited?: true, exitstatus: 4)
allow(Process).to receive(:last_status).and_return(status_failed)
expect {
@@ -184,16 +184,16 @@ RSpec.describe Object, "#ruby_exe" do
}.to raise_error(%r{Expected exit status is 0 but actual is 4 for command ruby_exe\(.+\)})
end
- it "shows in the exception message if exitstatus is nil (e.g., signal)" do
+ it "shows in the exception message if a signal killed the process" do
code = "code"
options = {}
- status_failed = double(Process::Status, exitstatus: nil)
+ status_failed = double(Process::Status, exited?: false, signaled?: true, termsig: Signal.list.fetch('TERM'))
allow(Process).to receive(:last_status).and_return(status_failed)
expect {
@script.ruby_exe(code, options)
- }.to raise_error(%r{Expected exit status is 0 but actual is nil for command ruby_exe\(.+\)})
+ }.to raise_error(%r{Expected exit status is 0 but actual is :SIGTERM for command ruby_exe\(.+\)})
end
describe "with :dir option" do
@@ -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)
@@ -236,7 +236,7 @@ RSpec.describe Object, "#ruby_exe" do
describe "with :exit_status option" do
before do
- status_failed = double(Process::Status, exitstatus: 4)
+ status_failed = double(Process::Status, exited?: true, exitstatus: 4)
allow(Process).to receive(:last_status).and_return(status_failed)
end
@@ -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/spec/integration/run_spec.rb b/spec/mspec/spec/integration/run_spec.rb
index 90dc051543..ea0735e9b2 100644
--- a/spec/mspec/spec/integration/run_spec.rb
+++ b/spec/mspec/spec/integration/run_spec.rb
@@ -1,20 +1,21 @@
require 'spec_helper'
RSpec.describe "Running mspec" do
+ q = BACKTRACE_QUOTE
a_spec_output = <<EOS
1)
Foo#bar errors FAILED
Expected 1 == 2
to be truthy but was false
-CWD/spec/fixtures/a_spec.rb:8:in `block (2 levels) in <top (required)>'
-CWD/spec/fixtures/a_spec.rb:2:in `<top (required)>'
+CWD/spec/fixtures/a_spec.rb:8:in #{q}block (2 levels) in <top (required)>'
+CWD/spec/fixtures/a_spec.rb:2:in #{q}<top (required)>'
2)
Foo#bar fails ERROR
RuntimeError: failure
-CWD/spec/fixtures/a_spec.rb:12:in `block (2 levels) in <top (required)>'
-CWD/spec/fixtures/a_spec.rb:2:in `<top (required)>'
+CWD/spec/fixtures/a_spec.rb:12:in #{q}block (2 levels) in <top (required)>'
+CWD/spec/fixtures/a_spec.rb:2:in #{q}<top (required)>'
Finished in D.DDDDDD seconds
EOS
diff --git a/spec/mspec/spec/integration/tag_spec.rb b/spec/mspec/spec/integration/tag_spec.rb
index 33df1cfd40..ae08e9d45f 100644
--- a/spec/mspec/spec/integration/tag_spec.rb
+++ b/spec/mspec/spec/integration/tag_spec.rb
@@ -13,6 +13,7 @@ RSpec.describe "Running mspec tag" do
it "tags the failing specs" do
fixtures = "spec/fixtures"
out, ret = run_mspec("tag", "--add fails --fail #{fixtures}/tagging_spec.rb")
+ q = BACKTRACE_QUOTE
expect(out).to eq <<EOS
RUBY_DESCRIPTION
.FF
@@ -26,15 +27,15 @@ Tag#me érròrs in unicode
Tag#me errors FAILED
Expected 1 == 2
to be truthy but was false
-CWD/spec/fixtures/tagging_spec.rb:9:in `block (2 levels) in <top (required)>'
-CWD/spec/fixtures/tagging_spec.rb:3:in `<top (required)>'
+CWD/spec/fixtures/tagging_spec.rb:9:in #{q}block (2 levels) in <top (required)>'
+CWD/spec/fixtures/tagging_spec.rb:3:in #{q}<top (required)>'
2)
Tag#me érròrs in unicode FAILED
Expected 1 == 2
to be truthy but was false
-CWD/spec/fixtures/tagging_spec.rb:13:in `block (2 levels) in <top (required)>'
-CWD/spec/fixtures/tagging_spec.rb:3:in `<top (required)>'
+CWD/spec/fixtures/tagging_spec.rb:13:in #{q}block (2 levels) in <top (required)>'
+CWD/spec/fixtures/tagging_spec.rb:3:in #{q}<top (required)>'
Finished in D.DDDDDD seconds
diff --git a/spec/mspec/spec/mocks/mock_spec.rb b/spec/mspec/spec/mocks/mock_spec.rb
index 73f9bdfa14..7426e0ff88 100644
--- a/spec/mspec/spec/mocks/mock_spec.rb
+++ b/spec/mspec/spec/mocks/mock_spec.rb
@@ -22,14 +22,14 @@ end
RSpec.describe Mock, ".replaced_name" do
it "returns the name for a method that is being replaced by a mock method" do
m = double('a fake id')
- expect(Mock.replaced_name(m, :method_call)).to eq(:"__mspec_#{m.object_id}_method_call__")
+ expect(Mock.replaced_name(Mock.replaced_key(m, :method_call))).to eq(:"__mspec_method_call__")
end
end
RSpec.describe Mock, ".replaced_key" do
it "returns a key used internally by Mock" do
m = double('a fake id')
- expect(Mock.replaced_key(m, :method_call)).to eq([:"__mspec_#{m.object_id}_method_call__", :method_call])
+ expect(Mock.replaced_key(m, :method_call)).to eq([m.object_id, :method_call])
end
end
@@ -42,16 +42,16 @@ RSpec.describe Mock, ".replaced?" do
it "returns true if a method has been stubbed on an object" do
Mock.install_method @mock, :method_call
- expect(Mock.replaced?(Mock.replaced_name(@mock, :method_call))).to be_truthy
+ expect(Mock.replaced?(Mock.replaced_key(@mock, :method_call))).to be_truthy
end
it "returns true if a method has been mocked on an object" do
Mock.install_method @mock, :method_call, :stub
- expect(Mock.replaced?(Mock.replaced_name(@mock, :method_call))).to be_truthy
+ expect(Mock.replaced?(Mock.replaced_key(@mock, :method_call))).to be_truthy
end
it "returns false if a method has not been stubbed or mocked" do
- expect(Mock.replaced?(Mock.replaced_name(@mock, :method_call))).to be_falsey
+ expect(Mock.replaced?(Mock.replaced_key(@mock, :method_call))).to be_falsey
end
end
@@ -197,11 +197,11 @@ RSpec.describe Mock, ".install_method" do
Mock.install_method @mock, :method_call
expect(@mock).to respond_to(:method_call)
- expect(@mock).not_to respond_to(Mock.replaced_name(@mock, :method_call))
+ expect(@mock).not_to respond_to(Mock.replaced_name(Mock.replaced_key(@mock, :method_call)))
Mock.install_method @mock, :method_call, :stub
expect(@mock).to respond_to(:method_call)
- expect(@mock).not_to respond_to(Mock.replaced_name(@mock, :method_call))
+ expect(@mock).not_to respond_to(Mock.replaced_name(Mock.replaced_key(@mock, :method_call)))
end
end
@@ -493,7 +493,7 @@ RSpec.describe Mock, ".cleanup" do
it "removes the replaced method if the mock method overrides an existing method" do
def @mock.already_here() :hey end
expect(@mock).to respond_to(:already_here)
- replaced_name = Mock.replaced_name(@mock, :already_here)
+ replaced_name = Mock.replaced_name(Mock.replaced_key(@mock, :already_here))
Mock.install_method @mock, :already_here
expect(@mock).to respond_to(replaced_name)
@@ -521,10 +521,9 @@ RSpec.describe Mock, ".cleanup" do
replaced_key = Mock.replaced_key(@mock, :method_call)
expect(Mock).to receive(:clear_replaced).with(replaced_key)
- replaced_name = Mock.replaced_name(@mock, :method_call)
- expect(Mock.replaced?(replaced_name)).to be_truthy
+ expect(Mock.replaced?(replaced_key)).to be_truthy
Mock.cleanup
- expect(Mock.replaced?(replaced_name)).to be_falsey
+ expect(Mock.replaced?(replaced_key)).to be_falsey
end
end
diff --git a/spec/mspec/spec/runner/context_spec.rb b/spec/mspec/spec/runner/context_spec.rb
index a864428aec..9ebc708c0c 100644
--- a/spec/mspec/spec/runner/context_spec.rb
+++ b/spec/mspec/spec/runner/context_spec.rb
@@ -914,7 +914,7 @@ RSpec.describe ContextState, "#it_should_behave_like" do
it "raises an Exception if unable to find the shared ContextState" do
expect(MSpec).to receive(:retrieve_shared).and_return(nil)
- expect { @state.it_should_behave_like "this" }.to raise_error(Exception)
+ expect { @state.it_should_behave_like :this }.to raise_error(Exception)
end
describe "for nested ContextState instances" do
diff --git a/spec/mspec/spec/spec_helper.rb b/spec/mspec/spec/spec_helper.rb
index 3a749581ee..5cabfe5626 100644
--- a/spec/mspec/spec/spec_helper.rb
+++ b/spec/mspec/spec/spec_helper.rb
@@ -66,3 +66,5 @@ PublicMSpecMatchers = Class.new {
include MSpecMatchers
public :raise_error
}.new
+
+BACKTRACE_QUOTE = RUBY_VERSION >= "3.4" ? "'" : "`"
diff --git a/spec/mspec/spec/utils/script_spec.rb b/spec/mspec/spec/utils/script_spec.rb
index d9f6eac9a9..c35bda8b47 100644
--- a/spec/mspec/spec/utils/script_spec.rb
+++ b/spec/mspec/spec/utils/script_spec.rb
@@ -96,11 +96,6 @@ RSpec.describe MSpecScript, ".main" do
MSpecScript.main
end
- it "attempts to load the '~/.mspecrc' script" do
- expect(@script).to receive(:try_load).with('~/.mspecrc')
- MSpecScript.main
- end
-
it "calls the #options method on the script" do
expect(@script).to receive(:options)
MSpecScript.main
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
diff --git a/spec/mspec/tool/remove_old_guards.rb b/spec/mspec/tool/remove_old_guards.rb
index 718e351e11..3fd95e6b31 100644
--- a/spec/mspec/tool/remove_old_guards.rb
+++ b/spec/mspec/tool/remove_old_guards.rb
@@ -21,20 +21,24 @@ def remove_guards(guard, keep)
puts file
lines = contents.lines.to_a
while first = lines.find_index { |line| line =~ guard }
+ comment = first
+ while comment > 0 and lines[comment-1] =~ /^(\s*)#/
+ comment -= 1
+ end
indent = lines[first][/^(\s*)/, 1].length
last = (first+1...lines.size).find { |i|
space = lines[i][/^(\s*)end$/, 1] and space.length == indent
}
raise file unless last
if keep
- lines[first..last] = lines[first+1..last-1].map { |l| dedent(l) }
+ lines[comment..last] = lines[first+1..last-1].map { |l| dedent(l) }
else
- if first > 0 and lines[first-1] == "\n"
- first -= 1
+ if comment > 0 and lines[comment-1] == "\n"
+ comment -= 1
elsif lines[last+1] == "\n"
last += 1
end
- lines[first..last] = []
+ lines[comment..last] = []
end
end
File.binwrite file, lines.join
@@ -42,6 +46,51 @@ def remove_guards(guard, keep)
end
end
+def remove_empty_files
+ each_spec_file do |file|
+ unless file.include?("fixtures/")
+ lines = File.readlines(file)
+ if lines.all? { |line| line.chomp.empty? or line.start_with?('require', '#') }
+ puts "Removing empty file #{file}"
+ File.delete(file)
+ end
+ end
+ end
+end
+
+def remove_unused_shared_specs
+ shared_groups = {}
+ # Dir["**/shared/**/*.rb"].each do |shared|
+ each_spec_file do |shared|
+ next if File.basename(shared) == 'constants.rb'
+ contents = File.binread(shared)
+ found = false
+ contents.scan(/^\s*describe (:[\w_?]+), shared: true do$/) {
+ shared_groups[$1] = 0
+ found = true
+ }
+ if !found and shared.include?('shared/') and !shared.include?('fixtures/') and !shared.end_with?('/constants.rb')
+ puts "no shared describe in #{shared} ?"
+ end
+ end
+
+ each_spec_file do |file|
+ contents = File.binread(file)
+ contents.scan(/(?:it_behaves_like|it_should_behave_like) (:[\w_?]+)[,\s]/) do
+ puts $1 unless shared_groups.key?($1)
+ shared_groups[$1] += 1
+ end
+ end
+
+ shared_groups.each_pair do |group, value|
+ if value == 0
+ puts "Shared describe #{group} seems unused"
+ elsif value == 1
+ puts "Shared describe #{group} seems used only once" if $VERBOSE
+ end
+ end
+end
+
def search(regexp)
each_spec_file do |file|
contents = File.binread(file)
@@ -60,7 +109,11 @@ version = Regexp.escape(ARGV.fetch(0))
version += "(?:\\.0)?" if version.count(".") < 2
remove_guards(/ruby_version_is (["'])#{version}\1 do/, true)
remove_guards(/ruby_version_is (["'])[0-9.]*\1 *... *(["'])#{version}\2 do/, false)
-remove_guards(/ruby_bug "#\d+", (["'])[0-9.]*\1 *... *(["'])#{version}\2 do/, true)
+remove_guards(/ruby_bug ["']#\d+["'], (["'])[0-9.]*\1 *... *(["'])#{version}\2 do/, true)
+
+remove_empty_files
+remove_unused_shared_specs
+puts "Search:"
search(/(["'])#{version}\1/)
search(/^\s*#.+#{version}/)
diff --git a/spec/mspec/tool/sync/sync-rubyspec.rb b/spec/mspec/tool/sync/sync-rubyspec.rb
index b4c79d2afc..13f1d8004d 100644
--- a/spec/mspec/tool/sync/sync-rubyspec.rb
+++ b/spec/mspec/tool/sync/sync-rubyspec.rb
@@ -20,7 +20,7 @@ IMPLS = {
MSPEC = ARGV.delete('--mspec')
-CHECK_LAST_MERGE = ENV['CHECK_LAST_MERGE'] != 'false'
+CHECK_LAST_MERGE = !MSPEC && ENV['CHECK_LAST_MERGE'] != 'false'
TEST_MASTER = ENV['TEST_MASTER'] != 'false'
MSPEC_REPO = File.expand_path("../../..", __FILE__)
diff --git a/spec/mspec/tool/tag_from_output.rb b/spec/mspec/tool/tag_from_output.rb
index ebe13434c2..b6b4603855 100755
--- a/spec/mspec/tool/tag_from_output.rb
+++ b/spec/mspec/tool/tag_from_output.rb
@@ -3,6 +3,8 @@
# Adds tags based on error and failures output (e.g., from a CI log),
# without running any spec code.
+tag = ENV["TAG"] || "fails"
+
tags_dir = %w[
spec/tags
spec/tags/ruby
@@ -11,6 +13,11 @@ abort 'Could not find tags directory' unless tags_dir
output = ARGF.readlines
+# Automatically strip datetime of GitHub Actions
+if output.first =~ /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d+Z /
+ output = output.map { |line| line.split(' ', 2).last }
+end
+
NUMBER = /^\d+\)$/
ERROR_OR_FAILED = / (ERROR|FAILED)$/
SPEC_FILE = /^(\/.+_spec\.rb)\:\d+/
@@ -22,11 +29,24 @@ output.slice_before(NUMBER).select { |number, *rest|
description = error_line.match(ERROR_OR_FAILED).pre_match
spec_file = rest.find { |line| line =~ SPEC_FILE }
- unless spec_file
- warn "Could not find file for:\n#{error_line}"
- next
+ if spec_file
+ spec_file = spec_file[SPEC_FILE, 1] or raise
+ else
+ if error_line =~ /^([\w:]+)[#\.](\w+) /
+ mod, method = $1, $2
+ file = "#{mod.downcase.gsub('::', '/')}/#{method}_spec.rb"
+ spec_file = ['spec/ruby/core', 'spec/ruby/library', *Dir.glob('spec/ruby/library/*')].find { |dir|
+ path = "#{dir}/#{file}"
+ break path if File.exist?(path)
+ }
+ end
+
+ unless spec_file
+ warn "Could not find file for:\n#{error_line}"
+ next
+ end
end
- spec_file = spec_file[SPEC_FILE, 1]
+
prefix = spec_file.index('spec/ruby/') || spec_file.index('spec/truffle/')
spec_file = spec_file[prefix..-1]
@@ -36,7 +56,7 @@ output.slice_before(NUMBER).select { |number, *rest|
dir = File.dirname(tags_file)
Dir.mkdir(dir) unless Dir.exist?(dir)
- tag_line = "fails:#{description}"
+ tag_line = "#{tag}:#{description}"
lines = File.exist?(tags_file) ? File.readlines(tags_file, chomp: true) : []
unless lines.include?(tag_line)
puts tags_file