test-prof 1.0.10 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9d847a0016b2749e8ba8a637d2db3d0044f7b77cfc5962596ff490aea0f5fc7d
4
- data.tar.gz: 1f32af0e6e09b555bf258c5a52586a9d4a768578b0c816298343dfd5deb826da
3
+ metadata.gz: 9e19d20bbf0af4e016f9dabfe56e8074cce79d750a359555a2855b982298aa8b
4
+ data.tar.gz: 609607e479fabd363a6ee27e20b2f55845c9a5682fdec6352b5411b7c20e5ed5
5
5
  SHA512:
6
- metadata.gz: 55db394aef01457404c8f5cd914cfa0eb359f3bea08874c6d4a6b75b1151af5b4153087815c90ed73ea5948c403941918b75af0b79558afe795db77c4d2b2568
7
- data.tar.gz: d95cfbd7c513d086cd47b2ce0a804ec52fdf174854c0a07c239289c65d97d18833529321e0ffb4853aabc802f05a8a8eac4e3fcdc7011f0ffd1bf0c60d150b41
6
+ metadata.gz: a17fe83206dd28c8b632acc3cfeb79c1acdf8344fbb199f1a688f1e61ef9f11097f0de0cc04ba1d153d108c2e966622062ea4e9917c2e8083c921de7b5bba9b6
7
+ data.tar.gz: cdaf249b8072a916dad83d690d32d9264ff40c46c542f26374fd217324af99a04be61560753d37ee44a0298f05b71d1ef0616e62bf020035414c4ed28d51fab3
data/CHANGELOG.md CHANGED
@@ -2,6 +2,62 @@
2
2
 
3
3
  ## master (unreleased)
4
4
 
5
+ ## 1.1.0 (2022-12-06)
6
+
7
+ - LetItBe: freeze records during initialization with `freeze: true`. ([@palkan][])
8
+
9
+ - Add FactoryDefault profiler (factory associations profilers). ([@palkan][])
10
+
11
+ - FactoryDefault: Allow creating a default per trait (or set of traits). ([@palkan][])
12
+
13
+ Now `create_default(:user)` and `create_default(:user, :admin)` would result into two defaults corresponding to the specified traits.
14
+
15
+ - FactoryDefault: Add stats support. ([@palkan][])
16
+
17
+ Now you can see how often the default factory values have been used by specifying
18
+ the `FACTORY_DEFAULT_SUMMARY=1` or `FACTORY_DEFAULT_STATS=1` env var.
19
+
20
+ - Support using FactoryDefault with before_all/let_it_be. ([@palkan][])
21
+
22
+ Currently, RSpec only. Default factories created within `before_all` or `let_it_be` are not reset 'till the end of the corresponding context. Thus, now it's possible to use `create_default` within `let_it_be` without any additional hacks.
23
+
24
+ - FactoryDefault: Add `preserve_attributes = false | true` option. ([@palkan][])
25
+
26
+ Allow skipping defaults if association is defined with overrides, e.g.:
27
+
28
+ ```ruby
29
+ factory :post do
30
+ association :user, name: "Post Author"
31
+ end
32
+ ```
33
+
34
+ - FactoryDefault: Add `skip_factory_default(&block)` to temporary disable default factories. ([@palkan][])
35
+
36
+ You can also use `TestProf::FactoryDefault.disable!(&block)`.
37
+
38
+ - Add support for global `before_all` tags ([@maxshend][])
39
+
40
+ ```ruby
41
+ TestProf::BeforeAll.configure do |config|
42
+ config.before(:begin, reset_sequences: true, foo: :bar) do
43
+ warn <<~MESSAGE
44
+ Do NOT create objects outside of transaction
45
+ because all db sequences will be reset to 1
46
+ in every single example, so that IDs of new objects
47
+ can get into conflict with the long-living ones.
48
+ MESSAGE
49
+ end
50
+ end
51
+ ```
52
+
53
+ ## 1.0.11 (2022-10-27)
54
+
55
+ - Fix monitoring methods with keyword args in Ruby 3+. ([@palkan][])
56
+
57
+ - Disable garbage collection frames when `TEST_STACK_PROF_IGNORE_GC` env variable is set ([@cbliard][])
58
+
59
+ - Fixed restoring lock_thread value in nested contexts ([@ygelfand][])
60
+
5
61
  ## 1.0.10 (2022-08-12)
