├── Manifest.txt ├── Rakefile ├── lib └── minitest │ ├── gcstats_plugin.rb │ └── gcstats.rb ├── .autotest ├── History.rdoc ├── test └── minitest │ └── test_gcstats.rb └── README.rdoc /Manifest.txt: -------------------------------------------------------------------------------- 1 | .autotest 2 | History.rdoc 3 | Manifest.txt 4 | README.rdoc 5 | Rakefile 6 | lib/minitest/gcstats.rb 7 | lib/minitest/gcstats_plugin.rb 8 | test/minitest/test_gcstats.rb 9 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # -*- ruby -*- 2 | 3 | require "rubygems" 4 | require "hoe" 5 | 6 | Hoe.plugin :seattlerb 7 | Hoe.plugin :rdoc 8 | Hoe.plugin :isolate 9 | 10 | Hoe.spec "minitest-gcstats" do 11 | developer "Ryan Davis", "ryand-ruby@zenspider.com" 12 | license "MIT" 13 | 14 | dependency "minitest", "> 5.0" 15 | dependency "rake", "< 11", :developer 16 | dependency "minitest-proveit", "~> 1.0", :developer 17 | end 18 | 19 | # vim: syntax=ruby 20 | -------------------------------------------------------------------------------- /lib/minitest/gcstats_plugin.rb: -------------------------------------------------------------------------------- 1 | require "minitest" 2 | 3 | module Minitest 4 | @gcstats = false 5 | 6 | def self.plugin_gcstats_options opts, options # :nodoc: 7 | opts.on "-g", "--gcstats [N]", Integer, "Display top-N GC statistics per test." do |n| 8 | @gcstats = Integer(n||10) 9 | end 10 | end 11 | 12 | def self.plugin_gcstats_init options # :nodoc: 13 | if @gcstats then 14 | require "minitest/gcstats" 15 | self.reporter << Minitest::GCStatsReporter.new(@gcstats) 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /.autotest: -------------------------------------------------------------------------------- 1 | # -*- ruby -*- 2 | 3 | require "autotest/restart" 4 | 5 | Autotest.add_hook :initialize do |at| 6 | at.testlib = "minitest/autorun" 7 | at.add_exception "tmp" 8 | 9 | # at.extra_files << "../some/external/dependency.rb" 10 | # 11 | # at.libs << ":../some/external" 12 | # 13 | # at.add_exception "vendor" 14 | # 15 | # at.add_mapping(/dependency.rb/) do |f, _| 16 | # at.files_matching(/test_.*rb$/) 17 | # end 18 | # 19 | # %w(TestA TestB).each do |klass| 20 | # at.extra_class_map[klass] = "test/test_misc.rb" 21 | # end 22 | end 23 | 24 | # Autotest.add_hook :run_command do |at| 25 | # system "rake build" 26 | # end 27 | -------------------------------------------------------------------------------- /History.rdoc: -------------------------------------------------------------------------------- 1 | === 1.3.1 / 2024-02-26 2 | 3 | * 1 minor enhancement: 4 | 5 | * Remove a bunch of ancient versioned code now that <2.3 is way behind us. 6 | 7 | * 1 bug fix: 8 | 9 | * Fix Result.from to include gc_stats. (fatkodima) 10 | 11 | === 1.3.0 / 2021-10-27 12 | 13 | This is release 1024!?! 14 | 15 | * 1 minor enhancement: 16 | 17 | * Switched to prepending GCStats in and overriding run to record. Helps w/ 3.0. 18 | 19 | * 1 bug fix: 20 | 21 | * MOSTLY Fixed testing for ruby 3.0, which allocates inline cache in GC 22 | 23 | === 1.2.2 / 2016-06-13 24 | 25 | * 1 bug fix: 26 | 27 | * Added minitest-proveit developer dependency to help with minitest/hell gauntlet. 28 | 29 | === 1.2.1 / 2015-05-28 30 | 31 | * 1 bug fix: 32 | 33 | * Remove deprecation warning in ruby 2.2. (filipegiusti) 34 | 35 | === 1.2.0 / 2015-05-06 36 | 37 | * 1 minor enhancement: 38 | 39 | * Added assert_allocations to verify the number of objects allocated in tests. 40 | 41 | * 1 bug fix: 42 | 43 | * Fixed dependencies 44 | 45 | === 1.1.0 / 2014-09-26 46 | 47 | * 1 minor enhancement: 48 | 49 | * Added total and percentages to report. 50 | 51 | === 1.0.0 / 2014-08-29 52 | 53 | * 1 major enhancement 54 | 55 | * Birthday! 56 | 57 | -------------------------------------------------------------------------------- /test/minitest/test_gcstats.rb: -------------------------------------------------------------------------------- 1 | require "minitest/autorun" 2 | require "minitest/gcstats" 3 | require "minitest/proveit" 4 | 5 | module TestMinitest; end 6 | 7 | class TestMinitest::TestGcstats < Minitest::Test 8 | self.prove_it = false 9 | 10 | def util_n_times_full n 11 | n.times { test_full } 12 | end 13 | 14 | def util_array 15 | [test_full] 16 | end 17 | 18 | def setup 19 | util_n_times_full 1 # warm 20 | 21 | assert_allocations :<, 99 do 22 | # warm it up 23 | end 24 | end 25 | 26 | def test_empty 27 | # 0 objects 28 | end 29 | 30 | def test_full 31 | Object.new # 1 object 32 | end 33 | 34 | def test_assert_allocations 35 | assert_allocations 1 do 36 | test_full 37 | end 38 | end 39 | 40 | def test_assert_allocations_0 41 | assert_allocations 0 do 42 | # nothing 43 | end 44 | end 45 | 46 | def test_assert_allocations_multi 47 | assert_allocations 3 do 48 | util_n_times_full 3 49 | end 50 | end 51 | 52 | def test_assert_allocations_multi_eq 53 | assert_allocations :==, 3 do 54 | util_n_times_full 3 55 | end 56 | end 57 | 58 | def test_assert_allocations_neg_lt 59 | assert_allocations :<=, 3 do 60 | util_n_times_full 2 61 | end 62 | end 63 | 64 | def test_assert_allocations_neg_eq 65 | assert_allocations :<=, 3 do 66 | util_n_times_full 3 67 | end 68 | end 69 | 70 | def assert_triggered expected, klass = Minitest::Assertion 71 | e = assert_raises klass do 72 | yield 73 | end 74 | 75 | msg = e.message.sub(/(---Backtrace---).*/m, '\1') 76 | msg.gsub!(/\(oid=[-0-9]+\)/, "(oid=N)") 77 | msg.gsub!(/(\d\.\d{6})\d+/, '\1xxx') # normalize: ruby version, impl, platform 78 | 79 | assert_equal expected, msg 80 | end 81 | 82 | def test_assert_allocations_bad 83 | exp = "Object allocations.\nExpected 2 to be == 1." 84 | 85 | util_array # warm 86 | 87 | assert_triggered exp do 88 | assert_allocations 1 do 89 | util_array 90 | end 91 | end 92 | end 93 | 94 | def test_assert_allocations_neg_bad 95 | util_n_times_full 5 96 | 97 | exp = "Object allocations.\nExpected #{5} to be <= 3." 98 | 99 | assert_triggered exp do 100 | assert_allocations :<=, 3 do 101 | util_n_times_full 5 102 | end 103 | end 104 | end 105 | end 106 | -------------------------------------------------------------------------------- /lib/minitest/gcstats.rb: -------------------------------------------------------------------------------- 1 | require "minitest" 2 | 3 | module Minitest::GCStats 4 | VERSION = "1.3.1" 5 | 6 | attr_accessor :gc_stats 7 | 8 | def self.current 9 | GC.stat :total_allocated_objects 10 | end 11 | 12 | def run 13 | r = super 14 | r.gc_stats = self.gc_stats 15 | r 16 | end 17 | 18 | def before_setup 19 | super 20 | self.gc_stats = -Minitest::GCStats.current 21 | end 22 | 23 | def after_teardown 24 | self.gc_stats += Minitest::GCStats.current 25 | super 26 | end 27 | end 28 | 29 | module Minitest::GCStats::Result 30 | attr_accessor :gc_stats 31 | 32 | def self.prepended klass 33 | klass.singleton_class.prepend ClassMethods 34 | end 35 | 36 | module ClassMethods 37 | def from runnable 38 | r = super 39 | r.gc_stats = runnable.gc_stats 40 | r 41 | end 42 | end 43 | end 44 | 45 | Minitest::Test.prepend Minitest::GCStats 46 | Minitest::Result.prepend Minitest::GCStats::Result 47 | 48 | module Minitest::Assertions 49 | ## 50 | # Assert that the number of object allocations during block 51 | # execution is what you think it is. Uses +assert_operator+ so +op+ 52 | # should be something like :== or :<=. Defaults to :==. 53 | # 54 | # assert_allocations 3 do ... end 55 | # 56 | # is equivalent to: 57 | # 58 | # assert_allocations :==, 3 do ... end 59 | 60 | def assert_allocations op_or_exp, exp = nil, msg = nil 61 | m0 = Minitest::GCStats.current 62 | yield 63 | m1 = Minitest::GCStats.current 64 | 65 | op = op_or_exp 66 | op, exp, msg = :==, op, exp if Integer === op 67 | 68 | act = m1 - m0 69 | msg ||= "Object allocations" 70 | 71 | assert_operator act, op, exp, msg 72 | end 73 | end 74 | 75 | class Minitest::GCStatsReporter < Minitest::AbstractReporter 76 | attr_accessor :stats, :max 77 | 78 | def initialize max 79 | super() 80 | 81 | self.max = max 82 | self.stats = {} 83 | end 84 | 85 | def record result 86 | self.stats[result] = result.gc_stats 87 | end 88 | 89 | def report 90 | total = stats.values.sum 91 | pct = total / 100.0 92 | 93 | puts 94 | puts "Top #{max} tests by objects allocated" 95 | puts 96 | stats.sort_by { |k,v| [-v, k.class.name, k.name] }.first(max).each do |k,v| 97 | puts "%6d (%5.2f%%): %s" % [v, v / pct, k] 98 | end 99 | puts 100 | 101 | puts "%6d: %s" % [total, "Total"] 102 | end 103 | end 104 | -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | = minitest-gcstats 2 | 3 | home :: https://github.com/seattlerb/minitest-gcstats 4 | rdoc :: http://docs.seattlerb.org/minitest-gcstats 5 | 6 | == DESCRIPTION: 7 | 8 | A minitest plugin that adds a report of the top tests by number of 9 | objects allocated. 10 | 11 | == FEATURES/PROBLEMS: 12 | 13 | * Minitest plugin. No cost if you don't activate it. 14 | * Reports top N tests by number of objects allocated. 15 | 16 | == SYNOPSIS: 17 | 18 | % rake TESTOPTS=-g 19 | Run options: -g --seed 59120 20 | 21 | # Running: 22 | 23 | .............................................................................. 24 | .............................................................................. 25 | .............................................................................. 26 | ................................................... 27 | 28 | Finished in 0.145769s, 1955.1482 runs/s, 5769.4023 assertions/s. 29 | 30 | 285 runs, 841 assertions, 0 failures, 0 errors, 0 skips 31 | 32 | Top 10 tests by objects allocated 33 | 34 | 2323: TestMeta#test_bug_dsl_expectations 35 | 2101: TestMinitestUnitOrder#test_setup_and_teardown_survive_inheritance 36 | 2096: TestMeta#test_setup_teardown_behavior 37 | 1462: TestMinitestRunner#test_run_filtered_string_method_only 38 | 1393: TestMinitestRunner#test_run_filtered_including_suite_name 39 | 1378: TestMinitestRunner#test_run_filtered_including_suite_name_string 40 | 1292: TestMinitestRunner#test_run_parallel 41 | 1287: TestMinitestRunner#test_run_skip_verbose 42 | 1276: TestMinitestUnitOrder#test_all_teardowns_are_guaranteed_to_run 43 | 1264: TestMinitestRunner#test_run_error 44 | 45 | == REQUIREMENTS: 46 | 47 | * minitest 48 | * ruby 2.0+ 49 | 50 | == INSTALL: 51 | 52 | * sudo gem install minitest-gcstats 53 | 54 | == LICENSE: 55 | 56 | (The MIT License) 57 | 58 | Copyright (c) Ryan Davis, seattle.rb 59 | 60 | Permission is hereby granted, free of charge, to any person obtaining 61 | a copy of this software and associated documentation files (the 62 | 'Software'), to deal in the Software without restriction, including 63 | without limitation the rights to use, copy, modify, merge, publish, 64 | distribute, sublicense, and/or sell copies of the Software, and to 65 | permit persons to whom the Software is furnished to do so, subject to 66 | the following conditions: 67 | 68 | The above copyright notice and this permission notice shall be 69 | included in all copies or substantial portions of the Software. 70 | 71 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 72 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 73 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 74 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 75 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 76 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 77 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 78 | --------------------------------------------------------------------------------