diff options
Diffstat (limited to 'spec/mspec')
| -rw-r--r-- | spec/mspec/lib/mspec/commands/mspec-ci.rb | 1 | ||||
| -rw-r--r-- | spec/mspec/lib/mspec/commands/mspec-tag.rb | 1 | ||||
| -rw-r--r-- | spec/mspec/lib/mspec/matchers/base.rb | 16 | ||||
| -rw-r--r-- | spec/mspec/lib/mspec/matchers/raise_error.rb | 63 | ||||
| -rw-r--r-- | spec/mspec/lib/mspec/runner/formatters/base.rb | 2 | ||||
| -rw-r--r-- | spec/mspec/lib/mspec/runner/mspec.rb | 1 | ||||
| -rw-r--r-- | spec/mspec/lib/mspec/utils/name_map.rb | 13 | ||||
| -rw-r--r-- | spec/mspec/spec/commands/mspec_ci_spec.rb | 5 | ||||
| -rw-r--r-- | spec/mspec/spec/commands/mspec_run_spec.rb | 5 | ||||
| -rw-r--r-- | spec/mspec/spec/matchers/raise_error_spec.rb | 73 | ||||
| -rw-r--r-- | spec/mspec/spec/utils/fixtures/this_file_raises.rb | 1 | ||||
| -rw-r--r-- | spec/mspec/spec/utils/fixtures/this_file_raises2.rb | 1 | ||||
| -rw-r--r-- | spec/mspec/spec/utils/name_map_spec.rb | 12 | ||||
| -rwxr-xr-x[-rw-r--r--] | spec/mspec/tool/remove_old_guards.rb | 28 | ||||
| -rw-r--r-- | spec/mspec/tool/sync/sync-rubyspec.rb | 30 | ||||
| -rwxr-xr-x | spec/mspec/tool/tag_from_output.rb | 2 |
16 files changed, 208 insertions, 46 deletions
diff --git a/spec/mspec/lib/mspec/commands/mspec-ci.rb b/spec/mspec/lib/mspec/commands/mspec-ci.rb index 9959059ca1..8951572f69 100644 --- a/spec/mspec/lib/mspec/commands/mspec-ci.rb +++ b/spec/mspec/lib/mspec/commands/mspec-ci.rb @@ -18,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-tag.rb b/spec/mspec/lib/mspec/commands/mspec-tag.rb index 8b1cb83809..9ce9f048c6 100644 --- a/spec/mspec/lib/mspec/commands/mspec-tag.rb +++ b/spec/mspec/lib/mspec/commands/mspec-tag.rb @@ -112,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/matchers/base.rb b/spec/mspec/lib/mspec/matchers/base.rb index d9d7f6fec0..3534520d88 100644 --- a/spec/mspec/lib/mspec/matchers/base.rb +++ b/spec/mspec/lib/mspec/matchers/base.rb @@ -36,6 +36,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 +78,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/raise_error.rb b/spec/mspec/lib/mspec/matchers/raise_error.rb index 54378bb34c..8cba842ce3 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,18 @@ 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) + 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/runner/formatters/base.rb b/spec/mspec/lib/mspec/runner/formatters/base.rb index e3b5bb23e0..882e15c8c2 100644 --- a/spec/mspec/lib/mspec/runner/formatters/base.rb +++ b/spec/mspec/lib/mspec/runner/formatters/base.rb @@ -46,7 +46,7 @@ class BaseFormatter LeakCheckerAction.new.register end - if ENV['CHECK_LEAKS'] || ENV['CHECK_CONSTANT_LEAKS'] + 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 diff --git a/spec/mspec/lib/mspec/runner/mspec.rb b/spec/mspec/lib/mspec/runner/mspec.rb index 97c3f365bc..0e016c67a7 100644 --- a/spec/mspec/lib/mspec/runner/mspec.rb +++ b/spec/mspec/lib/mspec/runner/mspec.rb @@ -365,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| diff --git a/spec/mspec/lib/mspec/utils/name_map.rb b/spec/mspec/lib/mspec/utils/name_map.rb index bf70e651a2..9b04112e2e 100644 --- a/spec/mspec/lib/mspec/utils/name_map.rb +++ b/spec/mspec/lib/mspec/utils/name_map.rb @@ -66,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) 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/matchers/raise_error_spec.rb b/spec/mspec/spec/matchers/raise_error_spec.rb index 8613eee118..3849c7dd2a 100644 --- a/spec/mspec/spec/matchers/raise_error_spec.rb +++ b/spec/mspec/spec/matchers/raise_error_spec.rb @@ -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/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/tool/remove_old_guards.rb b/spec/mspec/tool/remove_old_guards.rb index 3fd95e6b31..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) @@ -92,7 +115,7 @@ def remove_unused_shared_specs end def search(regexp) - each_spec_file do |file| + each_file do |file| contents = File.binread(file) if contents =~ regexp puts file @@ -105,6 +128,8 @@ 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) @@ -117,3 +142,4 @@ 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 effccc1567..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: { @@ -34,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" @@ -142,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 @@ -183,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 @@ -236,7 +233,6 @@ def main(impls) rebase_commits(impl) if new_commits?(impl) test_new_specs - verify_commits(impl) fast_forward_master(impl) check_ci else diff --git a/spec/mspec/tool/tag_from_output.rb b/spec/mspec/tool/tag_from_output.rb index b6b4603855..41aa70f932 100755 --- a/spec/mspec/tool/tag_from_output.rb +++ b/spec/mspec/tool/tag_from_output.rb @@ -20,7 +20,7 @@ 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 } |