6
62
 
7
63
  - Allow overriding global logger. ([@palkan][])
@@ -294,3 +350,6 @@ See [changelog](https://github.com/test-prof/test-prof/blob/v0.8.0/CHANGELOG.md)
294
350
  [@grillermo]: https://github.com/grillermo
295
351
  [@cou929]: https://github.com/cou929
296
352
  [@ruslanshakirov]: https://github.com/ruslanshakirov
353
+ [@ygelfand]: https://github.com/ygelfand
354
+ [@cbliard]: https://github.com/cbliard
355
+ [@maxshend]: https://github.com/maxshend
@@ -47,7 +47,7 @@ module TestProf
47
47
  # might lead to leaking connections
48
48
  config.before(:begin) do
49
49
  next unless ::ActiveRecord::Base.connection.pool.respond_to?(:lock_thread=)
50
- instance_variable_set("#{PREFIX_RESTORE_LOCK_THREAD}_orig_lock_thread", ::ActiveRecord::Base.connection.pool.instance_variable_get(:@lock_thread))
50
+ instance_variable_set("#{PREFIX_RESTORE_LOCK_THREAD}_orig_lock_thread", ::ActiveRecord::Base.connection.pool.instance_variable_get(:@lock_thread)) unless instance_variable_defined? "#{PREFIX_RESTORE_LOCK_THREAD}_orig_lock_thread"
51
51
  ::ActiveRecord::Base.connection.pool.lock_thread = true
52
52
  end
53
53
 
@@ -17,19 +17,19 @@ module TestProf
17
17
  class << self
18
18
  attr_accessor :adapter
19
19
 
20
- def begin_transaction
20
+ def begin_transaction(scope = nil, metadata = [])
21
21
  raise AdapterMissing if adapter.nil?
22
22
 
23
- config.run_hooks(:begin) do
23
+ config.run_hooks(:begin, scope, metadata) do
24
24
  adapter.begin_transaction
25
25
  end
26
26
  yield
27
27
  end
28
28
 
29
- def rollback_transaction
29
+ def rollback_transaction(scope = nil, metadata = [])
30
30
  raise AdapterMissing if adapter.nil?
31
31
 
32
- config.run_hooks(:rollback) do
32
+ config.run_hooks(:rollback, scope, metadata) do
33
33
  adapter.rollback_transaction
34
34
  end
35
35
  end
@@ -49,6 +49,33 @@ module TestProf
49
49
  end
50
50
  end
51
51
 
52
+ class HookEntry # :nodoc:
53
+ attr_reader :filters, :block
54
+
55
+ def initialize(block:, filters: [])
56
+ @block = block
57
+ @filters = TestProf.rspec? ? ::RSpec::Core::Metadata.build_hash_from(filters) : filters
58
+ end
59
+
60
+ def run(scope, metadata)
61
+ return unless filters_apply?(metadata)
62
+
63
+ block.call(scope)
64
+ end
65
+
66
+ private
67
+
68
+ def filters_apply?(metadata)
69
+ return true unless filters.present? && TestProf.rspec?
70
+
71
+ ::RSpec::Core::MetadataFilter.apply?(
72
+ :all?,
73
+ filters,
74
+ metadata
75
+ )
76
+ end
77
+ end
78
+
52
79
  class HooksChain # :nodoc:
53
80
  attr_reader :type, :after, :before
54
81
 
@@ -58,10 +85,10 @@ module TestProf
58
85
  @after = []
59
86
  end
60
87
 
61
- def run
62
- before.each(&:call)
88
+ def run(scope = nil, metadata = [])
89
+ before.each { |hook| hook.run(scope, metadata) }
63
90
  yield
64
- after.each(&:call)
91
+ after.each { |hook| hook.run(scope, metadata) }
65
92
  end
66
93
  end
67
94
 
@@ -76,26 +103,26 @@ module TestProf
76
103
  end
77
104
 
78
105
  # Add `before` hook for `begin` or
79
- # `rollback` operation:
106
+ # `rollback` operation with optional filters:
80
107
  #
81
- # config.before(:rollback) { ... }
82
- def before(type, &block)
108
+ # config.before(:rollback, foo: :bar) { ... }
109
+ def before(type, *filters, &block)
83
110
  validate_hook_type!(type)
84
- hooks[type].before << block if block
111
+ hooks[type].before << HookEntry.new(block: block, filters: filters) if block
85
112
  end
86
113
 
87
114
  # Add `after` hook for `begin` or
88
- # `rollback` operation:
115
+ # `rollback` operation with optional filters:
89
116
  #
90
- # config.after(:begin) { ... }
91
- def after(type, &block)
117
+ # config.after(:begin, foo: :bar) { ... }
118
+ def after(type, *filters, &block)
92
119
  validate_hook_type!(type)
93
- hooks[type].after << block if block
120
+ hooks[type].after << HookEntry.new(block: block, filters: filters) if block
94
121
  end
95
122
 
96
- def run_hooks(type) # :nodoc:
123
+ def run_hooks(type, scope = nil, metadata = []) # :nodoc:
97
124
  validate_hook_type!(type)
98
- hooks[type].run { yield }
125
+ hooks[type].run(scope, metadata) { yield }
99
126
  end
100
127
 
101
128
  private
@@ -48,9 +48,16 @@ module TestProf
48
48
 
49
49
  patch = Module.new do
50
50
  mids.each do |mid|
51
- define_method(mid) do |*args, &block|
52
- next super(*args, &block) unless guard.nil? || instance_exec(*args, &guard)
53
- tracker.track { super(*args, &block) }
51
+ if RUBY_VERSION >= "2.7.0"
52
+ define_method(mid) do |*args, **kwargs, &block|
53
+ next super(*args, **kwargs, &block) unless guard.nil? || instance_exec(*args, **kwargs, &guard)
54
+ tracker.track { super(*args, **kwargs, &block) }
55
+ end
56
+ else
57
+ define_method(mid) do |*args, &block|
58
+ next super(*args, &block) unless guard.nil? || instance_exec(*args, &guard)
59
+ tracker.track { super(*args, &block) }
60
+ end
54
61
  end
55
62
  end
56
63
  end
@@ -8,7 +8,7 @@ require "test_prof/utils/sized_ordered_set"
8
8
 
9
9
  module TestProf
10
10
  # EventProf profiles your tests and suites against custom events,
11
- # such as ActiveSupport::Notifacations.
11
+ # such as ActiveSupport::Notifications.
12
12
  #
13
13
  # It works very similar to `rspec --profile` but can track arbitrary events.
14
14
  #
@@ -4,13 +4,7 @@ module TestProf
4
4
  module FactoryDefault # :nodoc: all
5
5
  module RunnerExt
6
6
  refine TestProf::FactoryBot::FactoryRunner do
7
- def name
8
- @name
9
- end
10
-
11
- def traits
12
- @traits
13
- end
7
+ attr_reader :name, :traits, :overrides
14
8
  end
15
9
  end
16
10
 
@@ -18,7 +12,8 @@ module TestProf
18
12
 
19
13
  module StrategyExt
20
14
  def association(runner)
21
- FactoryDefault.get(runner.name, runner.traits) || super
15
+ FactoryDefault.get(runner.name, runner.traits, runner.overrides) ||
16
+ FactoryDefault.profiler.instrument(runner.name, runner.traits, runner.overrides) { super }
22
17
  end
23
18
  end
24
19
  end
@@ -3,27 +3,198 @@
3
3
  require "test_prof"
4
4
  require "test_prof/factory_bot"
5
5
  require "test_prof/factory_default/factory_bot_patch"
6
+ require "test_prof/ext/float_duration"
7
+ require "test_prof/ext/active_record_refind" if defined?(::ActiveRecord::Base)
6
8
 
7
9
  module TestProf
8
10
  # FactoryDefault allows use to re-use associated objects
9
11
  # in factories implicilty
10
12
  module FactoryDefault
13
+ using FloatDuration
14
+ using Ext::ActiveRecordRefind if defined?(::ActiveRecord::Base)
15
+
16
+ using(Module.new do
17
+ refine Object do
18
+ def to_override_key
19
+ "<#{self.class.name}::$id$#{object_id}$di$>"
20
+ end
21
+
22
+ def refind
23
+ self
24
+ end
25
+ end
26
+
27
+ if defined?(::ActiveRecord::Base)
28
+ refine ::ActiveRecord::Base do
29
+ def to_override_key
30
+ "<#{self.class.name}\#$id$#{public_send(self.class.primary_key)}$di$>"
31
+ end
32
+ end
33
+ end
34
+
35
+ [
36
+ String,
37
+ Integer,
38
+ Float,
39
+ FalseClass,
40
+ TrueClass,
41
+ NilClass,
42
+ Regexp
43
+ ].each do |mod|
44
+ refine(mod) do
45
+ def to_override_key
46
+ inspect
47
+ end
48
+ end
49
+ end
50
+ end)
51
+
52
+ class Profiler
53
+ include Logging
54
+
55
+ attr_reader :data
56
+
57
+ def initialize
58
+ @data = Hash.new { |h, k| h[k] = {count: 0, time: 0.0} }
59
+ end
60
+
61
+ def instrument(name, traits, overrides)
62
+ start = TestProf.now
63
+ yield.tap do
64
+ time = TestProf.now - start
65
+ key = build_association_name(name, traits, overrides)
66
+ data[key][:count] += 1
67
+ data[key][:time] += time
68
+ end
69
+ end
70
+
71
+ def print_report
72
+ if data.empty?
73
+ log :info, "FactoryDefault profiler collected no data"
74
+ return
75
+ end
76
+
77
+ # Merge object overrides into one stats record
78
+ data = self.data.each_with_object({}) do |(name, stats), acc|
79
+ name = name.gsub(/\$id\$.+\$di\$/, "<id>")
80
+ if acc.key?(name)
81
+ acc[name][:count] += stats[:count]
82
+ acc[name][:time] += stats[:time]
83
+ else
84
+ acc[name] = stats
85
+ end
86
+ end
87
+
88
+ msgs = []
89
+
90
+ msgs <<
91
+ <<~MSG
92
+ Factory associations usage:
93
+ MSG
94
+
95
+ first_column = data.keys.map(&:size).max + 2
96
+
97
+ msgs << format(
98
+ "%#{first_column}s %9s %12s",
99
+ "factory", "count", "total time"
100
+ )
101
+
102
+ msgs << ""
103
+
104
+ total_count = 0
105
+ total_time = 0.0
106
+
107
+ data.to_a.sort_by { |(_, v)| -v[:time] }.each do |(key, factory_stats)|
108
+ total_count += factory_stats[:count]
109
+ total_time += factory_stats[:time]
110
+
111
+ msgs << format(
112
+ "%#{first_column}s %9d %12s",
113
+ key, factory_stats[:count], factory_stats[:time].duration
114
+ )
115
+ end
116
+
117
+ msgs <<
118
+ <<~MSG
119
+
120
+ Total associations created: #{total_count}
121
+ Total uniq associations created: #{data.size}
122
+ Total time spent: #{total_time.duration}
123
+
124
+ MSG
125
+
126
+ log :info, msgs.join("\n")
127
+ end
128
+
129
+ private
130
+
131
+ def build_association_name(name, traits, overrides)
132
+ traits_str = "[#{traits.join(",")}]" if traits&.any?
133
+ overrides_str = "{#{overrides.map { |k, v| "#{k}:#{v.to_override_key}" }.join(",")}}" if overrides&.any?
134
+ "#{name}#{traits_str}#{overrides_str}"
135
+ end
136
+ end
137
+
138
+ class NoopProfiler
139
+ def instrument(*)
140
+ yield
141
+ end
142
+
143
+ def print_report
144
+ end
145
+ end
146
+
11
147
  module DefaultSyntax # :nodoc:
12
148
  def create_default(name, *args, &block)
13
149
  options = args.extract_options!
14
- preserve = options.delete(:preserve_traits)
150
+ default_options = {}
151
+ default_options[:preserve_traits] = options.delete(:preserve_traits) if options.key?(:preserve_traits)
152
+ default_options[:preserve_attributes] = options.delete(:preserve_attributes) if options.key?(:preserve_attributes)
15
153
 
16
154
  obj = TestProf::FactoryBot.create(name, *args, options, &block)
17
- set_factory_default(name, obj, preserve_traits: preserve)
155
+
156
+ # Factory with traits
157
+ name = [name, *args] if args.any?
158
+
159
+ set_factory_default(name, obj, **default_options)
18
160
  end
19
161
 
20
- def set_factory_default(name, obj, preserve_traits: nil)
21
- FactoryDefault.register(name, obj, preserve_traits: preserve_traits)
162
+ def set_factory_default(name, obj, preserve_traits: FactoryDefault.config.preserve_traits, preserve_attributes: FactoryDefault.config.preserve_attributes, **other)
163
+ FactoryDefault.register(
164
+ name, obj,
165
+ preserve_traits: preserve_traits,
166
+ preserve_attributes: preserve_attributes,
167
+ **other
168
+ )
169
+ end
170
+
171
+ def skip_factory_default(&block)
172
+ FactoryDefault.disable!(&block)
173
+ end
174
+ end
175
+
176
+ class Configuration
177
+ attr_accessor :preserve_traits, :preserve_attributes,
178
+ :report_summary, :report_stats,
179
+ :profiling_enabled
180
+
181
+ alias_method :profiling_enabled?, :profiling_enabled
182
+
183
+ def initialize
184
+ # TODO(v2): Switch to true
185
+ @preserve_traits = false
186
+ @preserve_attributes = false
187
+ @profiling_enabled = ENV["FACTORY_DEFAULT_PROF"] == "1"
188
+ @report_summary = ENV["FACTORY_DEFAULT_SUMMARY"] == "1"
189
+ @report_stats = ENV["FACTORY_DEFAULT_STATS"] == "1"
22
190
  end
23
191
  end
24
192
 
25
193
  class << self
26
- attr_accessor :preserve_traits
194
+ include Logging
195
+
196
+ attr_accessor :current_context
197
+ attr_reader :stats, :profiler
27
198
 
28
199
  def init
29
200
  TestProf::FactoryBot::Syntax::Methods.include DefaultSyntax
@@ -32,38 +203,181 @@ module TestProf
32
203
  TestProf::FactoryBot::Strategy::Build.prepend StrategyExt
33
204
  TestProf::FactoryBot::Strategy::Stub.prepend StrategyExt
34
205
 
35
- # default is false to retain backward compatibility
36
- @preserve_traits = false
206
+ @profiler = config.profiling_enabled? ? Profiler.new : NoopProfiler.new
207
+ @enabled = ENV["FACTORY_DEFAULT_DISABLED"] != "1"
208
+ @stats = {}
209
+ end
210
+
211
+ def config
212
+ @config ||= Configuration.new
213
+ end
214
+
215
+ def configure
216
+ yield config
217
+ end
218
+
219
+ # TODO(v2): drop
220
+ def preserve_traits=(val)
221
+ config.preserve_traits = val
222
+ end
223
+
224
+ def preserve_attributes=(val)
225
+ config.preserve_attributes = val
37
226
  end
38
227
 
39
228
  def register(name, obj, **options)
40
- options[:preserve_traits] = true if FactoryDefault.preserve_traits
41
- store[name] = {object: obj, **options}
229
+ # Name with traits
230
+ if name.is_a?(Array)
231
+ register_traited_record(*name, obj, **options)
232
+ else
233
+ register_default_record(name, obj, **options)
234
+ end
235
+
42
236
  obj
43
237
  end
44
238
 
45
- def get(name, traits = nil)
239
+ def get(name, traits = nil, overrides = nil)
240
+ return unless enabled?
241
+
46
242
  record = store[name]
47
243
  return unless record
48
244
 
49
- if traits && !traits.empty?
50
- return if FactoryDefault.preserve_traits || record[:preserve_traits]
245
+ if traits && (trait_key = record[:traits][traits])
246
+ name = trait_key
247
+ record = store[name]
248
+ traits = nil
249
+ end
250
+
251
+ stats[name][:miss] += 1
252
+
253
+ if traits && !traits.empty? && record[:preserve_traits]
254
+ return
255
+ end
256
+
257
+ object = record[:object]
258
+
259
+ if overrides && !overrides.empty? && record[:preserve_attributes]
260
+ overrides.each do |name, value|
261
+ return unless object.respond_to?(name) # rubocop:disable Lint/NonLocalExitFromIterator
262
+ return if object.public_send(name) != value # rubocop:disable Lint/NonLocalExitFromIterator
263
+ end
264
+ end
265
+
266
+ stats[name][:miss] -= 1
267
+ stats[name][:hit] += 1
268
+
269
+ if record[:context] && (record[:context] != :example)
270
+ object.refind
271
+ else
272
+ object
51
273
  end
52
- record[:object]
53
274
  end
54
275
 
55
276
  def remove(name)
56
277
  store.delete(name)
57
278
  end
58
279
 
59
- def reset
60
- store.clear
280
+ def reset(context: nil)
281
+ return store.clear unless context
282
+
283
+ store.delete_if do |_name, metadata|
284
+ metadata[:context] == context
285
+ end
286
+ end
287
+
288
+ def enabled?
289
+ @enabled
290
+ end
291
+
292
+ def enable!
293
+ was_enabled = @enabled
294
+ @enabled = true
295
+ return unless block_given?
296
+ yield
297
+ ensure
298
+ @enabled = was_enabled
299
+ end
300
+
301
+ def disable!
302
+ was_enabled = @enabled
303
+ @enabled = false
304
+ return unless block_given?
305
+ yield
306
+ ensure
307
+ @enabled = was_enabled
308
+ end
309
+
310
+ def print_report
311
+ profiler.print_report
312
+ return unless config.report_stats || config.report_summary
313
+
314
+ if stats.empty?
315
+ log :info, "FactoryDefault has not been used"
316
+ return
317
+ end
318
+
319
+ msgs = []
320
+
321
+ if config.report_stats
322
+ msgs <<
323
+ <<~MSG
324
+ FactoryDefault usage stats:
325
+ MSG
326
+
327
+ first_column = stats.keys.map(&:size).max + 2
328
+
329
+ msgs << format(
330
+ "%#{first_column}s %9s %9s",
331
+ "factory", "hit", "miss"
332
+ )
333
+
334
+ msgs << ""
335
+ end
336
+
337
+ total_hit = 0
338
+ total_miss = 0
339
+
340
+ stats.to_a.sort_by { |(_, v)| -v[:hit] }.each do |(key, record_stats)|
341
+ total_hit += record_stats[:hit]
342
+ total_miss += record_stats[:miss]
343
+
344
+ if config.report_stats
345
+ msgs << format(
346
+ "%#{first_column}s %9d %9d",
347
+ key, record_stats[:hit], record_stats[:miss]
348
+ )
349
+ end
350
+ end
351
+
352
+ msgs << "" if config.report_stats
353
+
354
+ msgs <<
355
+ <<~MSG
356
+ FactoryDefault summary: hit=#{total_hit} miss=#{total_miss}
357
+ MSG
358
+
359
+ log :info, msgs.join("\n")
61
360
  end
62
361
 
63
362
  private
64
363
 
364
+ def register_default_record(name, obj, **options)
365
+ store[name] = {object: obj, traits: {}, context: current_context, **options}
366
+ stats[name] ||= {hit: 0, miss: 0}
367
+ end
368
+
369
+ def register_traited_record(name, *traits, obj, **options)
370
+ name_with_traits = "#{name}[#{traits.join(",")}]"
371
+
372
+ register_default_record(name_with_traits, obj, **options)
373
+ register_default_record(name, obj, **options) unless store[name]
374
+
375
+ # Add reference to the traited default to the original default record
376
+ store[name][:traits][traits] = name_with_traits
377
+ end
378
+
65
379
  def store
66
- Thread.current[:testprof_factory_store] ||= {}
380
+ Thread.current[:testprof_factory_default_store] ||= {}
67
381
  end
68
382
  end
69
383
  end
@@ -87,7 +87,7 @@ module Minitest
87
87
  private
88
88
 
89
89
  def pluralize_records(count)
90
- count == 1 ? "1 record" : "#{count} records"
90
+ (count == 1) ? "1 record" : "#{count} records"
91
91
  end
92
92
  end
93
93
  end
@@ -18,7 +18,7 @@ module TestProf
18
18
  attr_accessor :mode, :printer
19
19
 
20
20
  def initialize
21
- @mode = ENV["FPROF"] == "flamegraph" ? :flamegraph : :simple
21
+ @mode = (ENV["FPROF"] == "flamegraph") ? :flamegraph : :simple
22
22
  @printer =
23
23
  case ENV["FPROF"]
24
24
  when "flamegraph"
@@ -17,23 +17,24 @@ module TestProf
17
17
  return
18
18
  end
19
19
 
20
- @__before_all_activated__ = true
20
+ @__before_all_activation__ = context = self
21
+ current_metadata = metadata
21
22
 
22
23
  before(:all) do
23
24
  @__inspect_output = "before_all hook"
24
25
  BeforeAll.setup_fixtures(self) if setup_fixtures
25
- BeforeAll.begin_transaction do
26
+ BeforeAll.begin_transaction(context, current_metadata) do
26
27
  instance_eval(&block)
27
28
  end
28
29
  end
29
30
 
30
31
  after(:all) do
31
- BeforeAll.rollback_transaction
32
+ BeforeAll.rollback_transaction(context, current_metadata)
32
33
  end
33
34
  end
34
35
 
35
36
  def within_before_all?
36
- instance_variable_defined?(:@__before_all_activated__)
37
+ instance_variable_defined?(:@__before_all_activation__)
37
38
  end
38
39
  end
39
40
  end
@@ -4,6 +4,25 @@ require "test_prof/factory_default"
4
4
 
5
5
  TestProf::FactoryDefault.init
6
6
 
7
+ if defined?(TestProf::BeforeAll)
8
+ TestProf::BeforeAll.configure do |config|
9
+ config.before(:begin) do |context|
10
+ TestProf::FactoryDefault.current_context = context
11
+ end
12
+
13
+ config.after(:rollback) do |context|
14
+ TestProf::FactoryDefault.reset(context: context)
15
+ end
16
+ end
17
+ end
18
+
7
19
  RSpec.configure do |config|
8
- config.after(:each) { TestProf::FactoryDefault.reset }
20
+ if defined?(TestProf::BeforeAll)
21
+ config.before(:each) { TestProf::FactoryDefault.current_context = :example }
22
+ config.after(:each) { TestProf::FactoryDefault.reset(context: :example) }
23
+ else
24
+ config.after(:each) { TestProf::FactoryDefault.reset }
25
+ end
26
+
27
+ config.after(:suite) { TestProf::FactoryDefault.print_report }
9
28
  end
@@ -7,6 +7,12 @@ module TestProf
7
7
  # Just like `let`, but persist the result for the whole group.
8
8
  # NOTE: Experimental and magical, for more control use `before_all`.
9
9
  module LetItBe
10
+ Modifier = Struct.new(:scope, :block) do
11
+ def call(record, config)
12
+ block.call(record, config)
13
+ end
14
+ end
15
+
10
16
  class Configuration
11
17
  # Define an alias for `let_it_be` with the predefined options:
12
18
  #
@@ -17,10 +23,13 @@ module TestProf
17
23
  LetItBe.define_let_it_be_alias(name, **default_args)
18
24
  end
19
25
 
20
- def register_modifier(key, &block)
26
+ # Register modifier by providing the name of key,
27
+ # optional scope (when to apply the modifier, on initialization (:initialize)
28
+ # or when accessed # via let (:let))
29
+ def register_modifier(key, on: :let, &block)
21
30
  raise ArgumentError, "Modifier #{key} is already defined for let_it_be" if LetItBe.modifiers.key?(key)
22
31
 
23
- LetItBe.modifiers[key] = block
32
+ LetItBe.modifiers[key] = Modifier.new(on, block)
24
33
  end
25
34
 
26
35
  def default_modifiers
@@ -41,17 +50,18 @@ module TestProf
41
50
  @modifiers ||= {}
42
51
  end
43
52
 
44
- def wrap_with_modifiers(mods, &block)
53
+ def wrap_with_modifiers(mods, on: :let, &block)
54
+ mods = mods.select { |k, val| LetItBe.modifiers.fetch(k).scope == on }
45
55
  return block if mods.empty?
46
56
 
47
57
  validate_modifiers! mods
48
58
 
49
- -> {
59
+ proc do
50
60
  record = instance_eval(&block)
51
61
  mods.inject(record) do |rec, (k, v)|
52
62
  LetItBe.modifiers.fetch(k).call(rec, v)
53
63
  end
54
- }
64
+ end
55
65
  end
56
66
 
57
67
  def module_for(group)
@@ -97,9 +107,11 @@ module TestProf
97
107
 
98
108
  options = default_options.merge(options)
99
109
 
110
+ initializer = LetItBe.wrap_with_modifiers(options, on: :initialize, &initializer)
111
+
100
112
  before_all(&initializer)
101
113
 
102
- let_accessor = LetItBe.wrap_with_modifiers(options) do
114
+ let_accessor = LetItBe.wrap_with_modifiers(options, on: :let) do
103
115
  instance_variable_get(:"#{PREFIX}#{identifier}")
104
116
  end
105
117
 
@@ -192,7 +204,7 @@ if defined?(::ActiveRecord::Base)
192
204
 
193
205
  next record.reload if record.is_a?(::ActiveRecord::Base)
194
206
 
195
- if record.respond_to?(:map)
207
+ if record.respond_to?(:to_ary)
196
208
  next record.map do |rec|
197
209
  rec.is_a?(::ActiveRecord::Base) ? rec.reload : rec
198
210
  end
@@ -205,7 +217,7 @@ if defined?(::ActiveRecord::Base)
205
217
 
206
218
  next record.refind if record.is_a?(::ActiveRecord::Base)
207
219
 
208
- if record.respond_to?(:map)
220
+ if record.respond_to?(:to_ary)
209
221
  next record.map do |rec|
210
222
  rec.is_a?(::ActiveRecord::Base) ? rec.refind : rec
211
223
  end
@@ -213,7 +225,7 @@ if defined?(::ActiveRecord::Base)
213
225
  record
214
226
  end
215
227
 
216
- config.register_modifier :freeze do |record, val|
228
+ config.register_modifier :freeze, on: :initialize do |record, val|
217
229
  if val == false
218
230
  TestProf::LetItBe::Freezer::Stoplist.stop!(record)
219
231
  next record
@@ -47,7 +47,7 @@ module TestProf
47
47
  @let_top_count = (ENV["RD_PROF_LET_TOP"] || 3).to_i
48
48
  @top_count = (ENV["RD_PROF_TOP"] || 5).to_i
49
49
  @stamp = ENV["RD_PROF_STAMP"]
50
- @mode = ENV["RD_PROF"] == "1" ? "all" : ENV["RD_PROF"]
50
+ @mode = (ENV["RD_PROF"] == "1") ? "all" : ENV["RD_PROF"]
51
51
 
52
52
  unless MODES.include?(mode)
53
53
  raise "Unknown RSpecDissect mode: #{mode};" \
@@ -24,11 +24,10 @@ module TestProf
24
24
  class Configuration
25
25
  FORMATS = %w[html json].freeze
26
26
 
27
- attr_accessor :mode, :interval, :raw, :target, :format
28
-
27
+ attr_accessor :mode, :raw, :target, :format, :interval, :ignore_gc
29
28
  def initialize
30
29
  @mode = ENV.fetch("TEST_STACK_PROF_MODE", :wall).to_sym
31
- @target = ENV["TEST_STACK_PROF"] == "boot" ? :boot : :suite
30
+ @target = (ENV["TEST_STACK_PROF"] == "boot") ? :boot : :suite
32
31
  @raw = ENV["TEST_STACK_PROF_RAW"] != "0"
33
32
  @format =
34
33
  if FORMATS.include?(ENV["TEST_STACK_PROF_FORMAT"])
@@ -38,7 +37,8 @@ module TestProf
38
37
  end
39
38
 
40
39
  sample_interval = ENV["TEST_STACK_PROF_INTERVAL"].to_i
41
- @interval = sample_interval > 0 ? sample_interval : nil
40
+ @interval = (sample_interval > 0) ? sample_interval : nil
41
+ @ignore_gc = !ENV["TEST_STACK_PROF_IGNORE_GC"].nil?
42
42
  end
43
43
 
44
44
  def raw?
@@ -96,6 +96,7 @@ module TestProf
96
96
  }
97
97
 
98
98
  options[:interval] = config.interval if config.interval
99
+ options[:ignore_gc] = true if config.ignore_gc
99
100
 
100
101
  if block_given?
101
102
  options[:out] = build_path(name)
@@ -13,7 +13,7 @@ module TestProf
13
13
  attr_reader :result, :printer
14
14
 
15
15
  def initialize
16
- @printer = ENV["TAG_PROF_FORMAT"] == "html" ? Printers::HTML : Printers::Simple
16
+ @printer = (ENV["TAG_PROF_FORMAT"] == "html") ? Printers::HTML : Printers::Simple
17
17
 
18
18
  @result =
19
19
  if ENV["TAG_PROF_EVENT"].nil?
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TestProf
4
- VERSION = "1.0.10"
4
+ VERSION = "1.1.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: test-prof
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.10
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vladimir Dementyev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-08-12 00:00:00.000000000 Z
11
+ date: 2022-12-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler