ruby-changes:54904
From: eregon <ko1@a...>
Date: Fri, 22 Feb 2019 00:38:41 +0900 (JST)
Subject: [ruby-changes:54904] eregon:r67109 (trunk): Update to ruby/mspec@2ee5661
eregon 2019-02-22 00:38:36 +0900 (Fri, 22 Feb 2019) New Revision: 67109 https://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=revision&revision=67109 Log: Update to ruby/mspec@2ee5661 Added files: trunk/spec/mspec/lib/mspec/runner/actions/constants_leak_checker.rb Modified files: trunk/spec/mspec/lib/mspec/runner/actions/leakchecker.rb trunk/spec/mspec/lib/mspec/runner/formatters/dotted.rb Index: spec/mspec/lib/mspec/runner/actions/leakchecker.rb =================================================================== --- spec/mspec/lib/mspec/runner/actions/leakchecker.rb (revision 67108) +++ spec/mspec/lib/mspec/runner/actions/leakchecker.rb (revision 67109) @@ -24,7 +24,12 @@ https://github.com/ruby/ruby/blob/trunk/spec/mspec/lib/mspec/runner/actions/leakchecker.rb#L24 # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. +class LeakError < StandardError +end + class LeakChecker + attr_reader :leaks + def initialize @fd_info = find_fds @tempfile_info = find_tempfiles @@ -34,19 +39,18 @@ class LeakChecker https://github.com/ruby/ruby/blob/trunk/spec/mspec/lib/mspec/runner/actions/leakchecker.rb#L39 @encoding_info = find_encodings end - def check(test_name) - @no_leaks = true - leaks = [ - check_fd_leak(test_name), - check_tempfile_leak(test_name), - check_thread_leak(test_name), - check_process_leak(test_name), - check_env(test_name), - check_argv(test_name), - check_encodings(test_name) - ] - GC.start if leaks.any? - return leaks.none? + def check(state) + @state = state + @leaks = [] + check_fd_leak + check_tempfile_leak + check_thread_leak + check_process_leak + check_env + check_argv + check_encodings + GC.start if !@leaks.empty? + @leaks.empty? end private @@ -66,8 +70,7 @@ class LeakChecker https://github.com/ruby/ruby/blob/trunk/spec/mspec/lib/mspec/runner/actions/leakchecker.rb#L70 end end - def check_fd_leak(test_name) - leaked = false + def check_fd_leak live1 = @fd_info if IO.respond_to?(:console) and (m = IO.method(:console)).arity.nonzero? m[:close] @@ -76,12 +79,11 @@ class LeakChecker https://github.com/ruby/ruby/blob/trunk/spec/mspec/lib/mspec/runner/actions/leakchecker.rb#L79 fd_closed = live1 - live2 if !fd_closed.empty? fd_closed.each {|fd| - puts "Closed file descriptor: #{test_name}: #{fd}" + leak "Closed file descriptor: #{fd}" } end fd_leaked = live2 - live1 if !fd_leaked.empty? - leaked = true h = {} ObjectSpace.each_object(IO) {|io| inspect = io.inspect @@ -105,19 +107,18 @@ class LeakChecker https://github.com/ruby/ruby/blob/trunk/spec/mspec/lib/mspec/runner/actions/leakchecker.rb#L107 str << s } end - puts "Leaked file descriptor: #{test_name}: #{fd}#{str}" + leak "Leaked file descriptor: #{fd}#{str}" } #system("lsof -p #$$") if !fd_leaked.empty? h.each {|fd, list| next if list.length <= 1 if 1 < list.count {|io, autoclose, inspect| autoclose } str = list.map {|io, autoclose, inspect| " #{inspect}" + (autoclose ? "(autoclose)" : "") }.sort.join - puts "Multiple autoclose IO object for a file descriptor:#{str}" + leak "Multiple autoclose IO object for a file descriptor:#{str}" end } end @fd_info = live2 - return leaked end def extend_tempfile_counter @@ -152,22 +153,19 @@ class LeakChecker https://github.com/ruby/ruby/blob/trunk/spec/mspec/lib/mspec/runner/actions/leakchecker.rb#L153 end end - def check_tempfile_leak(test_name) + def check_tempfile_leak return false unless defined? Tempfile count1, initial_tempfiles = @tempfile_info count2, current_tempfiles = find_tempfiles(count1) - leaked = false tempfiles_leaked = current_tempfiles - initial_tempfiles if !tempfiles_leaked.empty? - leaked = true list = tempfiles_leaked.map {|t| t.inspect }.sort list.each {|str| - puts "Leaked tempfile: #{test_name}: #{str}" + leak "Leaked tempfile: #{str}" } tempfiles_leaked.each {|t| t.close! } end @tempfile_info = [count2, initial_tempfiles] - return leaked end def find_threads @@ -176,108 +174,98 @@ class LeakChecker https://github.com/ruby/ruby/blob/trunk/spec/mspec/lib/mspec/runner/actions/leakchecker.rb#L174 } end - def check_thread_leak(test_name) + def check_thread_leak live1 = @thread_info live2 = find_threads thread_finished = live1 - live2 - leaked = false if !thread_finished.empty? list = thread_finished.map {|t| t.inspect }.sort list.each {|str| - puts "Finished thread: #{test_name}: #{str}" + leak "Finished thread: #{str}" } end thread_leaked = live2 - live1 if !thread_leaked.empty? - leaked = true list = thread_leaked.map {|t| t.inspect }.sort list.each {|str| - puts "Leaked thread: #{test_name}: #{str}" + leak "Leaked thread: #{str}" } end @thread_info = live2 - return leaked end - def check_process_leak(test_name) + def check_process_leak subprocesses_leaked = Process.waitall subprocesses_leaked.each { |pid, status| - puts "Leaked subprocess: #{pid}: #{status}" + leak "Leaked subprocess: #{pid}: #{status}" } - return !subprocesses_leaked.empty? end def find_env ENV.to_h end - def check_env(test_name) + def check_env old_env = @env_info new_env = find_env - return false if old_env == new_env + return if old_env == new_env + (old_env.keys | new_env.keys).sort.each {|k| if old_env.has_key?(k) if new_env.has_key?(k) if old_env[k] != new_env[k] - puts "Environment variable changed: #{test_name} : #{k.inspect} changed : #{old_env[k].inspect} -> #{new_env[k].inspect}" + leak "Environment variable changed : #{k.inspect} changed : #{old_env[k].inspect} -> #{new_env[k].inspect}" end else - puts "Environment variable changed: #{test_name} : #{k.inspect} deleted" + leak "Environment variable changed: #{k.inspect} deleted" end else if new_env.has_key?(k) - puts "Environment variable changed: #{test_name} : #{k.inspect} added" + leak "Environment variable changed: #{k.inspect} added" else flunk "unreachable" end end } @env_info = new_env - return true end def find_argv ARGV.map { |e| e.dup } end - def check_argv(test_name) + def check_argv old_argv = @argv_info new_argv = find_argv - leaked = false if new_argv != old_argv - puts "ARGV changed: #{test_name} : #{old_argv.inspect} to #{new_argv.inspect}" + leak "ARGV changed: #{old_argv.inspect} to #{new_argv.inspect}" @argv_info = new_argv - leaked = true end - return leaked end def find_encodings [Encoding.default_internal, Encoding.default_external] end - def check_encodings(test_name) + def check_encodings old_internal, old_external = @encoding_info new_internal, new_external = find_encodings - leaked = false if new_internal != old_internal - leaked = true - puts "Encoding.default_internal changed: #{test_name} : #{old_internal.inspect} to #{new_internal.inspect}" + leak "Encoding.default_internal changed: #{old_internal.inspect} to #{new_internal.inspect}" end if new_external != old_external - leaked = true - puts "Encoding.default_external changed: #{test_name} : #{old_external.inspect} to #{new_external.inspect}" + leak "Encoding.default_external changed: #{old_external.inspect} to #{new_external.inspect}" end @encoding_info = [new_internal, new_external] - return leaked end - def puts(*args) - if @no_leaks - @no_leaks = false - print "\n" + def leak(message) + if @leaks.empty? + $stderr.puts "\n" + $stderr.puts @state.description end - super(*args) + @leaks << message + $stderr.puts message end end @@ -292,9 +280,14 @@ class LeakCheckerAction https://github.com/ruby/ruby/blob/trunk/spec/mspec/lib/mspec/runner/actions/leakchecker.rb#L280 end def after(state) - unless @checker.check(state.description) + unless @checker.check(state) + leak_messages = @checker.leaks + location = state.description if state.example - puts state.example.source_location.join(':') + location = "#{location}\n#{state.example.source_location.join(':')}" + end + MSpec.protect(location) do + raise LeakError, leak_messages.join("\n") end end end Index: spec/mspec/lib/mspec/runner/actions/constants_leak_checker.rb =================================================================== --- spec/mspec/lib/mspec/runner/actions/constants_leak_checker.rb (nonexistent) +++ spec/mspec/lib/mspec/runner/actions/constants_leak_checker.rb (revision 67109) @@ -0,0 +1,79 @@ https://github.com/ruby/ruby/blob/trunk/spec/mspec/lib/mspec/runner/actions/constants_leak_checker.rb#L1 +class ConstantsLockFile + LOCK_FILE_NAME = '.mspec.constants' + + def self.load + if File.exist?(LOCK_FILE_NAME) + File.readlines(LOCK_FILE_NAME).map(&:chomp) + else + [] + end + end + + def self.dump(ary) + contents = ary.map(&:to_s).uniq.sort.join("\n") + "\n" + File.write(LOCK_FILE_NAME, contents) + end +end + +class ConstantLeakError < StandardError +end + +class ConstantsLeakCheckerAction + def initialize(save) + @save = save + @check = !save + @constants_locked = ConstantsLockFile.load + @exclude_patterns = MSpecScript.get(:toplevel_constants_excludes) || [] + end + + def register + MSpec.register :start, self + MSpec.register :before, self + MSpec.register :after, self + MSpec.register :finish, self + end + + def start + @constants_start = constants_now + end + + def before(state) + @constants_before = constants_now + end + + def after(state) + constants = remove_excludes(constants_now - @constants_before - @constants_locked) + + if @check && !constants.empty? + MSpec.protect 'Constants leak check' do + raise ConstantLeakError, "Top level constants leaked: #{constants.join(', ')}" + end + end + end + + def finish + constants = remove_excludes(constants_now - @constants_start - @constants_locked) + + if @save + ConstantsLockFile.dump(@constants_locked + constants) + end + + if @check && !constants.empty? + MSpec.protect 'Global constants leak check' do + raise ConstantLeakError, "Top level constants leaked in the whole test suite: #{constants.join(', ')}" + end + end + end + + private + + def constants_now + Object.constants.map(&:to_s) + end + + def remove_excludes(constants) + constants.reject { |name| + @exclude_patterns.any? { |pattern| pattern === name } + } + end +end Index: spec/mspec/lib/mspec/runner/formatters/dotted.rb =================================================================== --- spec/mspec/lib/mspec/runner/formatters/dotted.rb (revision 67108) +++ spec/mspec/lib/mspec/runner/formatters/dotted.rb (revision 67109) @@ -1,7 +1,11 @@ https://github.com/ruby/ruby/blob/trunk/spec/mspec/lib/mspec/runner/formatters/dotted.rb#L1 require 'mspec/expectations/expectations' require 'mspec/runner/actions/timer' require 'mspec/runner/actions/tally' -require 'mspec/runner/actions/leakchecker' if ENV['CHECK_LEAKS'] + +if ENV['CHECK_LEAKS'] + require 'mspec/runner/actions/leakchecker' + require 'mspec/runner/actions/constants_leak_checker' +end class DottedFormatter attr_reader :exceptions, :timer, :tally @@ -25,7 +29,11 @@ class DottedFormatter https://github.com/ruby/ruby/blob/trunk/spec/mspec/lib/mspec/runner/formatters/dotted.rb#L29 def register (@timer = TimerAction.new).register (@tally = TallyAction.new).register - LeakCheckerAction.new.register if ENV['CHECK_LEAKS'] + if ENV['CHECK_LEAKS'] + save = ENV['CHECK_LEAKS'] == 'save' + LeakCheckerAction.new.register + ConstantsLeakCheckerAction.new(save).register + end @counter = @tally.counter MSpec.register :exception, self -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/