test-prof 0.11.3 → 0.12.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9f94a3a078b73be780b867708b18572f4eec3d9847c733ddab65e04999895b14
4
- data.tar.gz: 5859b8f66524c27e07c96456bba4d44a3ba76a20eed7d987319ca287ee441392
3
+ metadata.gz: 64b1be0cdb0b04518ed2fb900756fd07d212f79709d76e784798384fb92a2a5f
4
+ data.tar.gz: fa59a7d025eda4e53ce0381cc9c973cbcad50e0c4584b1ca30aa5cfb1e9d0ebc
5
5
  SHA512:
6
- metadata.gz: 417495b0ccf198639e80396635922ad3802a622471304a93dd364bdca66ffbc09934badd74eb3e562e8524f141fa040e190068ab1f278d2ecc7575104889efb4
7
- data.tar.gz: e07278c5c32fbdd59fcd57a2e87785732503ea810fc21fc2222f4fdba5709dfa4c6794f7782c2498335eaf84516582f8cb5263ed302b12ac7f3137bbc3b4a3b4
6
+ metadata.gz: d08d874b6948df045adbdba66960abbb47e2fe8fd55700b8f9f4ab6a411f94ffd632d15937c30df36728709440be7209f21951a47b66f54c1ea39669aed35b72
7
+ data.tar.gz: 955199e5fce56fea5237bc07151526e83156ebd7f3f74f00a05eac5b7da20ad9b7ffe21807c8c7644327bc13020315d8262436e65f323057fd75be4378a89342
@@ -1,6 +1,41 @@
1
1
  # Change log
2
2
 
