test-prof 0.12.0 → 1.0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +155 -463
  3. data/README.md +9 -9
  4. data/config/default.yml +0 -15
  5. data/config/rubocop-rspec.yml +6 -0
  6. data/lib/test_prof/any_fixture/dump/base_adapter.rb +43 -0
  7. data/lib/test_prof/any_fixture/dump/digest.rb +29 -0
  8. data/lib/test_prof/any_fixture/dump/postgresql.rb +91 -0
  9. data/lib/test_prof/any_fixture/dump/sqlite.rb +42 -0
  10. data/lib/test_prof/any_fixture/dump.rb +212 -0
  11. data/lib/test_prof/any_fixture.rb +117 -8
  12. data/lib/test_prof/before_all/adapters/active_record.rb +15 -5
  13. data/lib/test_prof/before_all.rb +11 -2
  14. data/lib/test_prof/cops/rspec/aggregate_examples/its.rb +1 -1
  15. data/lib/test_prof/cops/rspec/aggregate_examples/line_range_helpers.rb +1 -1
  16. data/lib/test_prof/cops/rspec/aggregate_examples/matchers_with_side_effects.rb +1 -1
  17. data/lib/test_prof/cops/rspec/aggregate_examples/metadata_helpers.rb +1 -1
  18. data/lib/test_prof/cops/rspec/aggregate_examples/node_matchers.rb +1 -1
  19. data/lib/test_prof/cops/rspec/aggregate_examples.rb +1 -1
  20. data/lib/test_prof/event_prof/custom_events.rb +1 -1
  21. data/lib/test_prof/event_prof/instrumentations/active_support.rb +1 -1
  22. data/lib/test_prof/event_prof/profiler.rb +3 -4
  23. data/lib/test_prof/factory_prof/nate_heckler.rb +16 -0
  24. data/lib/test_prof/factory_prof/printers/flamegraph.rb +1 -1
  25. data/lib/test_prof/factory_prof/printers/nate_heckler.rb +26 -0
  26. data/lib/test_prof/factory_prof/printers/simple.rb +6 -2
  27. data/lib/test_prof/factory_prof.rb +24 -4
  28. data/lib/test_prof/logging.rb +18 -12
  29. data/lib/test_prof/recipes/logging.rb +1 -1
  30. data/lib/test_prof/recipes/minitest/before_all.rb +126 -24
  31. data/lib/test_prof/recipes/rspec/any_fixture.rb +1 -1
  32. data/lib/test_prof/recipes/rspec/before_all.rb +11 -3
  33. data/lib/test_prof/recipes/rspec/let_it_be.rb +6 -8
  34. data/lib/test_prof/rspec_dissect.rb +2 -2
  35. data/lib/test_prof/rspec_stamp.rb +1 -1
  36. data/lib/test_prof/rubocop.rb +0 -1
  37. data/lib/test_prof/ruby_prof.rb +11 -9
  38. data/lib/test_prof/tag_prof/result.rb +1 -1
  39. data/lib/test_prof/tag_prof/rspec.rb +3 -5
  40. data/lib/test_prof/utils/sized_ordered_set.rb +2 -2
  41. data/lib/test_prof/version.rb +1 -1
  42. data/lib/test_prof.rb +17 -4
  43. metadata +19 -14
  44. data/lib/test_prof/cops/rspec/aggregate_failures.rb +0 -26
  45. data/lib/test_prof/ext/active_record_3.rb +0 -27
  46. data/lib/test_prof/recipes/active_record_one_love.rb +0 -6
  47. data/lib/test_prof/recipes/active_record_shared_connection.rb +0 -77
@@ -1,10 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- if ::ActiveRecord::VERSION::MAJOR < 4
4
- require "test_prof/ext/active_record_3"
5
- using TestProf::ActiveRecord3Transactions
6
- end
7
-
8
3
  module TestProf
9
4
  module BeforeAll
10
5
  module Adapters
@@ -23,6 +18,21 @@ module TestProf
23
18
  end
24
19
  ::ActiveRecord::Base.connection.rollback_transaction
25
20
  end
21
+
22
+ def setup_fixtures(test_object)
23
+ test_object.instance_eval do
24
+ @@already_loaded_fixtures ||= {}
25
+ @fixture_cache ||= {}
26
+ config = ::ActiveRecord::Base
27
+
28
+ if @@already_loaded_fixtures[self.class]
29
+ @loaded_fixtures = @@already_loaded_fixtures[self.class]
30
+ else
31
+ @loaded_fixtures = load_fixtures(config)
32
+ @@already_loaded_fixtures[self.class] = @loaded_fixtures
33
+ end
34
+ end
35
+ end
26
36
  end
