diff options
Diffstat (limited to 'spec/mspec')
-rw-r--r-- | spec/mspec/lib/mspec/helpers/tmp.rb | 18 | ||||
-rw-r--r-- | spec/mspec/lib/mspec/mocks/mock.rb | 25 | ||||
-rw-r--r-- | spec/mspec/lib/mspec/runner/actions/leakchecker.rb | 58 | ||||
-rw-r--r-- | spec/mspec/lib/mspec/runner/actions/timeout.rb | 30 | ||||
-rw-r--r-- | spec/mspec/lib/mspec/runner/formatters/base.rb | 8 | ||||
-rw-r--r-- | spec/mspec/lib/mspec/runner/formatters/multi.rb | 2 | ||||
-rw-r--r-- | spec/mspec/lib/mspec/utils/options.rb | 2 | ||||
-rw-r--r-- | spec/mspec/spec/integration/run_spec.rb | 9 | ||||
-rw-r--r-- | spec/mspec/spec/integration/tag_spec.rb | 9 | ||||
-rw-r--r-- | spec/mspec/spec/mocks/mock_spec.rb | 21 | ||||
-rw-r--r-- | spec/mspec/spec/runner/context_spec.rb | 2 | ||||
-rw-r--r-- | spec/mspec/spec/spec_helper.rb | 2 | ||||
-rw-r--r-- | spec/mspec/tool/remove_old_guards.rb | 51 | ||||
-rwxr-xr-x | spec/mspec/tool/tag_from_output.rb | 10 |
14 files changed, 196 insertions, 51 deletions
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/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 69181b71d3..71797b9815 100644 --- a/spec/mspec/lib/mspec/runner/actions/leakchecker.rb +++ b/spec/mspec/lib/mspec/runner/actions/leakchecker.rb @@ -301,6 +301,7 @@ class LeakCheckerAction end def start + disable_nss_modules @checker = LeakChecker.new end @@ -316,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 499001c952..1200926872 100644 --- a/spec/mspec/lib/mspec/runner/actions/timeout.rb +++ b/spec/mspec/lib/mspec/runner/actions/timeout.rb @@ -48,11 +48,12 @@ class TimeoutAction show_backtraces if MSpec.subprocesses.empty? - exit 2 + exit! 2 else # Do not exit but signal the subprocess so we can get their output MSpec.subprocesses.each do |pid| - Process.kill :SIGTERM, pid + kill_wait_one_second :SIGTERM, pid + hard_kill :SIGKILL, pid end @fail = true @current_state = nil @@ -80,7 +81,7 @@ class TimeoutAction if @fail STDERR.puts "\n\nThe last example #{@error_message}. See above for the subprocess stacktrace." - exit 2 + exit! 2 end end @@ -89,12 +90,28 @@ class TimeoutAction @thread.join end + private def hard_kill(signal, pid) + begin + Process.kill signal, pid + rescue Errno::ESRCH + # Process already terminated + end + end + + private def kill_wait_one_second(signal, pid) + begin + Process.kill signal, pid + sleep 1 + rescue Errno::ESRCH + # Process already terminated + end + end + private def show_backtraces java_stacktraces = -> pid { if RUBY_ENGINE == 'truffleruby' || RUBY_ENGINE == 'jruby' STDERR.puts 'Java stacktraces:' - Process.kill :SIGQUIT, pid - sleep 1 + kill_wait_one_second :SIGQUIT, pid end } @@ -118,8 +135,7 @@ class TimeoutAction if RUBY_ENGINE == 'truffleruby' STDERR.puts "\nRuby backtraces:" - Process.kill :SIGALRM, pid - sleep 1 + kill_wait_one_second :SIGALRM, pid else STDERR.puts "Don't know how to print backtraces of a subprocess on #{RUBY_ENGINE}" end diff --git a/spec/mspec/lib/mspec/runner/formatters/base.rb b/spec/mspec/lib/mspec/runner/formatters/base.rb index 54a83c9c32..e3b5bb23e0 100644 --- a/spec/mspec/lib/mspec/runner/formatters/base.rb +++ b/spec/mspec/lib/mspec/runner/formatters/base.rb @@ -5,6 +5,9 @@ require 'mspec/utils/options' if ENV['CHECK_LEAKS'] require 'mspec/runner/actions/leakchecker' +end + +if ENV['CHECK_LEAKS'] || ENV['CHECK_CONSTANT_LEAKS'] require 'mspec/runner/actions/constants_leak_checker' end @@ -40,8 +43,11 @@ class BaseFormatter @counter = @tally.counter if ENV['CHECK_LEAKS'] - save = ENV['CHECK_LEAKS'] == 'save' LeakCheckerAction.new.register + end + + if ENV['CHECK_LEAKS'] || ENV['CHECK_CONSTANT_LEAKS'] + save = ENV['CHECK_LEAKS'] == 'save' || ENV['CHECK_CONSTANT_LEAKS'] == 'save' ConstantsLeakCheckerAction.new(save).register end diff --git a/spec/mspec/lib/mspec/runner/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/utils/options.rb b/spec/mspec/lib/mspec/utils/options.rb index 612caf6771..3b5962dbe6 100644 --- a/spec/mspec/lib/mspec/utils/options.rb +++ b/spec/mspec/lib/mspec/utils/options.rb @@ -477,7 +477,7 @@ class MSpecOptions def debug on("-d", "--debug", - "Set MSpec debugging flag for more verbose output") do + "Disable MSpec backtrace filtering") do $MSPEC_DEBUG = true end end diff --git a/spec/mspec/spec/integration/run_spec.rb b/spec/mspec/spec/integration/run_spec.rb index 90dc051543..ea0735e9b2 100644 --- a/spec/mspec/spec/integration/run_spec.rb +++ b/spec/mspec/spec/integration/run_spec.rb @@ -1,20 +1,21 @@ require 'spec_helper' RSpec.describe "Running mspec" do + q = BACKTRACE_QUOTE a_spec_output = <<EOS 1) Foo#bar errors FAILED Expected 1 == 2 to be truthy but was false -CWD/spec/fixtures/a_spec.rb:8:in `block (2 levels) in <top (required)>' -CWD/spec/fixtures/a_spec.rb:2:in `<top (required)>' +CWD/spec/fixtures/a_spec.rb:8:in #{q}block (2 levels) in <top (required)>' +CWD/spec/fixtures/a_spec.rb:2:in #{q}<top (required)>' 2) Foo#bar fails ERROR RuntimeError: failure -CWD/spec/fixtures/a_spec.rb:12:in `block (2 levels) in <top (required)>' -CWD/spec/fixtures/a_spec.rb:2:in `<top (required)>' +CWD/spec/fixtures/a_spec.rb:12:in #{q}block (2 levels) in <top (required)>' +CWD/spec/fixtures/a_spec.rb:2:in #{q}<top (required)>' Finished in D.DDDDDD seconds EOS diff --git a/spec/mspec/spec/integration/tag_spec.rb b/spec/mspec/spec/integration/tag_spec.rb index 33df1cfd40..ae08e9d45f 100644 --- a/spec/mspec/spec/integration/tag_spec.rb +++ b/spec/mspec/spec/integration/tag_spec.rb @@ -13,6 +13,7 @@ RSpec.describe "Running mspec tag" do it "tags the failing specs" do fixtures = "spec/fixtures" out, ret = run_mspec("tag", "--add fails --fail #{fixtures}/tagging_spec.rb") + q = BACKTRACE_QUOTE expect(out).to eq <<EOS RUBY_DESCRIPTION .FF @@ -26,15 +27,15 @@ Tag#me érròrs in unicode Tag#me errors FAILED Expected 1 == 2 to be truthy but was false -CWD/spec/fixtures/tagging_spec.rb:9:in `block (2 levels) in <top (required)>' -CWD/spec/fixtures/tagging_spec.rb:3:in `<top (required)>' +CWD/spec/fixtures/tagging_spec.rb:9:in #{q}block (2 levels) in <top (required)>' +CWD/spec/fixtures/tagging_spec.rb:3:in #{q}<top (required)>' 2) Tag#me érròrs in unicode FAILED Expected 1 == 2 to be truthy but was false -CWD/spec/fixtures/tagging_spec.rb:13:in `block (2 levels) in <top (required)>' -CWD/spec/fixtures/tagging_spec.rb:3:in `<top (required)>' +CWD/spec/fixtures/tagging_spec.rb:13:in #{q}block (2 levels) in <top (required)>' +CWD/spec/fixtures/tagging_spec.rb:3:in #{q}<top (required)>' Finished in D.DDDDDD seconds diff --git a/spec/mspec/spec/mocks/mock_spec.rb b/spec/mspec/spec/mocks/mock_spec.rb index 73f9bdfa14..7426e0ff88 100644 --- a/spec/mspec/spec/mocks/mock_spec.rb +++ b/spec/mspec/spec/mocks/mock_spec.rb @@ -22,14 +22,14 @@ end RSpec.describe Mock, ".replaced_name" do it "returns the name for a method that is being replaced by a mock method" do m = double('a fake id') - expect(Mock.replaced_name(m, :method_call)).to eq(:"__mspec_#{m.object_id}_method_call__") + expect(Mock.replaced_name(Mock.replaced_key(m, :method_call))).to eq(:"__mspec_method_call__") end end RSpec.describe Mock, ".replaced_key" do it "returns a key used internally by Mock" do m = double('a fake id') - expect(Mock.replaced_key(m, :method_call)).to eq([:"__mspec_#{m.object_id}_method_call__", :method_call]) + expect(Mock.replaced_key(m, :method_call)).to eq([m.object_id, :method_call]) end end @@ -42,16 +42,16 @@ RSpec.describe Mock, ".replaced?" do it "returns true if a method has been stubbed on an object" do Mock.install_method @mock, :method_call - expect(Mock.replaced?(Mock.replaced_name(@mock, :method_call))).to be_truthy + expect(Mock.replaced?(Mock.replaced_key(@mock, :method_call))).to be_truthy end it "returns true if a method has been mocked on an object" do Mock.install_method @mock, :method_call, :stub - expect(Mock.replaced?(Mock.replaced_name(@mock, :method_call))).to be_truthy + expect(Mock.replaced?(Mock.replaced_key(@mock, :method_call))).to be_truthy end it "returns false if a method has not been stubbed or mocked" do - expect(Mock.replaced?(Mock.replaced_name(@mock, :method_call))).to be_falsey + expect(Mock.replaced?(Mock.replaced_key(@mock, :method_call))).to be_falsey end end @@ -197,11 +197,11 @@ RSpec.describe Mock, ".install_method" do Mock.install_method @mock, :method_call expect(@mock).to respond_to(:method_call) - expect(@mock).not_to respond_to(Mock.replaced_name(@mock, :method_call)) + expect(@mock).not_to respond_to(Mock.replaced_name(Mock.replaced_key(@mock, :method_call))) Mock.install_method @mock, :method_call, :stub expect(@mock).to respond_to(:method_call) - expect(@mock).not_to respond_to(Mock.replaced_name(@mock, :method_call)) + expect(@mock).not_to respond_to(Mock.replaced_name(Mock.replaced_key(@mock, :method_call))) end end @@ -493,7 +493,7 @@ RSpec.describe Mock, ".cleanup" do it "removes the replaced method if the mock method overrides an existing method" do def @mock.already_here() :hey end expect(@mock).to respond_to(:already_here) - replaced_name = Mock.replaced_name(@mock, :already_here) + replaced_name = Mock.replaced_name(Mock.replaced_key(@mock, :already_here)) Mock.install_method @mock, :already_here expect(@mock).to respond_to(replaced_name) @@ -521,10 +521,9 @@ RSpec.describe Mock, ".cleanup" do replaced_key = Mock.replaced_key(@mock, :method_call) expect(Mock).to receive(:clear_replaced).with(replaced_key) - replaced_name = Mock.replaced_name(@mock, :method_call) - expect(Mock.replaced?(replaced_name)).to be_truthy + expect(Mock.replaced?(replaced_key)).to be_truthy Mock.cleanup - expect(Mock.replaced?(replaced_name)).to be_falsey + expect(Mock.replaced?(replaced_key)).to be_falsey end end diff --git a/spec/mspec/spec/runner/context_spec.rb b/spec/mspec/spec/runner/context_spec.rb index a864428aec..9ebc708c0c 100644 --- a/spec/mspec/spec/runner/context_spec.rb +++ b/spec/mspec/spec/runner/context_spec.rb @@ -914,7 +914,7 @@ RSpec.describe ContextState, "#it_should_behave_like" do it "raises an Exception if unable to find the shared ContextState" do expect(MSpec).to receive(:retrieve_shared).and_return(nil) - expect { @state.it_should_behave_like "this" }.to raise_error(Exception) + expect { @state.it_should_behave_like :this }.to raise_error(Exception) end describe "for nested ContextState instances" do diff --git a/spec/mspec/spec/spec_helper.rb b/spec/mspec/spec/spec_helper.rb index 3a749581ee..5cabfe5626 100644 --- a/spec/mspec/spec/spec_helper.rb +++ b/spec/mspec/spec/spec_helper.rb @@ -66,3 +66,5 @@ PublicMSpecMatchers = Class.new { include MSpecMatchers public :raise_error }.new + +BACKTRACE_QUOTE = RUBY_VERSION >= "3.4" ? "'" : "`" diff --git a/spec/mspec/tool/remove_old_guards.rb b/spec/mspec/tool/remove_old_guards.rb index 67485446bb..3fd95e6b31 100644 --- a/spec/mspec/tool/remove_old_guards.rb +++ b/spec/mspec/tool/remove_old_guards.rb @@ -46,6 +46,51 @@ def remove_guards(guard, keep) end end +def remove_empty_files + each_spec_file do |file| + unless file.include?("fixtures/") + lines = File.readlines(file) + if lines.all? { |line| line.chomp.empty? or line.start_with?('require', '#') } + puts "Removing empty file #{file}" + File.delete(file) + end + end + end +end + +def remove_unused_shared_specs + shared_groups = {} + # Dir["**/shared/**/*.rb"].each do |shared| + each_spec_file do |shared| + next if File.basename(shared) == 'constants.rb' + contents = File.binread(shared) + found = false + contents.scan(/^\s*describe (:[\w_?]+), shared: true do$/) { + shared_groups[$1] = 0 + found = true + } + if !found and shared.include?('shared/') and !shared.include?('fixtures/') and !shared.end_with?('/constants.rb') + puts "no shared describe in #{shared} ?" + end + end + + each_spec_file do |file| + contents = File.binread(file) + contents.scan(/(?:it_behaves_like|it_should_behave_like) (:[\w_?]+)[,\s]/) do + puts $1 unless shared_groups.key?($1) + shared_groups[$1] += 1 + end + end + + shared_groups.each_pair do |group, value| + if value == 0 + puts "Shared describe #{group} seems unused" + elsif value == 1 + puts "Shared describe #{group} seems used only once" if $VERBOSE + end + end +end + def search(regexp) each_spec_file do |file| contents = File.binread(file) @@ -64,7 +109,11 @@ version = Regexp.escape(ARGV.fetch(0)) version += "(?:\\.0)?" if version.count(".") < 2 remove_guards(/ruby_version_is (["'])#{version}\1 do/, true) remove_guards(/ruby_version_is (["'])[0-9.]*\1 *... *(["'])#{version}\2 do/, false) -remove_guards(/ruby_bug "#\d+", (["'])[0-9.]*\1 *... *(["'])#{version}\2 do/, true) +remove_guards(/ruby_bug ["']#\d+["'], (["'])[0-9.]*\1 *... *(["'])#{version}\2 do/, true) + +remove_empty_files +remove_unused_shared_specs +puts "Search:" search(/(["'])#{version}\1/) search(/^\s*#.+#{version}/) diff --git a/spec/mspec/tool/tag_from_output.rb b/spec/mspec/tool/tag_from_output.rb index a6e60945cd..b6b4603855 100755 --- a/spec/mspec/tool/tag_from_output.rb +++ b/spec/mspec/tool/tag_from_output.rb @@ -3,6 +3,8 @@ # Adds tags based on error and failures output (e.g., from a CI log), # without running any spec code. +tag = ENV["TAG"] || "fails" + tags_dir = %w[ spec/tags spec/tags/ruby @@ -30,9 +32,9 @@ output.slice_before(NUMBER).select { |number, *rest| if spec_file spec_file = spec_file[SPEC_FILE, 1] or raise else - if error_line =~ /^(\w+)[#\.](\w+) / - module_method = error_line.split(' ', 2).first - file = "#{$1.downcase}/#{$2}_spec.rb" + 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) @@ -54,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 |