3
- ## master (unreleased)
3
+ ## 0.12.0 (2020-07-17)
4
+
5
+ - Add state leakage detection for `let_it_be`. ([@pirj][], [@jaimerson][], [@alexvko][])
6
+
7
+ - Add default let_it_be modifiers configuration. ([@palkan][])
8
+
9
+ You can configure global modifiers:
10
+
11
+ ```ruby
12
+ TestProf::LetItBe.configure do |config|
13
+ # Make refind activated by default
14
+ config.default_modifiers[:refind] = true
15
+ end
16
+ ```
17
+
18
+ Or for specific contexts via tags:
19
+
20
+ ```ruby
21
+ context "with let_it_be reload", let_it_be_modifiers: {reload: true} do
22
+ # examples
23
+ end
24
+ ```
25
+
26
+ - **Drop Ruby 2.4 support.** ([@palkan][])
27
+
28
+ - SAMPLE and SAMPLE_GROUP work consistently with seed in RSpec and Minitest. ([@stefkin][])
29
+
30
+ - Make sure EventProf is not affected by time freezing. ([@palkan][])
31
+
32
+ EventProf results now is not affected by `Timecop.freeze` or similar.
33
+
34
+ See more in [#181](https://github.com/palkan/test-prof/issues/181).
35
+
36
+ - Adds the ability to define stackprof's interval sampling by using `TEST_STACK_PROF_INTERVAL` env variable ([@LynxEyes][])
37
+
38
+ Now you can use `$ TEST_STACK_PROF=1 TEST_STACK_PROF_INTERVAL=10000 rspec` to define a custom interval (in microseconds).
4
39
 
5
40
  ## 0.11.3 (2020-02-11)
6
41
 
@@ -536,3 +571,7 @@ Fixes [#10](https://github.com/palkan/test-prof/issues/10).
536
571
  [@tyleriguchi]: https://github.com/tyleriguchi
537
572
  [@lostie]: https://github.com/lostie
538
573
  [@pirj]: https://github.com/pirj
574
+ [@LynxEyes]: https://github.com/LynxEyes
575
+ [@stefkin]: https://github.com/stefkin
576
+ [@jaimerson]: https://github.com/jaimerson
577
+ [@alexvko]: https://github.com/alexvko
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2017-2019 palkan
3
+ Copyright (c) 2017-2020 Vladimir Dementyev
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- [![Cult Of Martians](http://cultofmartians.com/assets/badges/badge.svg)](http://cultofmartians.com)
1
+ [![Cult Of Martians](http://cultofmartians.com/assets/badges/badge.svg)](https://cultofmartians.com)
2
2
  [![Gem Version](https://badge.fury.io/rb/test-prof.svg)](https://rubygems.org/gems/test-prof) [![Build](https://github.com/palkan/test-prof/workflows/Build/badge.svg)](https://github.com/palkan/test-prof/actions)
3
3
  [![JRuby Build](https://github.com/palkan/test-prof/workflows/JRuby%20Build/badge.svg)](https://github.com/palkan/test-prof/actions)
4
4
  [![Code Triagers Badge](https://www.codetriage.com/palkan/test-prof/badges/users.svg)](https://www.codetriage.com/palkan/test-prof)
@@ -47,7 +47,7 @@ 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)
@@ -104,7 +104,3 @@ Already using TestProf? [Share your story!](https://github.com/palkan/test-prof/
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.
@@ -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
@@ -24,10 +24,6 @@ module TestProf
24
24
  yield
25
25
  end
26
26
 
27
- def within_transaction
28
- yield
29
- end
30
-
31
27
  def rollback_transaction
32
28
  raise AdapterMissing if adapter.nil?
33
29
 
@@ -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#expectations-per-example
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.
@@ -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) do |_event, start, finish, *_args|
12
- yield (finish - start)
13
- end
31
+ ::ActiveSupport::Notifications.subscribe(event, Subscriber.new(block))
14
32
  end
15
33
 
16
34
  def instrument(event)
@@ -35,18 +35,14 @@ module TestProf
35
35
  end
36
36
  end
37
37
  end
38
- end
39
38
 
40
- # Overrides Minitest.run
41
- def run(*)
42
- if ENV["SAMPLE"]
43
- MinitestSample.sample_examples(ENV["SAMPLE"].to_i)
44
- elsif ENV["SAMPLE_GROUPS"]
45
- MinitestSample.sample_groups(ENV["SAMPLE_GROUPS"].to_i)
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 within_before_all(&block) if within_before_all?
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
- if within_before_all?
90
- within_before_all(&initializer)
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
- define_let_it_be_methods(identifier, **options)
96
- end
101
+ options = default_options.merge(options)
102
+
103
+ before_all(&initializer)
97
104
 
98
- def define_let_it_be_methods(identifier, **modifiers)
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
@@ -15,7 +15,8 @@ if ENV["SAMPLE"]
15
15
  RSpec.configure do |config|
16
16
  config.before(:suite) do
17
17
  filtered_examples = RSpec.world.filtered_examples.values.flatten
18
- sample = filtered_examples.sample(ENV["SAMPLE"].to_i)
18
+ random = Random.new(RSpec.configuration.seed)
19
+ sample = filtered_examples.sample(ENV["SAMPLE"].to_i, random: random)
19
20
  RSpec.world.filtered_examples = Hash.new do |hash, group|
20
21
  hash[group] = group.examples & sample
21
22
  end
@@ -31,7 +32,8 @@ if ENV["SAMPLE_GROUPS"]
31
32
  filtered_groups = RSpec.world.filtered_examples.reject do |_group, examples|
32
33
  examples.empty?
33
34
  end.keys
34
- sample = filtered_groups.sample(ENV["SAMPLE_GROUPS"].to_i)
35
+ random = Random.new(RSpec.configuration.seed)
36
+ sample = filtered_groups.sample(ENV["SAMPLE_GROUPS"].to_i, random: random)
35
37
  RSpec.world.filtered_examples = Hash.new do |hash, group|
36
38
  hash[group] = sample.include?(group) ? group.examples : []
37
39
  end
@@ -36,6 +36,9 @@ module TestProf
36
36
  else
37
37
  "html"
38
38
  end
39
+
40
+ sample_interval = ENV["TEST_STACK_PROF_INTERVAL"].to_i
41
+ @interval = sample_interval > 0 ? sample_interval : nil
39
42
  end
40
43
 
41
44
  def raw?
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TestProf
4
- VERSION = "0.11.3"
4
+ VERSION = "0.12.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: 0.11.3
4
+ version: 0.12.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: 2020-02-11 00:00:00.000000000 Z
11
+ date: 2020-07-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '12.0'
33
+ version: '13.0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '12.0'
40
+ version: '13.0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rspec
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -56,42 +56,42 @@ dependencies:
56
56
  name: isolator
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - "~>"
59
+ - - ">="
60
60
  - !ruby/object:Gem::Version
61
61
  version: '0.6'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - "~>"
66
+ - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0.6'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: minitest
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - "~>"
73
+ - - ">="
74
74
  - !ruby/object:Gem::Version
75
75
  version: '5.9'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - "~>"
80
+ - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '5.9'
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: rubocop
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - "~>"
87
+ - - ">="
88
88
  - !ruby/object:Gem::Version
89
89
  version: 0.77.0
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - "~>"
94
+ - - ">="
95
95
  - !ruby/object:Gem::Version
96
96
  version: 0.77.0
97
97
  description: "\n Ruby applications tests profiling tools.\n\n Contains tools
@@ -225,7 +225,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
225
225
  requirements:
226
226
  - - ">="
227
227
  - !ruby/object:Gem::Version
228
- version: 2.4.0
228
+ version: 2.5.0
229
229
  required_rubygems_version: !ruby/object:Gem::Requirement
230
230
  requirements:
231
231
  - - ">="