27
37
  end
28
38
  end
@@ -32,6 +32,12 @@ module TestProf
32
32
  end
33
33
  end
34
34
 
35
+ def setup_fixtures(test_object)
36
+ raise ArgumentError, "Current adapter doesn't support #setup_fixtures" unless adapter.respond_to?(:setup_fixtures)
37
+
38
+ adapter.setup_fixtures(test_object)
39
+ end
40
+
35
41
  def config
36
42
  @config ||= Configuration.new
37
43
  end
@@ -60,8 +66,11 @@ module TestProf
60
66
  class Configuration
61
67
  HOOKS = %i[begin rollback].freeze
62
68
 
69
+ attr_accessor :setup_fixtures
70
+
63
71
  def initialize
64
72
  @hooks = Hash.new { |h, k| h[k] = HooksChain.new(k) }
73
+ @setup_fixtures = false
65
74
  end
66
75
 
67
76
  # Add `before` hook for `begin` or
@@ -70,7 +79,7 @@ module TestProf
70
79
  # config.before(:rollback) { ... }
71
80
  def before(type, &block)
72
81
  validate_hook_type!(type)
73
- hooks[type].before << block if block_given?
82
+ hooks[type].before << block if block
74
83
  end
75
84
 
76
85
  # Add `after` hook for `begin` or
@@ -79,7 +88,7 @@ module TestProf
79
88
  # config.after(:begin) { ... }
80
89
  def after(type, &block)
81
90
  validate_hook_type!(type)
82
- hooks[type].after << block if block_given?
91
+ hooks[type].after << block if block
83
92
  end
84
93
 
85
94
  def run_hooks(type) # :nodoc:
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module RSpec
6
- class AggregateExamples < Cop
6
+ class AggregateExamples < ::RuboCop::Cop::Cop
7
7
  # @example `its`
8
8
  #
9
9
  # # Supports regular `its` call with an attribute/method name,
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module RSpec
6
- class AggregateExamples < Cop
6
+ class AggregateExamples < ::RuboCop::Cop::Cop
7
7
  # @internal Support methods for keeping newlines around examples.
8
8
  module LineRangeHelpers
9
9
  include RangeHelp
@@ -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
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module RSpec
6
- class AggregateExamples < Cop
6
+ class AggregateExamples < ::RuboCop::Cop::Cop
7
7
  # @internal
8
8
  # Support methods for example metadata.
9
9
  # Examples with similar metadata are grouped.
@@ -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
  # @internal
10
10
  # Node matchers and searchers.
11
11
  module NodeMatchers
@@ -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
@@ -6,7 +6,7 @@ module TestProf
6
6
  module CustomEvents
7
7
  class << self
8
8
  def register(event, &block)
9
- raise ArgumentError, "Block is required!" unless block_given?
9
+ raise ArgumentError, "Block is required!" unless block
10
10
  registrations[event] = block
11
11
  end
12
12
 
@@ -26,7 +26,7 @@ module TestProf::EventProf
26
26
 
27
27
  class << self
28
28
  def subscribe(event, &block)
29
- raise ArgumentError, "Block is required!" unless block_given?
29
+ raise ArgumentError, "Block is required!" unless block
30
30
 
31
31
  ::ActiveSupport::Notifications.subscribe(event, Subscriber.new(block))
32
32
  end
@@ -6,7 +6,7 @@ module TestProf
6
6
  attr_reader :event, :total_count, :total_time, :rank_by, :top_count, :per_example,
7
7
  :time, :count, :example_time, :example_count, :absolute_run_time
8
8
 
9
- alias per_example? per_example
9
+ alias_method :per_example?, :per_example
10
10
 
11
11
  def initialize(event:, instrumenter:, rank_by: :time, top_count: 5, per_example: false)
12
12
  @event = event
@@ -88,14 +88,13 @@ module TestProf
88
88
  end
89
89
 
90
90
  def results
