test-prof 0.11.0 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +109 -446
- data/LICENSE.txt +1 -1
- data/README.md +9 -13
- data/config/default.yml +35 -0
- data/lib/minitest/test_prof_plugin.rb +3 -0
- data/lib/test_prof/before_all.rb +0 -4
- data/lib/test_prof/cops/inject.rb +16 -14
- data/lib/test_prof/cops/rspec/aggregate_examples.rb +2 -2
- data/lib/test_prof/cops/rspec/aggregate_examples/its.rb +1 -1
- data/lib/test_prof/cops/rspec/aggregate_examples/line_range_helpers.rb +1 -1
- data/lib/test_prof/cops/rspec/aggregate_examples/matchers_with_side_effects.rb +1 -1
- data/lib/test_prof/cops/rspec/aggregate_examples/metadata_helpers.rb +1 -1
- data/lib/test_prof/cops/rspec/aggregate_examples/node_matchers.rb +1 -1
- data/lib/test_prof/cops/rspec/aggregate_failures.rb +2 -4
- data/lib/test_prof/event_prof/instrumentations/active_support.rb +22 -4
- data/lib/test_prof/ext/active_record_3.rb +1 -1
- data/lib/test_prof/recipes/minitest/sample.rb +6 -10
- data/lib/test_prof/recipes/rspec/before_all.rb +1 -9
- data/lib/test_prof/recipes/rspec/let_it_be.rb +108 -11
- data/lib/test_prof/recipes/rspec/sample.rb +4 -2
- data/lib/test_prof/stack_prof.rb +3 -0
- data/lib/test_prof/version.rb +1 -1
- metadata +16 -15
    
        data/LICENSE.txt
    CHANGED
    
    
    
        data/README.md
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 | 
            -
            []( | 
| 2 | 
            -
            [](https://rubygems.org/gems/test-prof) [](https://cultofmartians.com)
         | 
| 2 | 
            +
            [](https://rubygems.org/gems/test-prof) [](https://github.com/test-prof/test-prof/actions)
         | 
| 3 | 
            +
            [](https://github.com/test-prof/test-prof/actions)
         | 
| 4 | 
            +
            [](https://www.codetriage.com/test-prof/test-prof)
         | 
| 5 5 | 
             
            [](https://test-prof.evilmartians.io)
         | 
| 6 6 |  | 
| 7 7 | 
             
            # Ruby Tests Profiling Toolbox
         | 
| @@ -47,11 +47,11 @@ TestProf toolbox aims to help you identify bottlenecks in your test suite. It co | |
| 47 47 | 
             
            ## Who uses TestProf
         | 
| 48 48 |  | 
| 49 49 | 
             
            - [Discourse](https://github.com/discourse/discourse) reduced [~27% of their test suite time](https://twitter.com/samsaffron/status/1125602558024699904)
         | 
| 50 | 
            -
            - [Gitlab](https://gitlab.com/gitlab-org/gitlab-ce) reduced [39% of their API tests time](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14370)
         | 
| 50 | 
            +
            - [Gitlab](https://gitlab.com/gitlab-org/gitlab-ce) reduced [39% of their API tests time](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14370) and [improved factories usage](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/26810)
         | 
| 51 51 | 
             
            - [CodeTriage](https://github.com/codetriage/codetriage)
         | 
| 52 52 | 
             
            - [Dev.to](https://github.com/thepracticaldev/dev.to)
         | 
| 53 53 | 
             
            - [Open Project](https://github.com/opf/openproject)
         | 
| 54 | 
            -
            - [...and others](https://github.com/ | 
| 54 | 
            +
            - [...and others](https://github.com/test-prof/test-prof/issues/73)
         | 
| 55 55 |  | 
| 56 56 | 
             
            ## Resources
         | 
| 57 57 |  | 
| @@ -83,7 +83,7 @@ And that's it) | |
| 83 83 |  | 
| 84 84 | 
             
            Supported Ruby versions:
         | 
| 85 85 |  | 
| 86 | 
            -
            - Ruby (MRI) >= 2. | 
| 86 | 
            +
            - Ruby (MRI) >= 2.5.0 (**NOTE:** for Ruby 2.2 use TestProf < 0.7.0, Ruby 2.3 use TestProf ~> 0.7.0, Ruby 2.4 use TestProf <0.12.0)
         | 
| 87 87 |  | 
| 88 88 | 
             
            - JRuby >= 9.1.0.0 (**NOTE:** refinements-dependent features might require 9.2.7+)
         | 
| 89 89 |  | 
| @@ -95,16 +95,12 @@ Check out our [docs][]. | |
| 95 95 |  | 
| 96 96 | 
             
            ## What's next?
         | 
| 97 97 |  | 
| 98 | 
            -
            Have an idea? [Propose](https://github.com/ | 
| 98 | 
            +
            Have an idea? [Propose](https://github.com/test-prof/test-prof/issues/new) a feature request!
         | 
| 99 99 |  | 
| 100 | 
            -
            Already using TestProf? [Share your story!](https://github.com/ | 
| 100 | 
            +
            Already using TestProf? [Share your story!](https://github.com/test-prof/test-prof/issues/73)
         | 
| 101 101 |  | 
| 102 102 | 
             
            ## License
         | 
| 103 103 |  | 
| 104 104 | 
             
            The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
         | 
| 105 105 |  | 
| 106 106 | 
             
            [docs]: https://test-prof.evilmartians.io
         | 
| 107 | 
            -
             | 
| 108 | 
            -
            ## Security Contact
         | 
| 109 | 
            -
             | 
| 110 | 
            -
            To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure.
         | 
    
        data/config/default.yml
    ADDED
    
    | @@ -0,0 +1,35 @@ | |
| 1 | 
            +
            ---
         | 
| 2 | 
            +
            AllCops:
         | 
| 3 | 
            +
              RSpec:
         | 
| 4 | 
            +
                Patterns:
         | 
| 5 | 
            +
                - _spec.rb
         | 
| 6 | 
            +
                - "(?:^|/)spec/"
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            RSpec/AggregateExamples:
         | 
| 9 | 
            +
              Description: Checks if example group contains two or more aggregatable examples.
         | 
| 10 | 
            +
              Enabled: true
         | 
| 11 | 
            +
              StyleGuide: https://rspec.rubystyle.guide/#expectation-per-example
         | 
| 12 | 
            +
              AddAggregateFailuresMetadata: true
         | 
| 13 | 
            +
              MatchersWithSideEffects:
         | 
| 14 | 
            +
                - allow_value
         | 
| 15 | 
            +
                - allow_values
         | 
| 16 | 
            +
                - validate_presence_of
         | 
| 17 | 
            +
                - validate_absence_of
         | 
| 18 | 
            +
                - validate_length_of
         | 
| 19 | 
            +
                - validate_inclusion_of
         | 
| 20 | 
            +
                - validates_exclusion_of
         | 
| 21 | 
            +
             | 
| 22 | 
            +
            # TODO: remove this one we hit 1.0
         | 
| 23 | 
            +
            RSpec/AggregateFailures:
         | 
| 24 | 
            +
              Description: Checks if example group contains two or more aggregatable examples.
         | 
| 25 | 
            +
              Enabled: false
         | 
| 26 | 
            +
              StyleGuide: https://rspec.rubystyle.guide/#expectation-per-example
         | 
| 27 | 
            +
              AddAggregateFailuresMetadata: true
         | 
| 28 | 
            +
              MatchersWithSideEffects:
         | 
| 29 | 
            +
                - allow_value
         | 
| 30 | 
            +
                - allow_values
         | 
| 31 | 
            +
                - validate_presence_of
         | 
| 32 | 
            +
                - validate_absence_of
         | 
| 33 | 
            +
                - validate_length_of
         | 
| 34 | 
            +
                - validate_inclusion_of
         | 
| 35 | 
            +
                - validates_exclusion_of
         | 
| @@ -12,6 +12,7 @@ module Minitest # :nodoc: | |
| 12 12 | 
             
                    opts[:top_count] = ENV["EVENT_PROF_TOP"].to_i if ENV["EVENT_PROF_TOP"]
         | 
| 13 13 | 
             
                    opts[:per_example] = true if ENV["EVENT_PROF_EXAMPLES"]
         | 
| 14 14 | 
             
                    opts[:fdoc] = true if ENV["FDOC"]
         | 
| 15 | 
            +
                    opts[:sample] = true if ENV["SAMPLE"] || ENV["SAMPLE_GROUPS"]
         | 
| 15 16 | 
             
                  end
         | 
| 16 17 | 
             
                end
         | 
| 17 18 | 
             
              end
         | 
| @@ -39,5 +40,7 @@ module Minitest # :nodoc: | |
| 39 40 |  | 
| 40 41 | 
             
                reporter << TestProf::EventProfReporter.new(options[:io], options) if options[:event]
         | 
| 41 42 | 
             
                reporter << TestProf::FactoryDoctorReporter.new(options[:io], options) if options[:fdoc]
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                ::TestProf::MinitestSample.call if options[:sample]
         | 
| 42 45 | 
             
              end
         | 
| 43 46 | 
             
            end
         | 
    
        data/lib/test_prof/before_all.rb
    CHANGED
    
    
| @@ -2,22 +2,24 @@ | |
| 2 2 |  | 
| 3 3 | 
             
            # This is shamelessly borrowed from RuboCop RSpec
         | 
| 4 4 | 
             
            # https://github.com/rubocop-hq/rubocop-rspec/blob/master/lib/rubocop/rspec/inject.rb
         | 
| 5 | 
            -
            module  | 
| 6 | 
            -
               | 
| 7 | 
            -
             | 
| 8 | 
            -
             | 
| 9 | 
            -
                 | 
| 10 | 
            -
             | 
| 5 | 
            +
            module TestProf
         | 
| 6 | 
            +
              module Cops
         | 
| 7 | 
            +
                # Because RuboCop doesn't yet support plugins, we have to monkey patch in a
         | 
| 8 | 
            +
                # bit of our configuration.
         | 
| 9 | 
            +
                module Inject
         | 
| 10 | 
            +
                  PROJECT_ROOT = Pathname.new(__dir__).parent.parent.parent.expand_path.freeze
         | 
| 11 | 
            +
                  CONFIG_DEFAULT = PROJECT_ROOT.join("config", "default.yml").freeze
         | 
| 11 12 |  | 
| 12 | 
            -
             | 
| 13 | 
            -
             | 
| 14 | 
            -
             | 
| 15 | 
            -
             | 
| 16 | 
            -
             | 
| 17 | 
            -
             | 
| 18 | 
            -
             | 
| 13 | 
            +
                  def self.defaults!
         | 
| 14 | 
            +
                    path = CONFIG_DEFAULT.to_s
         | 
| 15 | 
            +
                    hash = RuboCop::ConfigLoader.send(:load_yaml_configuration, path)
         | 
| 16 | 
            +
                    config = RuboCop::Config.new(hash, path)
         | 
| 17 | 
            +
                    puts "configuration from #{path}" if RuboCop::ConfigLoader.debug?
         | 
| 18 | 
            +
                    config = RuboCop::ConfigLoader.merge_with_default(config, path)
         | 
| 19 | 
            +
                    RuboCop::ConfigLoader.instance_variable_set(:@default_configuration, config)
         | 
| 20 | 
            +
                  end
         | 
| 19 21 | 
             
                end
         | 
| 20 22 | 
             
              end
         | 
| 21 23 | 
             
            end
         | 
| 22 24 |  | 
| 23 | 
            -
             | 
| 25 | 
            +
            TestProf::Cops::Inject.defaults!
         | 
| @@ -12,7 +12,7 @@ module RuboCop | |
| 12 12 | 
             
                module RSpec
         | 
| 13 13 | 
             
                  # Checks if example groups contain two or more aggregatable examples.
         | 
| 14 14 | 
             
                  #
         | 
| 15 | 
            -
                  # @see https://github.com/rubocop-hq/rspec-style-guide# | 
| 15 | 
            +
                  # @see https://github.com/rubocop-hq/rspec-style-guide#expectation-per-example
         | 
| 16 16 | 
             
                  #
         | 
| 17 17 | 
             
                  # This cop is primarily for reducing the cost of repeated expensive
         | 
| 18 18 | 
             
                  # context initialization.
         | 
| @@ -108,7 +108,7 @@ module RuboCop | |
| 108 108 | 
             
                  #     expect(number).to be_odd
         | 
| 109 109 | 
             
                  #   end
         | 
| 110 110 | 
             
                  #
         | 
| 111 | 
            -
                  class AggregateExamples < Cop
         | 
| 111 | 
            +
                  class AggregateExamples < ::RuboCop::Cop::Cop
         | 
| 112 112 | 
             
                    include LineRangeHelpers
         | 
| 113 113 | 
             
                    include MetadataHelpers
         | 
| 114 114 | 
             
                    include NodeMatchers
         | 
| @@ -5,7 +5,7 @@ require_relative "../language" | |
| 5 5 | 
             
            module RuboCop
         | 
| 6 6 | 
             
              module Cop
         | 
| 7 7 | 
             
                module RSpec
         | 
| 8 | 
            -
                  class AggregateExamples < Cop
         | 
| 8 | 
            +
                  class AggregateExamples < ::RuboCop::Cop::Cop
         | 
| 9 9 | 
             
                    # When aggregated, the expectations will fail when not supposed to or
         | 
| 10 10 | 
             
                    # have a risk of not failing when expected to. One example is
         | 
| 11 11 | 
             
                    # `validate_presence_of :comment` as it leaves an empty comment after
         | 
| @@ -4,14 +4,12 @@ module RuboCop | |
| 4 4 | 
             
              module Cop
         | 
| 5 5 | 
             
                module RSpec
         | 
| 6 6 | 
             
                  class AggregateExamples
         | 
| 7 | 
            -
                    def self. | 
| 8 | 
            -
                       | 
| 7 | 
            +
                    def self.registry
         | 
| 8 | 
            +
                      RuboCop::Cop::Cop.registry
         | 
| 9 9 | 
             
                    end
         | 
| 10 10 | 
             
                  end
         | 
| 11 11 |  | 
| 12 12 | 
             
                  class AggregateFailures < AggregateExamples
         | 
| 13 | 
            -
                    raise "Remove me" if TestProf::VERSION >= "1.0"
         | 
| 14 | 
            -
             | 
| 15 13 | 
             
                    def initialize(*)
         | 
| 16 14 | 
             
                      super
         | 
| 17 15 | 
             
                      self.class.just_once { warn "`AggregateFailures` cop has been renamed to `AggregateExamples`." }
         | 
| @@ -4,13 +4,31 @@ module TestProf::EventProf | |
| 4 4 | 
             
              module Instrumentations
         | 
| 5 5 | 
             
                # Wrapper over ActiveSupport::Notifications
         | 
| 6 6 | 
             
                module ActiveSupport
         | 
| 7 | 
            +
                  class Subscriber
         | 
| 8 | 
            +
                    attr_reader :block, :started_at
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                    def initialize(block)
         | 
| 11 | 
            +
                      @block = block
         | 
| 12 | 
            +
                    end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                    def start(*)
         | 
| 15 | 
            +
                      @started_at = TestProf.now
         | 
| 16 | 
            +
                    end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                    def publish(_name, started_at, finished_at, *)
         | 
| 19 | 
            +
                      block.call(finished_at - started_at)
         | 
| 20 | 
            +
                    end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                    def finish(*)
         | 
| 23 | 
            +
                      block.call(TestProf.now - started_at)
         | 
| 24 | 
            +
                    end
         | 
| 25 | 
            +
                  end
         | 
| 26 | 
            +
             | 
| 7 27 | 
             
                  class << self
         | 
| 8 | 
            -
                    def subscribe(event)
         | 
| 28 | 
            +
                    def subscribe(event, &block)
         | 
| 9 29 | 
             
                      raise ArgumentError, "Block is required!" unless block_given?
         | 
| 10 30 |  | 
| 11 | 
            -
                      ::ActiveSupport::Notifications.subscribe(event | 
| 12 | 
            -
                        yield (finish - start)
         | 
| 13 | 
            -
                      end
         | 
| 31 | 
            +
                      ::ActiveSupport::Notifications.subscribe(event, Subscriber.new(block))
         | 
| 14 32 | 
             
                    end
         | 
| 15 33 |  | 
| 16 34 | 
             
                    def instrument(event)
         | 
| @@ -5,8 +5,8 @@ module TestProf | |
| 5 5 | 
             
              module ActiveRecord3Transactions
         | 
| 6 6 | 
             
                refine ::ActiveRecord::ConnectionAdapters::AbstractAdapter do
         | 
| 7 7 | 
             
                  def begin_transaction(joinable: true)
         | 
| 8 | 
            -
                    increment_open_transactions
         | 
| 9 8 | 
             
                    if open_transactions > 0
         | 
| 9 | 
            +
                      increment_open_transactions
         | 
| 10 10 | 
             
                      create_savepoint
         | 
| 11 11 | 
             
                    else
         | 
| 12 12 | 
             
                      begin_db_transaction
         | 
| @@ -35,18 +35,14 @@ module TestProf | |
| 35 35 | 
             
                      end
         | 
| 36 36 | 
             
                    end
         | 
| 37 37 | 
             
                  end
         | 
| 38 | 
            -
                end
         | 
| 39 38 |  | 
| 40 | 
            -
             | 
| 41 | 
            -
             | 
| 42 | 
            -
             | 
| 43 | 
            -
                     | 
| 44 | 
            -
             | 
| 45 | 
            -
                     | 
| 39 | 
            +
                  def call
         | 
| 40 | 
            +
                    if ENV["SAMPLE"]
         | 
| 41 | 
            +
                      ::TestProf::MinitestSample.sample_examples(ENV["SAMPLE"].to_i)
         | 
| 42 | 
            +
                    elsif ENV["SAMPLE_GROUPS"]
         | 
| 43 | 
            +
                      ::TestProf::MinitestSample.sample_groups(ENV["SAMPLE_GROUPS"].to_i)
         | 
| 44 | 
            +
                    end
         | 
| 46 45 | 
             
                  end
         | 
| 47 | 
            -
                  super
         | 
| 48 46 | 
             
                end
         | 
| 49 47 | 
             
              end
         | 
| 50 48 | 
             
            end
         | 
| 51 | 
            -
             | 
| 52 | 
            -
            Minitest.singleton_class.prepend(TestProf::MinitestSample)
         | 
| @@ -9,7 +9,7 @@ module TestProf | |
| 9 9 | 
             
                  def before_all(&block)
         | 
| 10 10 | 
             
                    raise ArgumentError, "Block is required!" unless block_given?
         | 
| 11 11 |  | 
| 12 | 
            -
                    return  | 
| 12 | 
            +
                    return before(:all, &block) if within_before_all?
         | 
| 13 13 |  | 
| 14 14 | 
             
                    @__before_all_activated__ = true
         | 
| 15 15 |  | 
| @@ -24,14 +24,6 @@ module TestProf | |
| 24 24 | 
             
                    end
         | 
| 25 25 | 
             
                  end
         | 
| 26 26 |  | 
| 27 | 
            -
                  def within_before_all(&block)
         | 
| 28 | 
            -
                    before(:all) do
         | 
| 29 | 
            -
                      BeforeAll.within_transaction do
         | 
| 30 | 
            -
                        instance_eval(&block)
         | 
| 31 | 
            -
                      end
         | 
| 32 | 
            -
                    end
         | 
| 33 | 
            -
                  end
         | 
| 34 | 
            -
             | 
| 35 27 | 
             
                  def within_before_all?
         | 
| 36 28 | 
             
                    instance_variable_defined?(:@__before_all_activated__)
         | 
| 37 29 | 
             
                  end
         | 
| @@ -22,6 +22,10 @@ module TestProf | |
| 22 22 |  | 
| 23 23 | 
             
                    LetItBe.modifiers[key] = block
         | 
| 24 24 | 
             
                  end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  def default_modifiers
         | 
| 27 | 
            +
                    @default_modifiers ||= {}
         | 
| 28 | 
            +
                  end
         | 
| 25 29 | 
             
                end
         | 
| 26 30 |  | 
| 27 31 | 
             
                class << self
         | 
| @@ -75,6 +79,8 @@ module TestProf | |
| 75 79 | 
             
                # And we love cats!)
         | 
| 76 80 | 
             
                PREFIX = RUBY_ENGINE == "jruby" ? "@__jruby_is_not_cat_friendly__" : "@😸"
         | 
| 77 81 |  | 
| 82 | 
            +
                FROZEN_ERROR_HINT = "\nIf you are using `let_it_be`, you may want to pass `reload: true` or `refind: true` modifier to it."
         | 
| 83 | 
            +
             | 
| 78 84 | 
             
                def self.define_let_it_be_alias(name, **default_args)
         | 
| 79 85 | 
             
                  define_method(name) do |identifier, **options, &blk|
         | 
| 80 86 | 
             
                    let_it_be(identifier, **default_args.merge(options), &blk)
         | 
| @@ -83,20 +89,20 @@ module TestProf | |
| 83 89 |  | 
| 84 90 | 
             
                def let_it_be(identifier, **options, &block)
         | 
| 85 91 | 
             
                  initializer = proc do
         | 
| 86 | 
            -
                    instance_variable_set(:"#{PREFIX}#{identifier}", instance_exec(&block))
         | 
| 92 | 
            +
                    instance_variable_set(:"#{TestProf::LetItBe::PREFIX}#{identifier}", instance_exec(&block))
         | 
| 93 | 
            +
                  rescue FrozenError => e
         | 
| 94 | 
            +
                    e.message << TestProf::LetItBe::FROZEN_ERROR_HINT
         | 
| 95 | 
            +
                    raise
         | 
| 87 96 | 
             
                  end
         | 
| 88 97 |  | 
| 89 | 
            -
                   | 
| 90 | 
            -
             | 
| 91 | 
            -
                  else
         | 
| 92 | 
            -
                    before_all(&initializer)
         | 
| 93 | 
            -
                  end
         | 
| 98 | 
            +
                  default_options = LetItBe.config.default_modifiers.dup
         | 
| 99 | 
            +
                  default_options.merge!(metadata[:let_it_be_modifiers]) if metadata[:let_it_be_modifiers]
         | 
| 94 100 |  | 
| 95 | 
            -
                   | 
| 96 | 
            -
             | 
| 101 | 
            +
                  options = default_options.merge(options)
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                  before_all(&initializer)
         | 
| 97 104 |  | 
| 98 | 
            -
             | 
| 99 | 
            -
                  let_accessor = LetItBe.wrap_with_modifiers(modifiers) do
         | 
| 105 | 
            +
                  let_accessor = LetItBe.wrap_with_modifiers(options) do
         | 
| 100 106 | 
             
                    instance_variable_get(:"#{PREFIX}#{identifier}")
         | 
| 101 107 | 
             
                  end
         | 
| 102 108 |  | 
| @@ -114,16 +120,78 @@ module TestProf | |
| 114 120 |  | 
| 115 121 | 
             
                  let(identifier, &let_accessor)
         | 
| 116 122 | 
             
                end
         | 
| 123 | 
            +
             | 
| 124 | 
            +
                module Freezer
         | 
| 125 | 
            +
                  # Stoplist to prevent freezing objects and theirs associations that are defined
         | 
| 126 | 
            +
                  # with `let_it_be`'s `freeze: false` options during deep freezing.
         | 
| 127 | 
            +
                  #
         | 
| 128 | 
            +
                  # To only keep track of objects that are available in current example group,
         | 
| 129 | 
            +
                  # `begin` adds a new layer, and `rollback` removes a layer of unrelated objects
         | 
| 130 | 
            +
                  # along with rolling back the transaction where they were created.
         | 
| 131 | 
            +
                  #
         | 
| 132 | 
            +
                  # Stoplist holds records declared with `freeze: false` (so we do not freeze them even if they're used as
         | 
| 133 | 
            +
                  # associated records for frozen objects)
         | 
| 134 | 
            +
                  module Stoplist
         | 
| 135 | 
            +
                    class << self
         | 
| 136 | 
            +
                      def stop?(record)
         | 
| 137 | 
            +
                        @stoplist.any? { |layer| layer.include?(record) }
         | 
| 138 | 
            +
                      end
         | 
| 139 | 
            +
             | 
| 140 | 
            +
                      def stop!(record)
         | 
| 141 | 
            +
                        @stoplist.last.push(record)
         | 
| 142 | 
            +
                      end
         | 
| 143 | 
            +
             | 
| 144 | 
            +
                      def begin
         | 
| 145 | 
            +
                        @stoplist.push([])
         | 
| 146 | 
            +
                      end
         | 
| 147 | 
            +
             | 
| 148 | 
            +
                      def rollback
         | 
| 149 | 
            +
                        @stoplist.pop
         | 
| 150 | 
            +
                      end
         | 
| 151 | 
            +
                    end
         | 
| 152 | 
            +
             | 
| 153 | 
            +
                    # Stack of example group-related variable definitions
         | 
| 154 | 
            +
                    @stoplist = []
         | 
| 155 | 
            +
                  end
         | 
| 156 | 
            +
             | 
| 157 | 
            +
                  class << self
         | 
| 158 | 
            +
                    # Rerucsively freezes the object to detect modifications
         | 
| 159 | 
            +
                    def deep_freeze(record)
         | 
| 160 | 
            +
                      return if record.frozen?
         | 
| 161 | 
            +
                      return if Stoplist.stop?(record)
         | 
| 162 | 
            +
             | 
| 163 | 
            +
                      record.freeze
         | 
| 164 | 
            +
             | 
| 165 | 
            +
                      # Support `let_it_be` with `create_list`
         | 
| 166 | 
            +
                      return record.each { |rec| deep_freeze(rec) } if record.respond_to?(:each)
         | 
| 167 | 
            +
             | 
| 168 | 
            +
                      # Freeze associations as well.
         | 
| 169 | 
            +
                      return unless defined?(::ActiveRecord::Base)
         | 
| 170 | 
            +
                      return unless record.is_a?(::ActiveRecord::Base)
         | 
| 171 | 
            +
             | 
| 172 | 
            +
                      record.class.reflections.keys.each do |reflection|
         | 
| 173 | 
            +
                        # But only if they are already loaded. If not yet loaded, they weren't
         | 
| 174 | 
            +
                        # created by factories, and it's ok to mutate them.
         | 
| 175 | 
            +
             | 
| 176 | 
            +
                        next unless record.association(reflection.to_sym).loaded?
         | 
| 177 | 
            +
             | 
| 178 | 
            +
                        target = record.association(reflection.to_sym).target
         | 
| 179 | 
            +
                        deep_freeze(target) if target.is_a?(::ActiveRecord::Base) || target.respond_to?(:each)
         | 
| 180 | 
            +
                      end
         | 
| 181 | 
            +
                    end
         | 
| 182 | 
            +
                  end
         | 
| 183 | 
            +
                end
         | 
| 117 184 | 
             
              end
         | 
| 118 185 | 
             
            end
         | 
| 119 186 |  | 
| 120 | 
            -
            if defined?(::ActiveRecord)
         | 
| 187 | 
            +
            if defined?(::ActiveRecord::Base)
         | 
| 121 188 | 
             
              require "test_prof/ext/active_record_refind"
         | 
| 122 189 | 
             
              using TestProf::Ext::ActiveRecordRefind
         | 
| 123 190 |  | 
| 124 191 | 
             
              TestProf::LetItBe.configure do |config|
         | 
| 125 192 | 
             
                config.register_modifier :reload do |record, val|
         | 
| 126 193 | 
             
                  next record unless val
         | 
| 194 | 
            +
             | 
| 127 195 | 
             
                  next record.reload if record.is_a?(::ActiveRecord::Base)
         | 
| 128 196 |  | 
| 129 197 | 
             
                  if record.respond_to?(:map)
         | 
| @@ -136,6 +204,7 @@ if defined?(::ActiveRecord) | |
| 136 204 |  | 
| 137 205 | 
             
                config.register_modifier :refind do |record, val|
         | 
| 138 206 | 
             
                  next record unless val
         | 
| 207 | 
            +
             | 
| 139 208 | 
             
                  next record.refind if record.is_a?(::ActiveRecord::Base)
         | 
| 140 209 |  | 
| 141 210 | 
             
                  if record.respond_to?(:map)
         | 
| @@ -145,7 +214,35 @@ if defined?(::ActiveRecord) | |
| 145 214 | 
             
                  end
         | 
| 146 215 | 
             
                  record
         | 
| 147 216 | 
             
                end
         | 
| 217 | 
            +
             | 
| 218 | 
            +
                config.register_modifier :freeze do |record, val|
         | 
| 219 | 
            +
                  if val == false
         | 
| 220 | 
            +
                    TestProf::LetItBe::Freezer::Stoplist.stop!(record)
         | 
| 221 | 
            +
                    next record
         | 
| 222 | 
            +
                  end
         | 
| 223 | 
            +
             | 
| 224 | 
            +
                  TestProf::LetItBe::Freezer.deep_freeze(record)
         | 
| 225 | 
            +
                  record
         | 
| 226 | 
            +
                end
         | 
| 148 227 | 
             
              end
         | 
| 149 228 | 
             
            end
         | 
| 150 229 |  | 
| 151 230 | 
             
            RSpec::Core::ExampleGroup.extend TestProf::LetItBe
         | 
| 231 | 
            +
             | 
| 232 | 
            +
            TestProf::BeforeAll.configure do |config|
         | 
| 233 | 
            +
              config.before(:begin) do
         | 
| 234 | 
            +
                TestProf::LetItBe::Freezer::Stoplist.begin
         | 
| 235 | 
            +
              end
         | 
| 236 | 
            +
             | 
| 237 | 
            +
              config.after(:rollback) do
         | 
| 238 | 
            +
                TestProf::LetItBe::Freezer::Stoplist.rollback
         | 
| 239 | 
            +
              end
         | 
| 240 | 
            +
            end
         | 
| 241 | 
            +
             | 
| 242 | 
            +
            RSpec.configure do |config|
         | 
| 243 | 
            +
              config.after(:example) do |example|
         | 
| 244 | 
            +
                if example.exception&.is_a?(FrozenError)
         | 
| 245 | 
            +
                  example.exception.message << TestProf::LetItBe::FROZEN_ERROR_HINT
         | 
| 246 | 
            +
                end
         | 
| 247 | 
            +
              end
         | 
| 248 | 
            +
            end
         |