summaryrefslogtreecommitdiff
path: root/spec/mspec
diff options
context:
space:
mode:
Diffstat (limited to 'spec/mspec')
-rwxr-xr-xspec/mspec/lib/mspec/commands/mkspec.rb16
-rwxr-xr-xspec/mspec/lib/mspec/commands/mspec.rb5
-rw-r--r--spec/mspec/lib/mspec/guards/superuser.rb10
-rw-r--r--spec/mspec/lib/mspec/helpers/io.rb4
-rw-r--r--spec/mspec/lib/mspec/helpers/ruby_exe.rb54
-rw-r--r--spec/mspec/lib/mspec/helpers/tmp.rb3
-rw-r--r--spec/mspec/lib/mspec/matchers/complain.rb2
-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.rb78
-rw-r--r--spec/mspec/lib/mspec/runner/formatters/base.rb8
-rw-r--r--spec/mspec/lib/mspec/runner/mspec.rb5
-rw-r--r--spec/mspec/lib/mspec/utils/name_map.rb7
-rw-r--r--spec/mspec/lib/mspec/utils/options.rb2
-rw-r--r--spec/mspec/lib/mspec/utils/script.rb1
-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/ruby_exe_spec.rb10
-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.rb51
-rwxr-xr-xspec/mspec/tool/tag_from_output.rb30
28 files changed, 343 insertions, 183 deletions
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 9c38cebcda..f5341c699d 100755
--- a/spec/mspec/lib/mspec/commands/mspec.rb
+++ b/spec/mspec/lib/mspec/commands/mspec.rb
@@ -38,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
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/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/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/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/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/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 543b7366d7..1200926872 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,26 @@ 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|
+ 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
@@ -65,6 +78,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
@@ -72,20 +90,54 @@ class TimeoutAction
@thread.join
end
- private def show_backtraces
- if RUBY_ENGINE == 'truffleruby'
- STDERR.puts 'Java stacktraces:'
- Process.kill :SIGQUIT, Process.pid
+ 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
- STDERR.puts "\nRuby backtraces:"
- if defined?(Truffle::Debug.show_backtraces)
- Truffle::Debug.show_backtraces
+ 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
- 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:"
+ 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
diff --git a/spec/mspec/lib/mspec/runner/formatters/base.rb b/spec/mspec/lib/mspec/runner/formatters/base.rb
index 54a83c9c32..e3b5bb23e0 100644
--- a/spec/mspec/lib/mspec/runner/formatters/base.rb
+++ b/spec/mspec/lib/mspec/runner/formatters/base.rb
@@ -5,6 +5,9 @@ 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
@@ -40,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
diff --git a/spec/mspec/lib/mspec/runner/mspec.rb b/spec/mspec/lib/mspec/runner/mspec.rb
index 889e085175..97c3f365bc 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
@@ -395,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/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 612caf6771..3b5962dbe6 100644
--- a/spec/mspec/lib/mspec/utils/options.rb
+++ b/spec/mspec/lib/mspec/utils/options.rb
@@ -477,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 b9f8b17fdc..e86beaab86 100644
--- a/spec/mspec/lib/mspec/utils/script.rb
+++ b/spec/mspec/lib/mspec/utils/script.rb
@@ -283,7 +283,6 @@ class MSpecScript
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/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/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 67485446bb..3fd95e6b31 100644
--- a/spec/mspec/tool/remove_old_guards.rb
+++ b/spec/mspec/tool/remove_old_guards.rb
@@ -46,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)
@@ -64,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/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