91
- results = {
91
+ {
92
92
  groups: @groups.to_a
93
93
  }.tap do |data|
94
94
  next unless per_example?
95
95
 
96
96
  data[:examples] = @examples.to_a
97
97
  end
98
- results
99
98
  end
100
99
 
101
100
  def take_time(start_ts)
@@ -130,7 +129,7 @@ module TestProf
130
129
  end
131
130
 
132
131
  def each(&block)
133
- if block_given?
132
+ if block
134
133
  @profilers.each(&block)
135
134
  else
136
135
  @profilers.each
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_prof/factory_prof"
4
+
5
+ # A standalone Factory Prof printer which is meant to be always enabled
6
+
7
+ TestProf::FactoryProf.patch!
8
+
9
+ started_at = TestProf.now
10
+ at_exit do
11
+ TestProf::FactoryProf::Printers::NateHeckler.dump(
12
+ TestProf::FactoryProf.result, start_time: started_at
13
+ )
14
+ end
15
+
16
+ TestProf::FactoryProf.start
@@ -11,7 +11,7 @@ module TestProf::FactoryProf
11
11
  class << self
12
12
  include TestProf::Logging
13
13
 
14
- def dump(result)
14
+ def dump(result, **)
15
15
  return log(:info, "No factories detected") if result.raw_stats == {}
16
16
  report_data = {
17
17
  total_stacks: result.stacks.size,
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_prof/ext/float_duration"
4
+
5
+ module TestProf::FactoryProf
6
+ module Printers
7
+ # See https://twitter.com/nateberkopec/status/1389945187766456333
8
+ module NateHeckler # :nodoc: all
9
+ class << self
10
+ using TestProf::FloatDuration
11
+ include TestProf::Logging
12
+
13
+ def dump(result, start_time:)
14
+ return if result.raw_stats == {}
15
+
16
+ total_time = result.stats.sum { |stat| stat[:top_level_time] }
17
+ total_run_time = TestProf.now - start_time
18
+
19
+ percentage = ((total_time / total_run_time) * 100).round(2)
20
+
21
+ log :info, "Time spent in factories: #{total_time.duration} (#{percentage}% of total time)"
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -1,15 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "test_prof/ext/float_duration"
4
+
3
5
  module TestProf::FactoryProf
4
6
  module Printers
5
7
  module Simple # :nodoc: all
6
8
  class << self
9
+ using TestProf::FloatDuration
7
10
  include TestProf::Logging
8
11
 
9
- def dump(result)
12
+ def dump(result, start_time:)
10
13
  return log(:info, "No factories detected") if result.raw_stats == {}
11
14
  msgs = []
12
15
 
16
+ total_run_time = TestProf.now - start_time
13
17
  total_count = result.stats.sum { |stat| stat[:total_count] }
14
18
  total_top_level_count = result.stats.sum { |stat| stat[:top_level_count] }
15
19
  total_time = result.stats.sum { |stat| stat[:top_level_time] }
@@ -21,7 +25,7 @@ module TestProf::FactoryProf
21
25
 
22
26
  Total: #{total_count}
23
27
  Total top-level: #{total_top_level_count}
24
- Total time: #{format("%.4f", total_time)}s
28
+ Total time: #{total_time.duration} (out of #{total_run_time.duration})
25
29
  Total uniq factories: #{total_uniq_factories}
26
30
 
27
31
  total top-level total time time per call top-level time name
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "test_prof/factory_prof/printers/simple"
4
4
  require "test_prof/factory_prof/printers/flamegraph"
5
+ require "test_prof/factory_prof/printers/nate_heckler"
5
6
  require "test_prof/factory_prof/factory_builders/factory_bot"
6
7
  require "test_prof/factory_prof/factory_builders/fabrication"
7
8
 
@@ -10,14 +11,23 @@ module TestProf
10
11
  # flamegraphs or detect most popular factories
11
12
  module FactoryProf
12
13
  FACTORY_BUILDERS = [FactoryBuilders::FactoryBot,
13
- FactoryBuilders::Fabrication].freeze
14
+ FactoryBuilders::Fabrication].freeze
14
15
 
15
16
  # FactoryProf configuration
16
17
  class Configuration
17
- attr_accessor :mode
18
+ attr_accessor :mode, :printer
18
19
 
19
20
  def initialize
20
21
  @mode = ENV["FPROF"] == "flamegraph" ? :flamegraph : :simple
22
+ @printer =
23
+ case ENV["FPROF"]
24
+ when "flamegraph"
25
+ Printers::Flamegraph
26
+ when "nate_heckler"
27
+ Printers::NateHeckler
28
+ else
29
+ Printers::Simple
30
+ end
21
31
  end
22
32
 
23
33
  # Whether we want to generate flamegraphs
@@ -74,7 +84,15 @@ module TestProf
74
84
 
75
85
  log :info, "FactoryProf enabled (#{config.mode} mode)"
76
86
 
87
+ patch!
88
+ end
89
+
90
+ def patch!
91
+ return if @patched
92
+
77
93
  FACTORY_BUILDERS.each(&:patch)
94
+
95
+ @patched = true
78
96
  end
79
97
 
80
98
  # Inits FactoryProf and setups at exit hook,
@@ -82,9 +100,11 @@ module TestProf
82
100
  def run
83
101
  init
84
102
 
85
- printer = config.flamegraph? ? Printers::Flamegraph : Printers::Simple
103
+ printer = config.printer
104
+
105
+ started_at = TestProf.now
86
106
 
87
- at_exit { printer.dump(result) }
107
+ at_exit { printer.dump(result, start_time: started_at) }
88
108
 
89
109
  start
90
110
  end
@@ -4,23 +4,29 @@ module TestProf
4
4
  # Helper for output printing
5
5
  module Logging
6
6
  COLORS = {
7
- info: "\e[34m", # blue
8
- warn: "\e[33m", # yellow
9
- error: "\e[31m" # red
7
+ INFO: "\e[34m", # blue
8
+ WARN: "\e[33m", # yellow
9
+ ERROR: "\e[31m" # red
10
10
  }.freeze
11
11
 
12
- def log(level, msg)
13
- TestProf.config.output.puts(build_log_msg(level, msg))
14
- end
12
+ class Formatter
13
+ def call(severity, _time, progname, msg)
14
+ colorize(severity.to_sym, "[#{progname} #{severity}] #{msg}\n")
15
+ end
15
16
 
16
- def build_log_msg(level, msg)
17
- colorize(level, "[TEST PROF #{level.to_s.upcase}] #{msg}")
18
- end
17
+ private
18
+
19
+ def colorize(level, msg)
20
+ return msg unless TestProf.config.color?
19
21
 
20
- def colorize(level, msg)
21
- return msg unless TestProf.config.color?
22
+ return msg unless COLORS.key?(level)
22
23
 
23
- "#{COLORS[level]}#{msg}\e[0m"
24
+ "#{COLORS[level]}#{msg}\e[0m"
25
+ end
26
+ end
27
+
28
+ def log(level, msg)
29
+ TestProf.config.logger.public_send(level, "TEST PROF") { msg }
24
30
  end
25
31
  end
26
32
  end
@@ -12,7 +12,7 @@ module TestProf
12
12
  def logger
13
13
  return @logger if instance_variable_defined?(:@logger)
14
14
 
15
- @logger = Logger.new(STDOUT)
15
+ @logger = Logger.new($stdout)
16
16
  end
17
17
 
18
18
  def ar_loggables
@@ -2,77 +2,179 @@
2
2
 
3
3
  require "test_prof/before_all"
4
4
 
5
+ Minitest.singleton_class.prepend(Module.new do
6
+ attr_reader :previous_klass
7
+ @previous_klass = nil
8
+
9
+ def run_one_method(klass, method_name)
10
+ return super unless klass.respond_to?(:parallelized) && klass.parallelized
11
+
12
+ if @previous_klass && @previous_klass != klass
13
+ @previous_klass.before_all_executor&.deactivate!
14
+ end
15
+ @previous_klass = klass
16
+
17
+ super
18
+ end
19
+ end)
20
+
5
21
  module TestProf
6
22
  module BeforeAll
7
23
  # Add before_all hook to Minitest: wrap all examples into a transaction and
8
24
  # store instance variables
9
25
  module Minitest # :nodoc: all
10
26
  class Executor
11
- attr_reader :active
27
+ attr_reader :active, :block, :captured_ivars, :teardown_block, :current_test_object,
28
+ :setup_fixtures, :parent
12
29
 
13
- alias active? active
30
+ alias_method :active?, :active
31
+ alias_method :setup_fixtures?, :setup_fixtures
14
32
 
15
- def initialize(&block)
33
+ def initialize(setup_fixtures: false, parent: nil, &block)
34
+ @parent = parent
35
+ # Fixtures must be instantiated if any of the executors needs them
36
+ @setup_fixtures = setup_fixtures || parent&.setup_fixtures
16
37
  @block = block
38
+ @captured_ivars = []
39
+ end
40
+
41
+ def teardown(&block)
42
+ @teardown_block = block
17
43
  end
18
44
 
19
- def activate!(test_class)
20
- return if active?
45
+ def activate!(test_object)
46
+ @current_test_object = test_object
47
+
48
+ return restore_ivars(test_object) if active?
49
+
21
50
  @active = true
22
- @examples_left = test_class.runnable_methods.size
51
+
52
+ BeforeAll.setup_fixtures(test_object) if setup_fixtures?
23
53
  BeforeAll.begin_transaction do
24
- capture!
54
+ capture!(test_object)
25
55
  end
26
56
  end
27
57
 
28
- def try_deactivate!
29
- @examples_left -= 1
30
- return unless @examples_left.zero?
58
+ def deactivate!
59
+ return unless active
31
60
 
32
61
  @active = false
62
+
63
+ perform_teardown(current_test_object)
64
+
65
+ @current_test_object = nil
33
66
  BeforeAll.rollback_transaction
34
67
  end
35
68
 
36
- def capture!
37
- instance_eval(&@block)
69
+ def capture!(test_object)
70
+ before_ivars = test_object.instance_variables
71
+
72
+ perform_setup(test_object)
73
+
74
+ (test_object.instance_variables - before_ivars).each do |ivar|
75
+ captured_ivars << [ivar, test_object.instance_variable_get(ivar)]
76
+ end
38
77
  end
39
78
 
40
- def restore_to(test_object)
41
- instance_variables.each do |ivar|
42
- next if ivar == :@block
79
+ def restore_ivars(test_object)
80
+ captured_ivars.each do |(ivar, val)|
43
81
  test_object.instance_variable_set(
44
82
  ivar,
45
- instance_variable_get(ivar)
83
+ val
46
84
  )
47
85
  end
48
86
  end
87
+
88
+ def perform_setup(test_object)
89
+ parent&.perform_setup(test_object)
90
+ test_object.instance_eval(&block) if block
91
+ end
92
+
93
+ def perform_teardown(test_object)
94
+ current_test_object&.instance_eval(&teardown_block) if teardown_block
95
+ parent&.perform_teardown(test_object)
96
+ end
49
97
  end
50
98
 
51
99
  class << self
52
100
  def included(base)
53
101
  base.extend ClassMethods
102
+
103
+ base.cattr_accessor :parallelized
104
+ if base.respond_to?(:parallelize_teardown)
105
+ base.parallelize_teardown do
106
+ last_klass = ::Minitest.previous_klass
107
+ if last_klass&.respond_to?(:parallelized) && last_klass&.parallelized
108
+ last_klass.before_all_executor&.deactivate!
109
+ end
110
+ end
111
+ end
112
+
113
+ if base.respond_to?(:parallelize)
114
+ base.singleton_class.prepend(Module.new do
115
+ def parallelize(workers: :number_of_processors, with: :processes)
116
+ # super.parallelize returns nil when no parallelization is set up
117
+ if super(workers: workers, with: with).nil?
118
+ return
119
+ end
120
+
121
+ case with
122
+ when :processes
123
+ self.parallelized = true
124
+ when :threads
125
+ warn "!!! before_all is not implemented for parallalization with threads and " \
126
+ "could work incorrectly"
127
+ else
128
+ warn "!!! tests are using an unknown parallelization strategy and before_all " \
129
+ "could work incorrectly"
130
+ end
131
+ end
132
+ end)
133
+ end
54
134
  end
55
135
  end
56
136
 
57
137
  module ClassMethods
58
- attr_accessor :before_all_executor
138
+ attr_writer :before_all_executor
59
139
 
60
- def before_all(&block)
61
- self.before_all_executor = Executor.new(&block)
140
+ def before_all_executor
141
+ return @before_all_executor if instance_variable_defined?(:@before_all_executor)
142
+
143
+ @before_all_executor = if superclass.respond_to?(:before_all_executor)
144
+ superclass.before_all_executor
145
+ end
146
+ end
147
+
148
+ def before_all(setup_fixtures: BeforeAll.config.setup_fixtures, &block)
149
+ self.before_all_executor = Executor.new(
150
+ setup_fixtures: setup_fixtures,
151
+ parent: before_all_executor,
152
+ &block
153
+ )
154
+
155
+ # Do not add patches multiple times
156
+ return if before_all_executor.parent
62
157
 
63
158
  prepend(Module.new do
64
- def setup
65
- self.class.before_all_executor.activate!(self.class)
66
- self.class.before_all_executor.restore_to(self)
159
+ def before_setup
160
+ self.class.before_all_executor.activate!(self)
67
161
  super
68
162
  end
163
+ end)
69
164
 
70
- def teardown
165
+ singleton_class.prepend(Module.new do
166
+ def run(*)
71
167
  super
72
- self.class.before_all_executor.try_deactivate!
168
+ ensure
169
+ before_all_executor&.deactivate! unless parallelized
73
170
  end
74
171
  end)
75
172
  end
173
+
174
+ def after_all(&block)
175
+ self.before_all_executor = Executor.new(parent: before_all_executor)
176
+ before_all_executor.teardown(&block)
177
+ end
76
178
  end
77
179
  end
78
180
  end
@@ -15,7 +15,7 @@ RSpec.configure do |config|
15
15
  config.include_context "any_fixture:clean", with_clean_fixture: true
16
16
 
17
17
  config.after(:suite) do
18
- TestProf::AnyFixture.report_stats if TestProf::AnyFixture.reporting_enabled?
18
+ TestProf::AnyFixture.report_stats if TestProf::AnyFixture.config.reporting_enabled?
19
19
  TestProf::AnyFixture.reset
20
20
  end
21
21
  end
@@ -6,14 +6,22 @@ module TestProf
6
6
  module BeforeAll
7
7
  # Helper to wrap the whole example group into a transaction
8
8
  module RSpec
9
- def before_all(&block)
10
- raise ArgumentError, "Block is required!" unless block_given?
9
+ def before_all(setup_fixtures: BeforeAll.config.setup_fixtures, &block)
10
+ raise ArgumentError, "Block is required!" unless block
11
11
 
12
- return before(:all, &block) if within_before_all?
12
+ if within_before_all?
13
+ before(:all) do
14
+ @__inspect_output = "before_all hook"
15
+ instance_eval(&block)
16
+ end
17
+ return
18
+ end
13
19
 
14
20
  @__before_all_activated__ = true
15
21
 
16
22
  before(:all) do
23
+ @__inspect_output = "before_all hook"
24
+ BeforeAll.setup_fixtures(self) if setup_fixtures
17
25
  BeforeAll.begin_transaction do
18
26
  instance_eval(&block)
19
27
  end
@@ -55,9 +55,7 @@ module TestProf
55
55
  end
56
56
 
57
57
  def module_for(group)
58
- modules[group] ||= begin
59
- Module.new.tap { |mod| group.prepend(mod) }
60
- end
58
+ modules[group] ||= Module.new.tap { |mod| group.prepend(mod) }
61
59
  end
62
60
 
63
61
  private
@@ -91,8 +89,7 @@ module TestProf
91
89
  initializer = proc do
92
90
  instance_variable_set(:"#{TestProf::LetItBe::PREFIX}#{identifier}", instance_exec(&block))
93
91
  rescue FrozenError => e
94
- e.message << TestProf::LetItBe::FROZEN_ERROR_HINT
95
- raise
92
+ raise e.exception("#{e.message}#{TestProf::LetItBe::FROZEN_ERROR_HINT}")
96
93
  end
97
94
 
98
95
  default_options = LetItBe.config.default_modifiers.dup
@@ -108,8 +105,9 @@ module TestProf
108
105
 
109
106
  LetItBe.module_for(self).module_eval do
110
107
  define_method(identifier) do
111
- # Trying to detect the context (couldn't find other way so far)
112
- if /\(:context\)/.match?(@__inspect_output)
108
+ # Trying to detect the context
109
+ # Based on https://github.com/rspec/rspec-rails/commit/7cb796db064f58da7790a92e73ab906ef50b1f34
110
+ if /(before|after)\(:context\)/.match?(@__inspect_output) || @__inspect_output.include?("before_all")
113
111
  instance_variable_get(:"#{PREFIX}#{identifier}")
114
112
  else
115
113
  # Fallback to let definition
@@ -242,7 +240,7 @@ end
242
240
  RSpec.configure do |config|
243
241
  config.after(:example) do |example|
244
242
  if example.exception&.is_a?(FrozenError)
245
- example.exception.message << TestProf::LetItBe::FROZEN_ERROR_HINT
243
+ example.exception.message << TestProf::LetItBe::FROZEN_ERROR_HINT unless example.exception.message.frozen?
246
244
  end
247
245
  end
248
246
  end