summaryrefslogtreecommitdiff
path: root/spec/mspec
diff options
context:
space:
mode:
Diffstat (limited to 'spec/mspec')
-rw-r--r--spec/mspec/Gemfile2
-rw-r--r--spec/mspec/Gemfile.lock4
-rw-r--r--[-rwxr-xr-x]spec/mspec/lib/mspec/commands/mkspec.rb18
-rw-r--r--spec/mspec/lib/mspec/commands/mspec-ci.rb5
-rw-r--r--spec/mspec/lib/mspec/commands/mspec-run.rb8
-rw-r--r--spec/mspec/lib/mspec/commands/mspec-tag.rb3
-rw-r--r--[-rwxr-xr-x]spec/mspec/lib/mspec/commands/mspec.rb7
-rw-r--r--spec/mspec/lib/mspec/guards/platform.rb24
-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/io.rb4
-rw-r--r--spec/mspec/lib/mspec/helpers/numeric.rb26
-rw-r--r--spec/mspec/lib/mspec/helpers/ruby_exe.rb45
-rw-r--r--spec/mspec/lib/mspec/helpers/tmp.rb18
-rw-r--r--spec/mspec/lib/mspec/matchers/base.rb18
-rw-r--r--spec/mspec/lib/mspec/matchers/be_an_instance_of.rb1
-rw-r--r--spec/mspec/lib/mspec/matchers/be_ancestor_of.rb1
-rw-r--r--spec/mspec/lib/mspec/matchers/be_empty.rb1
-rw-r--r--spec/mspec/lib/mspec/matchers/be_false.rb1
-rw-r--r--spec/mspec/lib/mspec/matchers/be_kind_of.rb1
-rw-r--r--spec/mspec/lib/mspec/matchers/be_nan.rb1
-rw-r--r--spec/mspec/lib/mspec/matchers/be_nil.rb1
-rw-r--r--spec/mspec/lib/mspec/matchers/be_true.rb1
-rw-r--r--spec/mspec/lib/mspec/matchers/complain.rb2
-rw-r--r--spec/mspec/lib/mspec/matchers/eql.rb1
-rw-r--r--spec/mspec/lib/mspec/matchers/equal.rb1
-rw-r--r--spec/mspec/lib/mspec/matchers/have_class_variable.rb1
-rw-r--r--spec/mspec/lib/mspec/matchers/have_constant.rb1
-rw-r--r--spec/mspec/lib/mspec/matchers/have_instance_method.rb1
-rw-r--r--spec/mspec/lib/mspec/matchers/have_instance_variable.rb1
-rw-r--r--spec/mspec/lib/mspec/matchers/have_method.rb1
-rw-r--r--spec/mspec/lib/mspec/matchers/have_private_instance_method.rb1
-rw-r--r--spec/mspec/lib/mspec/matchers/have_private_method.rb1
-rw-r--r--spec/mspec/lib/mspec/matchers/have_protected_instance_method.rb1
-rw-r--r--spec/mspec/lib/mspec/matchers/have_public_instance_method.rb1
-rw-r--r--spec/mspec/lib/mspec/matchers/have_singleton_method.rb1
-rw-r--r--spec/mspec/lib/mspec/matchers/include.rb1
-rw-r--r--spec/mspec/lib/mspec/matchers/infinity.rb2
-rw-r--r--spec/mspec/lib/mspec/matchers/raise_error.rb64
-rw-r--r--spec/mspec/lib/mspec/matchers/respond_to.rb1
-rw-r--r--spec/mspec/lib/mspec/mocks/mock.rb25
-rw-r--r--spec/mspec/lib/mspec/runner/actions/leakchecker.rb67
-rw-r--r--spec/mspec/lib/mspec/runner/actions/timeout.rb78
-rw-r--r--spec/mspec/lib/mspec/runner/context.rb1
-rw-r--r--spec/mspec/lib/mspec/runner/formatters/base.rb16
-rw-r--r--spec/mspec/lib/mspec/runner/formatters/launchable.rb88
-rw-r--r--spec/mspec/lib/mspec/runner/formatters/multi.rb2
-rw-r--r--spec/mspec/lib/mspec/runner/mspec.rb10
-rw-r--r--spec/mspec/lib/mspec/runner/shared.rb8
-rw-r--r--spec/mspec/lib/mspec/utils/name_map.rb20
-rw-r--r--spec/mspec/lib/mspec/utils/options.rb23
-rw-r--r--spec/mspec/lib/mspec/utils/script.rb9
-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_ci_spec.rb5
-rw-r--r--spec/mspec/spec/commands/mspec_run_spec.rb5
-rw-r--r--spec/mspec/spec/commands/mspec_spec.rb27
-rw-r--r--spec/mspec/spec/fixtures/should.rb4
-rw-r--r--spec/mspec/spec/guards/platform_spec.rb38
-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/matchers/raise_error_spec.rb77
-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.rb5
-rw-r--r--spec/mspec/spec/utils/fixtures/this_file_raises.rb1
-rw-r--r--spec/mspec/spec/utils/fixtures/this_file_raises2.rb1
-rw-r--r--spec/mspec/spec/utils/name_map_spec.rb12
-rw-r--r--spec/mspec/spec/utils/script_spec.rb5
-rwxr-xr-xspec/mspec/tool/check_require_spec_helper.rb34
-rwxr-xr-x[-rw-r--r--]spec/mspec/tool/remove_old_guards.rb79
-rw-r--r--spec/mspec/tool/sync/sync-rubyspec.rb50
-rwxr-xr-xspec/mspec/tool/tag_from_output.rb32
74 files changed, 848 insertions, 292 deletions
diff --git a/spec/mspec/Gemfile b/spec/mspec/Gemfile
index 617a995cad..e57acd6435 100644
--- a/spec/mspec/Gemfile
+++ b/spec/mspec/Gemfile
@@ -1,4 +1,4 @@
source 'https://rubygems.org'
-gem "rake", "~> 12.3"
+gem "rake"
gem "rspec", "~> 3.0"
diff --git a/spec/mspec/Gemfile.lock b/spec/mspec/Gemfile.lock
index cd39906044..075337d671 100644
--- a/spec/mspec/Gemfile.lock
+++ b/spec/mspec/Gemfile.lock
@@ -2,7 +2,7 @@ GEM
remote: https://rubygems.org/
specs:
diff-lcs (1.4.4)
- rake (12.3.3)
+ rake (13.4.2)
rspec (3.10.0)
rspec-core (~> 3.10.0)
rspec-expectations (~> 3.10.0)
@@ -22,5 +22,5 @@ PLATFORMS
ruby
DEPENDENCIES
- rake (~> 12.3)
+ rake
rspec (~> 3.0)
diff --git a/spec/mspec/lib/mspec/commands/mkspec.rb b/spec/mspec/lib/mspec/commands/mkspec.rb
index d10cc35d18..f75e683b19 100755..100644
--- a/spec/mspec/lib/mspec/commands/mkspec.rb
+++ b/spec/mspec/lib/mspec/commands/mkspec.rb
@@ -1,5 +1,3 @@
-#!/usr/bin/env ruby
-
require 'rbconfig'
require 'mspec/version'
require 'mspec/utils/options'
@@ -95,7 +93,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 +133,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-ci.rb b/spec/mspec/lib/mspec/commands/mspec-ci.rb
index a31db1d7dc..8951572f69 100644
--- a/spec/mspec/lib/mspec/commands/mspec-ci.rb
+++ b/spec/mspec/lib/mspec/commands/mspec-ci.rb
@@ -1,7 +1,3 @@
-#!/usr/bin/env ruby
-
-$:.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
-
require 'mspec/version'
require 'mspec/utils/options'
require 'mspec/utils/script'
@@ -22,6 +18,7 @@ class MSpecCI < MSpecScript
options.chdir
options.prefix
options.configure { |f| load f }
+ options.repeat
options.pretend
options.interrupt
options.timeout
diff --git a/spec/mspec/lib/mspec/commands/mspec-run.rb b/spec/mspec/lib/mspec/commands/mspec-run.rb
index 4d8f4d9984..0fb338fa23 100644
--- a/spec/mspec/lib/mspec/commands/mspec-run.rb
+++ b/spec/mspec/lib/mspec/commands/mspec-run.rb
@@ -1,7 +1,3 @@
-#!/usr/bin/env ruby
-
-$:.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
-
require 'mspec/version'
require 'mspec/utils/options'
require 'mspec/utils/script'
@@ -32,6 +28,7 @@ class MSpecRun < MSpecScript
options.chdir
options.prefix
options.configure { |f| load f }
+ options.env
options.randomize
options.repeat
options.pretend
@@ -52,6 +49,9 @@ class MSpecRun < MSpecScript
options.doc "\n When to perform it"
options.action_filters
+ options.doc "\n Launchable"
+ options.launchable
+
options.doc "\n Help!"
options.debug
options.version MSpec::VERSION
diff --git a/spec/mspec/lib/mspec/commands/mspec-tag.rb b/spec/mspec/lib/mspec/commands/mspec-tag.rb
index e1d04d1446..9ce9f048c6 100644
--- a/spec/mspec/lib/mspec/commands/mspec-tag.rb
+++ b/spec/mspec/lib/mspec/commands/mspec-tag.rb
@@ -1,5 +1,3 @@
-#!/usr/bin/env ruby
-
require 'mspec/version'
require 'mspec/utils/options'
require 'mspec/utils/script'
@@ -114,6 +112,7 @@ class MSpecTag < MSpecScript
MSpec.register_mode :pretend
MSpec.register_mode :unguarded
config[:formatter] = false
+ config[:xtags] = []
else
raise ArgumentError, "No recognized action given"
end
diff --git a/spec/mspec/lib/mspec/commands/mspec.rb b/spec/mspec/lib/mspec/commands/mspec.rb
index 9c38cebcda..a9d94ca354 100755..100644
--- a/spec/mspec/lib/mspec/commands/mspec.rb
+++ b/spec/mspec/lib/mspec/commands/mspec.rb
@@ -1,5 +1,3 @@
-#!/usr/bin/env ruby
-
require 'mspec/version'
require 'mspec/utils/options'
require 'mspec/utils/script'
@@ -38,11 +36,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/platform.rb b/spec/mspec/lib/mspec/guards/platform.rb
index e87b08a4c1..fadd8d75ef 100644
--- a/spec/mspec/lib/mspec/guards/platform.rb
+++ b/spec/mspec/lib/mspec/guards/platform.rb
@@ -53,16 +53,27 @@ class PlatformGuard < SpecGuard
end
end
+ # In bits
WORD_SIZE = 1.size * 8
+ deprecate_constant :WORD_SIZE
+ # In bits
POINTER_SIZE = begin
require 'rbconfig/sizeof'
RbConfig::SIZEOF["void*"] * 8
rescue LoadError
- WORD_SIZE
+ [0].pack('j').size * 8
+ end
+
+ # In bits
+ C_LONG_SIZE = if defined?(RbConfig::SIZEOF[])
+ RbConfig::SIZEOF["long"] * 8
+ else
+ [0].pack('l!').size * 8
end
def self.wordsize?(size)
+ warn "#wordsize? is deprecated, use #c_long_size?"
size == WORD_SIZE
end
@@ -70,6 +81,10 @@ class PlatformGuard < SpecGuard
size == POINTER_SIZE
end
+ def self.c_long_size?(size)
+ size == C_LONG_SIZE
+ end
+
def initialize(*args)
if args.last.is_a?(Hash)
@options, @platforms = args.last, args[0..-2]
@@ -85,10 +100,13 @@ class PlatformGuard < SpecGuard
case key
when :os
match &&= PlatformGuard.os?(*value)
- when :wordsize
- match &&= PlatformGuard.wordsize? value
when :pointer_size
match &&= PlatformGuard.pointer_size? value
+ when :wordsize
+ warn ":wordsize is deprecated, use :c_long_size"
+ match &&= PlatformGuard.wordsize? value
+ when :c_long_size
+ match &&= PlatformGuard::c_long_size? value
end
end
match
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/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 c1ed81a233..0b47855cd2 100644
--- a/spec/mspec/lib/mspec/helpers/numeric.rb
+++ b/spec/mspec/lib/mspec/helpers/numeric.rb
@@ -1,3 +1,4 @@
+# frozen_string_literal: true
require 'mspec/guards/platform'
def nan_value
@@ -15,11 +16,11 @@ def bignum_value(plus = 0)
end
def max_long
- 2**(0.size * 8 - 1) - 1
+ 2**(PlatformGuard::C_LONG_SIZE - 1) - 1
end
def min_long
- -(2**(0.size * 8 - 1))
+ -(2**(PlatformGuard::C_LONG_SIZE - 1))
end
# This is a bit hairy, but we need to be able to write specs that cover the
@@ -28,7 +29,24 @@ end
# specs based on the relationship between values rather than specific
# values.
if PlatformGuard.standard? or PlatformGuard.implementation? :topaz
- if PlatformGuard.wordsize? 32
+ limits_available = begin
+ require 'rbconfig/sizeof'
+ defined?(RbConfig::LIMITS.[]) && ['FIXNUM_MAX', 'FIXNUM_MIN'].all? do |key|
+ Integer === RbConfig::LIMITS[key]
+ end
+ rescue LoadError
+ false
+ end
+
+ if limits_available
+ def fixnum_max
+ RbConfig::LIMITS['FIXNUM_MAX']
+ end
+
+ def fixnum_min
+ RbConfig::LIMITS['FIXNUM_MIN']
+ end
+ elsif PlatformGuard.c_long_size? 32
def fixnum_max
(2**30) - 1
end
@@ -36,7 +54,7 @@ if PlatformGuard.standard? or PlatformGuard.implementation? :topaz
def fixnum_min
-(2**30)
end
- elsif PlatformGuard.wordsize? 64
+ elsif PlatformGuard.c_long_size? 64
def fixnum_max
(2**62) - 1
end
diff --git a/spec/mspec/lib/mspec/helpers/ruby_exe.rb b/spec/mspec/lib/mspec/helpers/ruby_exe.rb
index 922178dab0..2e499d6f9a 100644
--- a/spec/mspec/lib/mspec/helpers/ruby_exe.rb
+++ b/spec/mspec/lib/mspec/helpers/ruby_exe.rb
@@ -140,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..e903dd9f50 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
@@ -36,11 +36,25 @@ all specs are cleaning up temporary files:
end
def tmp(name, uniquify = true)
- mkdir_p SPEC_TEMP_DIR unless Dir.exist? SPEC_TEMP_DIR
+ if Dir.exist? SPEC_TEMP_DIR
+ stat = File.stat(SPEC_TEMP_DIR)
+ if stat.world_writable? and !stat.sticky?
+ raise ArgumentError, "SPEC_TEMP_DIR (#{SPEC_TEMP_DIR}) is world writable but not sticky"
+ end
+ else
+ platform_is_not :windows do
+ umask = File.umask
+ if (umask & 0002) == 0 # o+w
+ raise ArgumentError, "File.umask #=> #{umask.to_s(8)} (world-writable)"
+ end
+ end
+ mkdir_p SPEC_TEMP_DIR
+ end
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 d9d7f6fec0..4056e73a81 100644
--- a/spec/mspec/lib/mspec/matchers/base.rb
+++ b/spec/mspec/lib/mspec/matchers/base.rb
@@ -1,3 +1,5 @@
+require 'mspec/utils/deprecate'
+
module MSpecMatchers
end
@@ -36,6 +38,14 @@ class SpecPositiveOperatorMatcher < BasicObject
end
end
+ def raise(exception = ::Exception, message = nil, options = nil, &block)
+ matcher = ::RaiseErrorMatcher.new(exception, message, options, &block)
+ unless matcher.matches? @actual
+ expected, actual = matcher.failure_message
+ ::SpecExpectation.fail_with(expected, actual)
+ end
+ end
+
def method_missing(name, *args, &block)
result = @actual.__send__(name, *args, &block)
unless result
@@ -70,6 +80,14 @@ class SpecNegativeOperatorMatcher < BasicObject
end
end
+ def raise(exception = ::Exception, message = nil, options = nil, &block)
+ matcher = ::RaiseErrorMatcher.new(exception, message, options, &block)
+ if matcher.matches? @actual
+ expected, actual = matcher.negative_failure_message
+ ::SpecExpectation.fail_with(expected, actual)
+ end
+ end
+
def method_missing(name, *args, &block)
result = @actual.__send__(name, *args, &block)
if result
diff --git a/spec/mspec/lib/mspec/matchers/be_an_instance_of.rb b/spec/mspec/lib/mspec/matchers/be_an_instance_of.rb
index fdf3736ac2..230cea2e9b 100644
--- a/spec/mspec/lib/mspec/matchers/be_an_instance_of.rb
+++ b/spec/mspec/lib/mspec/matchers/be_an_instance_of.rb
@@ -21,6 +21,7 @@ end
module MSpecMatchers
private def be_an_instance_of(expected)
+ MSpec.deprecate __method__, '.should.instance_of?'
BeAnInstanceOfMatcher.new(expected)
end
end
diff --git a/spec/mspec/lib/mspec/matchers/be_ancestor_of.rb b/spec/mspec/lib/mspec/matchers/be_ancestor_of.rb
index 05f72099e4..b428a153bf 100644
--- a/spec/mspec/lib/mspec/matchers/be_ancestor_of.rb
+++ b/spec/mspec/lib/mspec/matchers/be_ancestor_of.rb
@@ -19,6 +19,7 @@ end
module MSpecMatchers
private def be_ancestor_of(expected)
+ MSpec.deprecate __method__, '.ancestors.should.include?'
BeAncestorOfMatcher.new(expected)
end
end
diff --git a/spec/mspec/lib/mspec/matchers/be_empty.rb b/spec/mspec/lib/mspec/matchers/be_empty.rb
index 5abd5c9485..aac175ffb4 100644
--- a/spec/mspec/lib/mspec/matchers/be_empty.rb
+++ b/spec/mspec/lib/mspec/matchers/be_empty.rb
@@ -15,6 +15,7 @@ end
module MSpecMatchers
private def be_empty
+ MSpec.deprecate __method__, '.should.empty?'
BeEmptyMatcher.new
end
end
diff --git a/spec/mspec/lib/mspec/matchers/be_false.rb b/spec/mspec/lib/mspec/matchers/be_false.rb
index 9e9a2608e1..4fa0bba8a3 100644
--- a/spec/mspec/lib/mspec/matchers/be_false.rb
+++ b/spec/mspec/lib/mspec/matchers/be_false.rb
@@ -15,6 +15,7 @@ end
module MSpecMatchers
private def be_false
+ MSpec.deprecate __method__, '.should == false'
BeFalseMatcher.new
end
end
diff --git a/spec/mspec/lib/mspec/matchers/be_kind_of.rb b/spec/mspec/lib/mspec/matchers/be_kind_of.rb
index a69906f210..d0b23086aa 100644
--- a/spec/mspec/lib/mspec/matchers/be_kind_of.rb
+++ b/spec/mspec/lib/mspec/matchers/be_kind_of.rb
@@ -19,6 +19,7 @@ end
module MSpecMatchers
private def be_kind_of(expected)
+ MSpec.deprecate __method__, '.should.is_a?'
BeKindOfMatcher.new(expected)
end
end
diff --git a/spec/mspec/lib/mspec/matchers/be_nan.rb b/spec/mspec/lib/mspec/matchers/be_nan.rb
index b279d8f1cf..8ba2a3cafe 100644
--- a/spec/mspec/lib/mspec/matchers/be_nan.rb
+++ b/spec/mspec/lib/mspec/matchers/be_nan.rb
@@ -15,6 +15,7 @@ end
module MSpecMatchers
private def be_nan
+ MSpec.deprecate __method__, '.should.nan?'
BeNaNMatcher.new
end
end
diff --git a/spec/mspec/lib/mspec/matchers/be_nil.rb b/spec/mspec/lib/mspec/matchers/be_nil.rb
index 049b1e3a53..c79a50a3f9 100644
--- a/spec/mspec/lib/mspec/matchers/be_nil.rb
+++ b/spec/mspec/lib/mspec/matchers/be_nil.rb
@@ -15,6 +15,7 @@ end
module MSpecMatchers
private def be_nil
+ MSpec.deprecate __method__, '.should == nil'
BeNilMatcher.new
end
end
diff --git a/spec/mspec/lib/mspec/matchers/be_true.rb b/spec/mspec/lib/mspec/matchers/be_true.rb
index 52f5013752..91020cc3fe 100644
--- a/spec/mspec/lib/mspec/matchers/be_true.rb
+++ b/spec/mspec/lib/mspec/matchers/be_true.rb
@@ -15,6 +15,7 @@ end
module MSpecMatchers
private def be_true
+ MSpec.deprecate __method__, '.should == true'
BeTrueMatcher.new
end
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/matchers/eql.rb b/spec/mspec/lib/mspec/matchers/eql.rb
index bcab88ebee..3b132f9ae3 100644
--- a/spec/mspec/lib/mspec/matchers/eql.rb
+++ b/spec/mspec/lib/mspec/matchers/eql.rb
@@ -21,6 +21,7 @@ end
module MSpecMatchers
private def eql(expected)
+ MSpec.deprecate __method__, '.should.eql?'
EqlMatcher.new(expected)
end
end
diff --git a/spec/mspec/lib/mspec/matchers/equal.rb b/spec/mspec/lib/mspec/matchers/equal.rb
index 5ba4856d82..1ca5172e0b 100644
--- a/spec/mspec/lib/mspec/matchers/equal.rb
+++ b/spec/mspec/lib/mspec/matchers/equal.rb
@@ -21,6 +21,7 @@ end
module MSpecMatchers
private def equal(expected)
+ MSpec.deprecate __method__, '.should.equal?'
EqualMatcher.new(expected)
end
end
diff --git a/spec/mspec/lib/mspec/matchers/have_class_variable.rb b/spec/mspec/lib/mspec/matchers/have_class_variable.rb
index dd43ced621..576f43793b 100644
--- a/spec/mspec/lib/mspec/matchers/have_class_variable.rb
+++ b/spec/mspec/lib/mspec/matchers/have_class_variable.rb
@@ -7,6 +7,7 @@ end
module MSpecMatchers
private def have_class_variable(variable)
+ MSpec.deprecate __method__, '.should.class_variable_defined?'
HaveClassVariableMatcher.new(variable)
end
end
diff --git a/spec/mspec/lib/mspec/matchers/have_constant.rb b/spec/mspec/lib/mspec/matchers/have_constant.rb
index 6ec7c75b85..c796d742e0 100644
--- a/spec/mspec/lib/mspec/matchers/have_constant.rb
+++ b/spec/mspec/lib/mspec/matchers/have_constant.rb
@@ -7,6 +7,7 @@ end
module MSpecMatchers
private def have_constant(variable)
+ MSpec.deprecate __method__, '.should.const_defined?'
HaveConstantMatcher.new(variable)
end
end
diff --git a/spec/mspec/lib/mspec/matchers/have_instance_method.rb b/spec/mspec/lib/mspec/matchers/have_instance_method.rb
index 9a5a31aa0f..76dde482d5 100644
--- a/spec/mspec/lib/mspec/matchers/have_instance_method.rb
+++ b/spec/mspec/lib/mspec/matchers/have_instance_method.rb
@@ -19,6 +19,7 @@ end
module MSpecMatchers
private def have_instance_method(method, include_super = true)
+ MSpec.deprecate __method__, '.should.method_defined?'
HaveInstanceMethodMatcher.new method, include_super
end
end
diff --git a/spec/mspec/lib/mspec/matchers/have_instance_variable.rb b/spec/mspec/lib/mspec/matchers/have_instance_variable.rb
index de51b3209d..f49595ff7e 100644
--- a/spec/mspec/lib/mspec/matchers/have_instance_variable.rb
+++ b/spec/mspec/lib/mspec/matchers/have_instance_variable.rb
@@ -7,6 +7,7 @@ end
module MSpecMatchers
private def have_instance_variable(variable)
+ MSpec.deprecate __method__, '.should.instance_variable_defined?'
HaveInstanceVariableMatcher.new(variable)
end
end
diff --git a/spec/mspec/lib/mspec/matchers/have_method.rb b/spec/mspec/lib/mspec/matchers/have_method.rb
index e962e69e0a..3db01a7235 100644
--- a/spec/mspec/lib/mspec/matchers/have_method.rb
+++ b/spec/mspec/lib/mspec/matchers/have_method.rb
@@ -19,6 +19,7 @@ end
module MSpecMatchers
private def have_method(method, include_super = true)
+ MSpec.deprecate __method__, '.should.respond_to?'
HaveMethodMatcher.new method, include_super
end
end
diff --git a/spec/mspec/lib/mspec/matchers/have_private_instance_method.rb b/spec/mspec/lib/mspec/matchers/have_private_instance_method.rb
index d32db76c6a..bae01e846e 100644
--- a/spec/mspec/lib/mspec/matchers/have_private_instance_method.rb
+++ b/spec/mspec/lib/mspec/matchers/have_private_instance_method.rb
@@ -19,6 +19,7 @@ end
module MSpecMatchers
private def have_private_instance_method(method, include_super = true)
+ MSpec.deprecate __method__, '.private_instance_methods(false).should.include?'
HavePrivateInstanceMethodMatcher.new method, include_super
end
end
diff --git a/spec/mspec/lib/mspec/matchers/have_private_method.rb b/spec/mspec/lib/mspec/matchers/have_private_method.rb
index c74165cfc7..32efd5a155 100644
--- a/spec/mspec/lib/mspec/matchers/have_private_method.rb
+++ b/spec/mspec/lib/mspec/matchers/have_private_method.rb
@@ -19,6 +19,7 @@ end
module MSpecMatchers
private def have_private_method(method, include_super = true)
+ MSpec.deprecate __method__, '.private_methods(false).should.include?'
HavePrivateMethodMatcher.new method, include_super
end
end
diff --git a/spec/mspec/lib/mspec/matchers/have_protected_instance_method.rb b/spec/mspec/lib/mspec/matchers/have_protected_instance_method.rb
index 1deb2f995d..9f09e8b529 100644
--- a/spec/mspec/lib/mspec/matchers/have_protected_instance_method.rb
+++ b/spec/mspec/lib/mspec/matchers/have_protected_instance_method.rb
@@ -19,6 +19,7 @@ end
module MSpecMatchers
private def have_protected_instance_method(method, include_super = true)
+ MSpec.deprecate __method__, '.protected_instance_methods(false).should.include?'
HaveProtectedInstanceMethodMatcher.new method, include_super
end
end
diff --git a/spec/mspec/lib/mspec/matchers/have_public_instance_method.rb b/spec/mspec/lib/mspec/matchers/have_public_instance_method.rb
index 0e620532c0..69abadfafa 100644
--- a/spec/mspec/lib/mspec/matchers/have_public_instance_method.rb
+++ b/spec/mspec/lib/mspec/matchers/have_public_instance_method.rb
@@ -19,6 +19,7 @@ end
module MSpecMatchers
private def have_public_instance_method(method, include_super = true)
+ MSpec.deprecate __method__, '.public_instance_methods(false).should.include?'
HavePublicInstanceMethodMatcher.new method, include_super
end
end
diff --git a/spec/mspec/lib/mspec/matchers/have_singleton_method.rb b/spec/mspec/lib/mspec/matchers/have_singleton_method.rb
index b60dd2536b..2d2707c528 100644
--- a/spec/mspec/lib/mspec/matchers/have_singleton_method.rb
+++ b/spec/mspec/lib/mspec/matchers/have_singleton_method.rb
@@ -19,6 +19,7 @@ end
module MSpecMatchers
private def have_singleton_method(method, include_super = true)
+ MSpec.deprecate __method__, '.singleton_methods(false).should.include?'
HaveSingletonMethodMatcher.new method, include_super
end
end
diff --git a/spec/mspec/lib/mspec/matchers/include.rb b/spec/mspec/lib/mspec/matchers/include.rb
index 3f07f35548..05d5079f13 100644
--- a/spec/mspec/lib/mspec/matchers/include.rb
+++ b/spec/mspec/lib/mspec/matchers/include.rb
@@ -26,6 +26,7 @@ end
# Cannot override #include at the toplevel in MRI
module MSpecMatchers
private def include(*expected)
+ MSpec.deprecate __method__, '.should.include?'
IncludeMatcher.new(*expected)
end
end
diff --git a/spec/mspec/lib/mspec/matchers/infinity.rb b/spec/mspec/lib/mspec/matchers/infinity.rb
index 8bfa6dbd10..0a4c95c7cc 100644
--- a/spec/mspec/lib/mspec/matchers/infinity.rb
+++ b/spec/mspec/lib/mspec/matchers/infinity.rb
@@ -19,10 +19,12 @@ end
module MSpecMatchers
private def be_positive_infinity
+ MSpec.deprecate __method__, '.should.infinite? == 1'
InfinityMatcher.new(1)
end
private def be_negative_infinity
+ MSpec.deprecate __method__, '.should.infinite? == -1'
InfinityMatcher.new(-1)
end
end
diff --git a/spec/mspec/lib/mspec/matchers/raise_error.rb b/spec/mspec/lib/mspec/matchers/raise_error.rb
index 54378bb34c..17ea47148b 100644
--- a/spec/mspec/lib/mspec/matchers/raise_error.rb
+++ b/spec/mspec/lib/mspec/matchers/raise_error.rb
@@ -1,11 +1,18 @@
class RaiseErrorMatcher
FAILURE_MESSAGE_FOR_EXCEPTION = {}.compare_by_identity
+ UNDEF_CAUSE = Object.new
attr_writer :block
- def initialize(exception, message, &block)
+ def initialize(exception, message = nil, options = nil, &block)
+ if message.is_a? Hash
+ @message = nil
+ options = message
+ else
+ @message = message
+ end
+ @cause = options ? options.fetch(:cause, UNDEF_CAUSE) : UNDEF_CAUSE
@exception = exception
- @message = message
@block = block
@actual = nil
end
@@ -45,24 +52,45 @@ class RaiseErrorMatcher
end
end
+ def matching_cause?(exc)
+ case @cause
+ when UNDEF_CAUSE
+ true
+ else
+ @cause == exc.cause
+ end
+ end
+
def matching_exception?(exc)
- matching_class?(exc) and matching_message?(exc)
+ matching_class?(exc) and matching_message?(exc) and matching_cause?(exc)
end
- def exception_class_and_message(exception_class, message)
- if message
- "#{exception_class} (#{message})"
- else
- "#{exception_class}"
+ def exception_class_and_message_and_cause(exception_class, message, cause)
+ string = "#{exception_class}"
+ prefixed = false
+ prefix = -> { prefixed ? ", " : prefixed = "(" }
+
+ if message != nil
+ string << "#{prefix.()}#{message.inspect}"
+ end
+
+ if cause != UNDEF_CAUSE
+ string << "#{prefix.()}cause: #{cause.inspect}"
end
+
+ string << ")" if prefixed
+
+ string
end
def format_expected_exception
- exception_class_and_message(@exception, @message)
+ exception_class_and_message_and_cause(@exception, @message, @cause)
end
def format_exception(exception)
- exception_class_and_message(exception.class, exception.message)
+ exception_class_and_message_and_cause(exception.class,
+ @message == nil ? nil : exception.message,
+ @cause == UNDEF_CAUSE ? UNDEF_CAUSE : exception.cause)
end
def failure_message
@@ -87,7 +115,19 @@ class RaiseErrorMatcher
end
module MSpecMatchers
- private def raise_error(exception = Exception, message = nil, &block)
- RaiseErrorMatcher.new(exception, message, &block)
+ private def raise_error(exception = Exception, message = nil, options = nil, &block)
+ MSpec.deprecate __method__, '.should.raise'
+ RaiseErrorMatcher.new(exception, message, options, &block)
+ end
+
+ # CRuby < 4.1 has inconsistent coercion errors:
+ # https://bugs.ruby-lang.org/issues/21864
+ # This matcher ignores the message on CRuby < 4.1
+ # and checks the message for all other cases, including other Rubies
+ private def raise_consistent_error(exception = Exception, message = nil, options = nil, &block)
+ if RUBY_ENGINE == "ruby" and ruby_version_is ""..."4.1"
+ message = nil
+ end
+ RaiseErrorMatcher.new(exception, message, options, &block)
end
end
diff --git a/spec/mspec/lib/mspec/matchers/respond_to.rb b/spec/mspec/lib/mspec/matchers/respond_to.rb
index 6b35ae2d3c..a36bf8aee2 100644
--- a/spec/mspec/lib/mspec/matchers/respond_to.rb
+++ b/spec/mspec/lib/mspec/matchers/respond_to.rb
@@ -19,6 +19,7 @@ end
module MSpecMatchers
private def respond_to(expected)
+ MSpec.deprecate __method__, '.should.respond_to?'
RespondToMatcher.new(expected)
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..0a8c9c3252 100644
--- a/spec/mspec/lib/mspec/runner/actions/leakchecker.rb
+++ b/spec/mspec/lib/mspec/runner/actions/leakchecker.rb
@@ -132,14 +132,14 @@ class LeakChecker
attr_accessor :count
end
- def new(data)
+ def new(...)
LeakChecker::TempfileCounter.count += 1
- super(data)
+ super
end
}
LeakChecker.const_set(:TempfileCounter, m)
- class << Tempfile::Remover
+ class << Tempfile
prepend LeakChecker::TempfileCounter
end
end
@@ -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/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/formatters/base.rb b/spec/mspec/lib/mspec/runner/formatters/base.rb
index c7c50c40d8..882e15c8c2 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']) && ENV['CHECK_CONSTANT_LEAKS'] != 'false'
+ save = ENV['CHECK_LEAKS'] == 'save' || ENV['CHECK_CONSTANT_LEAKS'] == 'save'
ConstantsLeakCheckerAction.new(save).register
end
@@ -113,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
diff --git a/spec/mspec/lib/mspec/runner/formatters/launchable.rb b/spec/mspec/lib/mspec/runner/formatters/launchable.rb
new file mode 100644
index 0000000000..f738781c71
--- /dev/null
+++ b/spec/mspec/lib/mspec/runner/formatters/launchable.rb
@@ -0,0 +1,88 @@
+module LaunchableFormatter
+ def self.extend_object(obj)
+ super
+ obj.init
+ end
+
+ def self.setDir(dir)
+ @@path = File.join(dir, "#{rand.to_s}.json")
+ self
+ end
+
+ def init
+ @timer = nil
+ @tests = []
+ end
+
+ def before(state = nil)
+ super
+ @timer = TimerAction.new
+ @timer.start
+ end
+
+ def after(state = nil)
+ super
+ @timer.finish
+ file = MSpec.file
+ return if file.nil? || state&.example.nil? || exception?
+
+ @tests << {:test => state, :file => file, :exception => false, duration: @timer.elapsed}
+ end
+
+ def exception(exception)
+ super
+ @timer.finish
+ file = MSpec.file
+ return if file.nil?
+
+ @tests << {:test => exception, :file => file, :exception => true, duration: @timer.elapsed}
+ end
+
+ def finish
+ super
+
+ require_relative '../../../../../../tool/lib/launchable'
+
+ @writer = writer = Launchable::JsonStreamWriter.new(@@path)
+ @writer.write_array('testCases')
+ at_exit {
+ @writer.close
+ }
+
+ repo_path = File.expand_path("#{__dir__}/../../../../../../")
+
+ @tests.each do |t|
+ testcase = t[:test].description
+ relative_path = t[:file].delete_prefix("#{repo_path}/")
+ # The test path is a URL-encoded representation.
+ # https://github.com/launchableinc/cli/blob/v1.81.0/launchable/testpath.py#L18
+ test_path = {file: relative_path, testcase: testcase}.map{|key, val|
+ "#{encode_test_path_component(key)}=#{encode_test_path_component(val)}"
+ }.join('#')
+
+ status = 'TEST_PASSED'
+ if t[:exception]
+ message = t[:test].message
+ backtrace = t[:test].backtrace
+ e = "#{message}\n#{backtrace}"
+ status = 'TEST_FAILED'
+ end
+
+ @writer.write_object(
+ {
+ testPath: test_path,
+ status: status,
+ duration: t[:duration],
+ createdAt: Time.now.to_s,
+ stderr: e,
+ stdout: nil
+ }
+ )
+ end
+ end
+
+ private
+ def encode_test_path_component component
+ component.to_s.gsub('%', '%25').gsub('=', '%3D').gsub('#', '%23').gsub('&', '%26').tr("\x00-\x08", "")
+ end
+end
diff --git a/spec/mspec/lib/mspec/runner/formatters/multi.rb b/spec/mspec/lib/mspec/runner/formatters/multi.rb
index a723ae8eb9..fa1da3766b 100644
--- a/spec/mspec/lib/mspec/runner/formatters/multi.rb
+++ b/spec/mspec/lib/mspec/runner/formatters/multi.rb
@@ -42,6 +42,6 @@ module MultiFormatter
end
def print_exception(exc, count)
- print "\n#{count})\n#{exc}\n"
+ @err.print "\n#{count})\n#{exc}\n"
end
end
diff --git a/spec/mspec/lib/mspec/runner/mspec.rb b/spec/mspec/lib/mspec/runner/mspec.rb
index 19cf59b7d2..0e016c67a7 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,6 +119,7 @@ module MSpec
rescue SystemExit => e
raise e
rescue SkippedSpecError => e
+ @skips << [e, block]
return false
rescue Object => exc
register_exit 1
@@ -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
@@ -360,6 +365,7 @@ module MSpec
# Writes each tag in +tags+ to the tag file. Overwrites the
# tag file if it exists.
def self.write_tags(tags)
+ return delete_tags if tags.empty?
file = tags_file
make_tag_dir(file)
File.open(file, "w:utf-8") do |f|
@@ -391,7 +397,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..9b04112e2e 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
@@ -62,10 +66,17 @@ class NameMap
end
def class_or_module(c)
- const = Object.const_get(c, false)
+ begin
+ const = Object.const_get(c, false)
+ rescue NameError, RuntimeError
+ # Either the constant doesn't exist or it is
+ # explicitly raising an error, like `SortedSet`.
+ return nil
+ end
+ return nil unless Module === const
+
filtered = @filter && EXCLUDED.include?(const.name)
- return const if Module === const and !filtered
- rescue NameError
+ return const unless filtered
end
def namespace(mod, const)
@@ -86,7 +97,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 23a4c9a2a0..adeafa1f81 100644
--- a/spec/mspec/lib/mspec/utils/options.rb
+++ b/spec/mspec/lib/mspec/utils/options.rb
@@ -204,6 +204,13 @@ class MSpecOptions
"Load FILE containing configuration options", &block)
end
+ def env
+ on("--env", "KEY=VALUE", "Set environment variable") do |env|
+ key, value = env.split('=', 2)
+ ENV[key] = value
+ end
+ end
+
def targets
on("-t", "--target", "TARGET",
"Implementation to run the specs, where TARGET is:") do |t|
@@ -423,6 +430,10 @@ class MSpecOptions
end
MSpec.register :load, obj
end
+
+ on("--print-skips", "Print skips") do
+ config[:print_skips] = true
+ end
end
def interrupt
@@ -473,13 +484,22 @@ 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
+ def launchable
+ on("--launchable-test-reports", "DIR",
+ "DIR The directory for reporting test results in Launchable JSON format") do |o|
+ require 'mspec/runner/formatters/launchable'
+ config[:launchable] = LaunchableFormatter.setDir(o)
+ end
+ end
+
def all
configure {}
+ env
targets
formatters
filters
@@ -496,5 +516,6 @@ class MSpecOptions
action_filters
actions
debug
+ launchable
end
end
diff --git a/spec/mspec/lib/mspec/utils/script.rb b/spec/mspec/lib/mspec/utils/script.rb
index b9f8b17fdc..15fd23fabf 100644
--- a/spec/mspec/lib/mspec/utils/script.rb
+++ b/spec/mspec/lib/mspec/utils/script.rb
@@ -159,8 +159,14 @@ class MSpecScript
end
if config[:formatter]
- config[:formatter].new(config[:output])
+ config[:formatter] = config[:formatter].new(config[:output])
end
+
+ if config[:launchable]
+ config[:formatter].extend config[:launchable]
+ end
+
+ config[:formatter]
end
# Callback for enabling custom actions, etc. This version is a
@@ -283,7 +289,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_ci_spec.rb b/spec/mspec/spec/commands/mspec_ci_spec.rb
index bcbc5b4224..b8dc9d062f 100644
--- a/spec/mspec/spec/commands/mspec_ci_spec.rb
+++ b/spec/mspec/spec/commands/mspec_ci_spec.rb
@@ -78,6 +78,11 @@ RSpec.describe MSpecCI, "#options" do
@script.options []
end
+ it "enables the repeat option" do
+ expect(@options).to receive(:repeat)
+ @script.options @argv
+ end
+
it "calls #custom_options" do
expect(@script).to receive(:custom_options).with(@options)
@script.options []
diff --git a/spec/mspec/spec/commands/mspec_run_spec.rb b/spec/mspec/spec/commands/mspec_run_spec.rb
index 62acd49d7f..f96be2b43e 100644
--- a/spec/mspec/spec/commands/mspec_run_spec.rb
+++ b/spec/mspec/spec/commands/mspec_run_spec.rb
@@ -105,6 +105,11 @@ RSpec.describe MSpecRun, "#options" do
@script.options @argv
end
+ it "enables the repeat option" do
+ expect(@options).to receive(:repeat)
+ @script.options @argv
+ end
+
it "exits if there are no files to process and './spec' is not a directory" do
expect(File).to receive(:directory?).with("./spec").and_return(false)
expect(@options).to receive(:parse).and_return([])
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/fixtures/should.rb b/spec/mspec/spec/fixtures/should.rb
index f494775c5f..6eb156da25 100644
--- a/spec/mspec/spec/fixtures/should.rb
+++ b/spec/mspec/spec/fixtures/should.rb
@@ -40,7 +40,7 @@ MSpec.setup_env
# Specs
describe "MSpec expectation method #should" do
it "accepts a matcher" do
- :sym.should be_kind_of(Symbol)
+ 0.4.should be_close(0.5, 0.2)
end
it "causes a failure to be recorded" do
@@ -59,7 +59,7 @@ end
describe "MSpec expectation method #should_not" do
it "accepts a matcher" do
- "sym".should_not be_kind_of(Symbol)
+ 0.1.should_not be_close(0.5, 0.2)
end
it "causes a failure to be recorded" do
diff --git a/spec/mspec/spec/guards/platform_spec.rb b/spec/mspec/spec/guards/platform_spec.rb
index 88a7ad86f2..bd37432800 100644
--- a/spec/mspec/spec/guards/platform_spec.rb
+++ b/spec/mspec/spec/guards/platform_spec.rb
@@ -81,44 +81,44 @@ RSpec.describe Object, "#platform_is_not" do
end
end
-RSpec.describe Object, "#platform_is :wordsize => SIZE_SPEC" do
+RSpec.describe Object, "#platform_is :c_long_size => SIZE_SPEC" do
before :each do
- @guard = PlatformGuard.new :darwin, :wordsize => 32
+ @guard = PlatformGuard.new :darwin, :c_long_size => 32
allow(PlatformGuard).to receive(:os?).and_return(true)
allow(PlatformGuard).to receive(:new).and_return(@guard)
ScratchPad.clear
end
- it "yields when #wordsize? returns true" do
- allow(PlatformGuard).to receive(:wordsize?).and_return(true)
- platform_is(:wordsize => 32) { ScratchPad.record :yield }
+ it "yields when #c_long_size? returns true" do
+ allow(PlatformGuard).to receive(:c_long_size?).and_return(true)
+ platform_is(:c_long_size => 32) { ScratchPad.record :yield }
expect(ScratchPad.recorded).to eq(:yield)
end
- it "doesn not yield when #wordsize? returns false" do
- allow(PlatformGuard).to receive(:wordsize?).and_return(false)
- platform_is(:wordsize => 32) { ScratchPad.record :yield }
+ it "doesn not yield when #c_long_size? returns false" do
+ allow(PlatformGuard).to receive(:c_long_size?).and_return(false)
+ platform_is(:c_long_size => 32) { ScratchPad.record :yield }
expect(ScratchPad.recorded).not_to eq(:yield)
end
end
-RSpec.describe Object, "#platform_is_not :wordsize => SIZE_SPEC" do
+RSpec.describe Object, "#platform_is_not :c_long_size => SIZE_SPEC" do
before :each do
- @guard = PlatformGuard.new :darwin, :wordsize => 32
+ @guard = PlatformGuard.new :darwin, :c_long_size => 32
allow(PlatformGuard).to receive(:os?).and_return(true)
allow(PlatformGuard).to receive(:new).and_return(@guard)
ScratchPad.clear
end
- it "yields when #wordsize? returns false" do
- allow(PlatformGuard).to receive(:wordsize?).and_return(false)
- platform_is_not(:wordsize => 32) { ScratchPad.record :yield }
+ it "yields when #c_long_size? returns false" do
+ allow(PlatformGuard).to receive(:c_long_size?).and_return(false)
+ platform_is_not(:c_long_size => 32) { ScratchPad.record :yield }
expect(ScratchPad.recorded).to eq(:yield)
end
- it "doesn not yield when #wordsize? returns true" do
- allow(PlatformGuard).to receive(:wordsize?).and_return(true)
- platform_is_not(:wordsize => 32) { ScratchPad.record :yield }
+ it "doesn not yield when #c_long_size? returns true" do
+ allow(PlatformGuard).to receive(:c_long_size?).and_return(true)
+ platform_is_not(:c_long_size => 32) { ScratchPad.record :yield }
expect(ScratchPad.recorded).not_to eq(:yield)
end
end
@@ -184,13 +184,13 @@ RSpec.describe PlatformGuard, ".standard?" do
end
end
-RSpec.describe PlatformGuard, ".wordsize?" do
+RSpec.describe PlatformGuard, ".c_long_size?" do
it "returns true when arg is 32 and 1.size is 4" do
- expect(PlatformGuard.wordsize?(32)).to eq(1.size == 4)
+ expect(PlatformGuard.c_long_size?(32)).to eq(1.size == 4)
end
it "returns true when arg is 64 and 1.size is 8" do
- expect(PlatformGuard.wordsize?(64)).to eq(1.size == 8)
+ expect(PlatformGuard.c_long_size?(64)).to eq(1.size == 8)
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/matchers/raise_error_spec.rb b/spec/mspec/spec/matchers/raise_error_spec.rb
index 8613eee118..957baa087d 100644
--- a/spec/mspec/spec/matchers/raise_error_spec.rb
+++ b/spec/mspec/spec/matchers/raise_error_spec.rb
@@ -19,7 +19,7 @@ RSpec.describe RaiseErrorMatcher do
ensure_mspec_method(-> {}.method(:should))
run = false
- -> { raise ExpectedException }.should PublicMSpecMatchers.raise_error { |error|
+ -> { raise ExpectedException }.should.raise { |error|
expect(error.class).to eq(ExpectedException)
run = true
}
@@ -30,7 +30,7 @@ RSpec.describe RaiseErrorMatcher do
ensure_mspec_method(-> {}.method(:should))
run = false
- -> { raise ExpectedException }.should PublicMSpecMatchers.raise_error do |error|
+ -> { raise ExpectedException }.should.raise do |error|
expect(error.class).to eq(ExpectedException)
run = true
end
@@ -84,10 +84,10 @@ RSpec.describe RaiseErrorMatcher do
matcher.matches?(Proc.new { raise exc })
rescue UnexpectedException => e
expect(matcher.failure_message).to eq(
- ["Expected ExpectedException (message)", "but got: UnexpectedException (message)"]
+ ['Expected ExpectedException("message")', 'but got: UnexpectedException("message")']
)
expect(ExceptionState.new(nil, nil, e).message).to eq(
- "Expected ExpectedException (message)\nbut got: UnexpectedException (message)"
+ "Expected ExpectedException(\"message\")\nbut got: UnexpectedException(\"message\")"
)
else
raise "no exception"
@@ -103,10 +103,10 @@ RSpec.describe RaiseErrorMatcher do
matcher.matches?(Proc.new { raise exc })
rescue ExpectedException => e
expect(matcher.failure_message).to eq(
- ["Expected ExpectedException (expected)", "but got: ExpectedException (unexpected)"]
+ ['Expected ExpectedException("expected")', 'but got: ExpectedException("unexpected")']
)
expect(ExceptionState.new(nil, nil, e).message).to eq(
- "Expected ExpectedException (expected)\nbut got: ExpectedException (unexpected)"
+ "Expected ExpectedException(\"expected\")\nbut got: ExpectedException(\"unexpected\")"
)
else
raise "no exception"
@@ -122,10 +122,10 @@ RSpec.describe RaiseErrorMatcher do
matcher.matches?(Proc.new { raise exc })
rescue UnexpectedException => e
expect(matcher.failure_message).to eq(
- ["Expected ExpectedException (expected)", "but got: UnexpectedException (unexpected)"]
+ ['Expected ExpectedException("expected")', 'but got: UnexpectedException("unexpected")']
)
expect(ExceptionState.new(nil, nil, e).message).to eq(
- "Expected ExpectedException (expected)\nbut got: UnexpectedException (unexpected)"
+ "Expected ExpectedException(\"expected\")\nbut got: UnexpectedException(\"unexpected\")"
)
else
raise "no exception"
@@ -137,7 +137,7 @@ RSpec.describe RaiseErrorMatcher do
matcher = RaiseErrorMatcher.new(ExpectedException, "expected")
matcher.matches?(proc)
expect(matcher.failure_message).to eq(
- ["Expected ExpectedException (expected)", "but no exception was raised (120 was returned)"]
+ ['Expected ExpectedException("expected")', "but no exception was raised (120 was returned)"]
)
end
@@ -146,7 +146,7 @@ RSpec.describe RaiseErrorMatcher do
matcher = RaiseErrorMatcher.new(ExpectedException, "expected")
matcher.matches?(proc)
expect(matcher.failure_message).to eq(
- ["Expected ExpectedException (expected)", "but no exception was raised (nil was returned)"]
+ ['Expected ExpectedException("expected")', "but no exception was raised (nil was returned)"]
)
end
@@ -159,7 +159,7 @@ RSpec.describe RaiseErrorMatcher do
matcher = RaiseErrorMatcher.new(ExpectedException, "expected")
matcher.matches?(proc)
expect(matcher.failure_message).to eq(
- ["Expected ExpectedException (expected)", "but no exception was raised (#<Object>(#pretty_inspect raised #<ArgumentError: bad>) was returned)"]
+ ['Expected ExpectedException("expected")', 'but no exception was raised (#<Object>(#pretty_inspect raised #<ArgumentError: bad>) was returned)']
)
end
@@ -168,7 +168,7 @@ RSpec.describe RaiseErrorMatcher do
matcher = RaiseErrorMatcher.new(ExpectedException, "expected")
matcher.matches?(proc)
expect(matcher.negative_failure_message).to eq(
- ["Expected to not get ExpectedException (expected)", ""]
+ ['Expected to not get ExpectedException("expected")', ""]
)
end
@@ -177,7 +177,58 @@ RSpec.describe RaiseErrorMatcher do
matcher = RaiseErrorMatcher.new(Exception, nil)
matcher.matches?(proc)
expect(matcher.negative_failure_message).to eq(
- ["Expected to not get Exception", "but got: UnexpectedException (unexpected)"]
+ ['Expected to not get Exception', 'but got: UnexpectedException']
)
end
+
+ it "matches cause if given" do
+ cause = RuntimeError.new("foo")
+ proc = -> do
+ raise cause
+ rescue
+ raise "bar"
+ end
+
+ matcher = RaiseErrorMatcher.new(RuntimeError, cause: cause)
+ expect(matcher.matches?(proc)).to eq(true)
+ end
+
+ it "matches message and cause if given" do
+ cause = RuntimeError.new("foo")
+ proc = -> do
+ raise cause
+ rescue
+ raise "bar"
+ end
+
+ matcher = RaiseErrorMatcher.new(RuntimeError, "bar", cause: cause)
+ expect(matcher.matches?(proc)).to eq(true)
+ end
+
+ it "provides useful negative failure message when cause does not match" do
+ cause = RuntimeError.new("bar")
+ proc = -> do
+ raise "foo"
+ end
+
+ matcher = RaiseErrorMatcher.new(RuntimeError, cause: cause)
+
+ begin
+ matcher.matches?(proc)
+ rescue RuntimeError
+ expect(matcher.failure_message).to eq(
+ ['Expected RuntimeError(cause: #<RuntimeError: bar>)', 'but got: RuntimeError(cause: nil)']
+ )
+ end
+
+ matcher = RaiseErrorMatcher.new(RuntimeError, "foo", cause: cause)
+
+ begin
+ matcher.matches?(proc)
+ rescue RuntimeError
+ expect(matcher.failure_message).to eq(
+ ['Expected RuntimeError("foo", cause: #<RuntimeError: bar>)', 'but got: RuntimeError("foo", cause: nil)']
+ )
+ end
+ end
end
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..8ea38b644f 100644
--- a/spec/mspec/spec/spec_helper.rb
+++ b/spec/mspec/spec/spec_helper.rb
@@ -62,7 +62,4 @@ def ensure_mspec_method(method)
expect(file).to start_with(File.expand_path('../../lib/mspec', __FILE__ ))
end
-PublicMSpecMatchers = Class.new {
- include MSpecMatchers
- public :raise_error
-}.new
+BACKTRACE_QUOTE = RUBY_VERSION >= "3.4" ? "'" : "`"
diff --git a/spec/mspec/spec/utils/fixtures/this_file_raises.rb b/spec/mspec/spec/utils/fixtures/this_file_raises.rb
new file mode 100644
index 0000000000..8e37a587bf
--- /dev/null
+++ b/spec/mspec/spec/utils/fixtures/this_file_raises.rb
@@ -0,0 +1 @@
+raise "This is a BAD file"
diff --git a/spec/mspec/spec/utils/fixtures/this_file_raises2.rb b/spec/mspec/spec/utils/fixtures/this_file_raises2.rb
new file mode 100644
index 0000000000..8efc10199a
--- /dev/null
+++ b/spec/mspec/spec/utils/fixtures/this_file_raises2.rb
@@ -0,0 +1 @@
+raise "This is a BAD file 2"
diff --git a/spec/mspec/spec/utils/name_map_spec.rb b/spec/mspec/spec/utils/name_map_spec.rb
index a18a481500..a42dc9ffec 100644
--- a/spec/mspec/spec/utils/name_map_spec.rb
+++ b/spec/mspec/spec/utils/name_map_spec.rb
@@ -21,6 +21,9 @@ module NameMapSpecs
def f; end
end
+ autoload :BadFile, "#{__dir__}/fixtures/this_file_raises.rb"
+ autoload :BadFile2, "#{__dir__}/fixtures/this_file_raises2.rb"
+
def self.n; end
def n; end
end
@@ -84,6 +87,15 @@ RSpec.describe NameMap, "#class_or_module" do
expect(@map.class_or_module("Hell")).to eq(nil)
expect(@map.class_or_module("Bush::Brain")).to eq(nil)
end
+
+ it "returns nil if accessing the constant raises RuntimeError" do
+ expect { NameMapSpecs::BadFile }.to raise_error(RuntimeError)
+ expect(@map.class_or_module("NameMapSpecs::BadFile")).to eq(nil)
+ end
+
+ it "returns nil if accessing the constant raises RuntimeError when not triggering the autoload before" do
+ expect(@map.class_or_module("NameMapSpecs::BadFile2")).to eq(nil)
+ end
end
RSpec.describe NameMap, "#dir_name" do
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..bc5612c78d 100644..100755
--- a/spec/mspec/tool/remove_old_guards.rb
+++ b/spec/mspec/tool/remove_old_guards.rb
@@ -1,6 +1,23 @@
+#!/usr/bin/env ruby
+
# Removes old version guards in ruby/spec.
# Run it from the ruby/spec repository root.
# The argument is the new minimum supported version.
+#
+# cd spec
+# ../mspec/tool/remove_old_guards.rb <ruby-version>
+#
+# where <ruby-version> is a version guard with which should be removed
+#
+# Example:
+# tool/remove_old_guards.rb 3.1
+#
+# As a result guards like
+# ruby_version_is "3.1" do
+# # ...
+# end
+#
+# will be removed.
def dedent(line)
if line.start_with?(" ")
@@ -14,6 +31,12 @@ def each_spec_file(&block)
Dir["*/**/*.rb"].each(&block)
end
+def each_file(&block)
+ Dir["**/*"].each { |path|
+ yield path if File.file?(path)
+ }
+end
+
def remove_guards(guard, keep)
each_spec_file do |file|
contents = File.binread(file)
@@ -46,9 +69,54 @@ def remove_guards(guard, keep)
end
end
-def search(regexp)
+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_file do |file|
+ contents = File.binread(file)
if contents =~ regexp
puts file
contents.each_line do |line|
@@ -60,11 +128,18 @@ def search(regexp)
end
end
+abort "usage: #{$0} <ruby-version>" if ARGV.empty?
+
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}/)
+search(/RUBY_VERSION_IS_#{version.tr('.', '_')}/)
diff --git a/spec/mspec/tool/sync/sync-rubyspec.rb b/spec/mspec/tool/sync/sync-rubyspec.rb
index 13f1d8004d..86c43d0dc8 100644
--- a/spec/mspec/tool/sync/sync-rubyspec.rb
+++ b/spec/mspec/tool/sync/sync-rubyspec.rb
@@ -3,7 +3,7 @@
IMPLS = {
truffleruby: {
- git: "https://github.com/oracle/truffleruby.git",
+ git: "https://github.com/truffleruby/truffleruby.git",
from_commit: "f10ab6988d",
},
jruby: {
@@ -23,6 +23,8 @@ MSPEC = ARGV.delete('--mspec')
CHECK_LAST_MERGE = !MSPEC && ENV['CHECK_LAST_MERGE'] != 'false'
TEST_MASTER = ENV['TEST_MASTER'] != 'false'
+ONLY_FILTER = ENV['ONLY_FILTER'] == 'true'
+
MSPEC_REPO = File.expand_path("../../..", __FILE__)
raise MSPEC_REPO if !Dir.exist?(MSPEC_REPO) or !Dir.exist?("#{MSPEC_REPO}/.git")
@@ -32,6 +34,13 @@ raise RUBYSPEC_REPO unless Dir.exist?(RUBYSPEC_REPO)
SOURCE_REPO = MSPEC ? MSPEC_REPO : RUBYSPEC_REPO
+# LAST_MERGE is a commit of ruby/spec or ruby/mspec
+# which is the spec/mspec commit that was last imported in the Ruby implementation
+# (i.e. the commit in "Update to ruby/spec@commit").
+# It is normally automatically computed, but can be manually set when
+# e.g. the last update of specs wasn't merged in the Ruby implementation.
+LAST_MERGE = ENV["LAST_MERGE"]
+
NOW = Time.now
BRIGHT_RED = "\e[31;1m"
@@ -140,8 +149,8 @@ def rebase_commits(impl)
else
sh "git", "checkout", impl.name
- if ENV["LAST_MERGE"]
- last_merge = `git log -n 1 --format='%H %ct' #{ENV["LAST_MERGE"]}`
+ if LAST_MERGE
+ last_merge = `git log -n 1 --format='%H %ct' #{LAST_MERGE}`
else
last_merge = `git log --grep='^#{impl.last_merge_message}' -n 1 --format='%H %ct'`
end
@@ -181,30 +190,20 @@ def test_new_specs
Dir.chdir(SOURCE_REPO) do
workflow = YAML.load_file(".github/workflows/ci.yml")
job_name = MSPEC ? "test" : "specs"
- versions = workflow.dig("jobs", job_name, "strategy", "matrix", "ruby")
+ versions = workflow.dig("jobs", job_name, "strategy", "matrix", "ruby").map(&:to_s)
versions = versions.grep(/^\d+\./) # Test on MRI
min_version, max_version = versions.minmax
test_command = MSPEC ? "bundle install && bundle exec rspec" : "../mspec/bin/mspec -j"
run_test = -> version {
- command = "chruby #{version} && #{test_command}"
+ command = "chruby ruby-#{version} && #{test_command}"
sh ENV["SHELL"], "-c", command
}
run_test[min_version]
run_test[max_version]
- run_test["ruby-master"] if TEST_MASTER
- end
-end
-
-def verify_commits(impl)
- puts
- Dir.chdir(SOURCE_REPO) do
- puts "Manually check commit messages:"
- print "Press enter >"
- STDIN.gets
- system "git", "log", "master..."
+ run_test["master"] if TEST_MASTER
end
end
@@ -230,15 +229,16 @@ def main(impls)
impl = RubyImplementation.new(impl, data)
update_repo(impl)
filter_commits(impl)
- rebase_commits(impl)
- if new_commits?(impl)
- test_new_specs
- verify_commits(impl)
- fast_forward_master(impl)
- check_ci
- else
- STDERR.puts "#{BRIGHT_YELLOW}No new commits#{RESET}"
- fast_forward_master(impl)
+ unless ONLY_FILTER
+ rebase_commits(impl)
+ if new_commits?(impl)
+ test_new_specs
+ fast_forward_master(impl)
+ check_ci
+ else
+ STDERR.puts "#{BRIGHT_YELLOW}No new commits#{RESET}"
+ fast_forward_master(impl)
+ end
end
end
end
diff --git a/spec/mspec/tool/tag_from_output.rb b/spec/mspec/tool/tag_from_output.rb
index ebe13434c2..41aa70f932 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,9 +13,14 @@ 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+/
+SPEC_FILE = /^((?:\/|[CD]:\/).+_spec\.rb)\:\d+/
output.slice_before(NUMBER).select { |number, *rest|
number =~ NUMBER and rest.any? { |line| line =~ ERROR_OR_FAILED }
@@ -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