rspec-benchmark 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +16 -0
- data/README.md +156 -24
- data/lib/rspec-benchmark.rb +1 -3
- data/lib/rspec/benchmark.rb +39 -0
- data/lib/rspec/benchmark/allocation_matcher.rb +164 -0
- data/lib/rspec/benchmark/complexity_matcher.rb +25 -5
- data/lib/rspec/benchmark/configuration.rb +40 -0
- data/lib/rspec/benchmark/matchers.rb +16 -0
- data/lib/rspec/benchmark/timing_matcher.rb +8 -2
- data/lib/rspec/benchmark/version.rb +1 -1
- data/rspec-benchmark.gemspec +15 -8
- data/spec/unit/configuration_spec.rb +84 -0
- data/spec/unit/perform_allocation_spec.rb +128 -0
- data/spec/unit/perform_exponential_spec.rb +1 -1
- data/spec/unit/perform_linear_spec.rb +11 -3
- data/spec/unit/perform_logarithmic_spec.rb +2 -2
- data/spec/unit/perform_under_spec.rb +4 -1
- metadata +44 -22
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 798bc5d4e03d43057fe769a612d69c7b9e94c1d6cf00cb119ff343f184c2dcc2
|
4
|
+
data.tar.gz: 437e723c5984d44d2af9e24dc2eab5a952d4be2531f86d1e5a67de6edfcd122c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 48a31057edbf78fc8f535110e9a99ccc1bf7a3f7e2ce044eb99cfb3dd1322fe75ce00996acec8b5759b15e9622ef39b6b5cfa4fb678055dda0a8cfd943d1e1cf
|
7
|
+
data.tar.gz: 755534b8baf828aad86a8659bd59c123a2056a3f8b169699db0de5e19468476637f83c093fbd12a1db153c24a29039dacbecd648c67aba7df351b4f15906c492
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,20 @@
|
|
1
1
|
# Change log
|
2
2
|
|
3
|
+
## [v0.5.0] - 2019-04-21
|
4
|
+
|
5
|
+
## Added
|
6
|
+
* Add benchmark-malloc as a dependency
|
7
|
+
* Add AllocationMatcher with #perform_allocation expectation
|
8
|
+
* Add #perform_log, #perform_exp aliases
|
9
|
+
* Add threshold matcher for specifying allowed error level when asserting computational complexity
|
10
|
+
* Add #configure to allow for global configuration of options
|
11
|
+
* Add :run_in_subprocess, :disable_gc & :samples configuration options
|
12
|
+
|
13
|
+
## Changed
|
14
|
+
* Change to require Ruby >= 2.1.0
|
15
|
+
* Change ComplexityMatcher to use threshold when verifying the assertion
|
16
|
+
* Change ComplexityMatcher#in_range to accept full range as an input
|
17
|
+
|
3
18
|
## [v0.4.0] - 2018-10-01
|
4
19
|
|
5
20
|
### Added
|
@@ -34,6 +49,7 @@
|
|
34
49
|
|
35
50
|
Initial release
|
36
51
|
|
52
|
+
[v0.5.0]: https://github.com/peter-murach/rspec-benchmark/compare/v0.4.0...v0.5.0
|
37
53
|
[v0.4.0]: https://github.com/peter-murach/rspec-benchmark/compare/v0.3.0...v0.4.0
|
38
54
|
[v0.3.0]: https://github.com/peter-murach/rspec-benchmark/compare/v0.2.0...v0.3.0
|
39
55
|
[v0.2.0]: https://github.com/peter-murach/rspec-benchmark/compare/v0.1.0...v0.2.0
|
data/README.md
CHANGED
@@ -14,15 +14,19 @@
|
|
14
14
|
[coverage]: https://coveralls.io/github/piotrmurach/rspec-benchmark
|
15
15
|
[inchpages]: http://inch-ci.org/github/piotrmurach/rspec-benchmark
|
16
16
|
|
17
|
-
> Performance testing matchers for RSpec
|
17
|
+
> Performance testing matchers for RSpec to set expectations on speed, resources usage and scalability.
|
18
18
|
|
19
|
-
**RSpec::Benchmark**
|
19
|
+
**RSpec::Benchmark** is powered by:
|
20
|
+
|
21
|
+
* [benchmark-perf](https://github.com/piotrmurach/benchmark-perf) for measuring execution time and iterations per second.
|
22
|
+
* [benchmark-trend](https://github.com/piotrmurach/benchmark-trend) for estimating computation complexity.
|
23
|
+
* [benchmark-malloc](https://github.com/piotrmurach/benchmark-malloc) for measuring object and memory allocations.
|
20
24
|
|
21
25
|
## Why?
|
22
26
|
|
23
27
|
Integration and unit tests ensure that changing code maintains expected functionality. What is not guaranteed is the code changes impact on library performance. It is easy to refactor your way out of fast to slow code.
|
24
28
|
|
25
|
-
If you are new to performance testing you may find [Caveats](#
|
29
|
+
If you are new to performance testing you may find [Caveats](#5-caveats) section helpful.
|
26
30
|
|
27
31
|
## Contents
|
28
32
|
|
@@ -31,9 +35,14 @@ If you are new to performance testing you may find [Caveats](#4-caveats) section
|
|
31
35
|
* [1.2 Iterations ](#12-iterations)
|
32
36
|
* [1.3 Comparison ](#13-comparison)
|
33
37
|
* [1.4 Complexity](#14-complexity)
|
38
|
+
* [1.5 Allocation](#15-allocation)
|
34
39
|
* [2. Compounding](#2-compounding)
|
35
|
-
* [3.
|
36
|
-
* [
|
40
|
+
* [3. Configuration](#3-configuration)
|
41
|
+
* [3.1 :disable_gc](#31-disable_gc)
|
42
|
+
* [3.2 :run_in_subprocess](#32-run_in_subprocess)
|
43
|
+
* [3.3 :samples](#33-samples)
|
44
|
+
* [4. Filtering](#4-filtering)
|
45
|
+
* [5. Caveats](#5-caveats)
|
37
46
|
|
38
47
|
## Installation
|
39
48
|
|
@@ -69,8 +78,9 @@ This will add the following matchers:
|
|
69
78
|
* `perform_at_least` to see how many iteration per second your code can do
|
70
79
|
* `perform_(faster|slower)_than` to compare implementations
|
71
80
|
* `perform_(constant|linear|logarithmic|power|exponential)` to see how your code scales with time
|
81
|
+
* `perform_allocation` to limit object and memory allocations
|
72
82
|
|
73
|
-
|
83
|
+
These will help you express expected performance benchmark for an evaluated code.
|
74
84
|
|
75
85
|
Alternatively, you can add matchers for particular example:
|
76
86
|
|
@@ -90,7 +100,7 @@ expect {
|
|
90
100
|
|
91
101
|
### 1.1 Timing
|
92
102
|
|
93
|
-
The `perform_under` matcher answers the question of how long does it take to perform a given block of code on average. The measurements are taken executing the block of code in a child process for
|
103
|
+
The `perform_under` matcher answers the question of how long does it take to perform a given block of code on average. The measurements are taken executing the block of code in a child process for accurate CPU times.
|
94
104
|
|
95
105
|
```ruby
|
96
106
|
expect { ... }.to perform_under(0.01).sec
|
@@ -103,7 +113,7 @@ expect { ... }.to perform_under(10).ms
|
|
103
113
|
expect { ... }.to perform_under(10000).us
|
104
114
|
```
|
105
115
|
|
106
|
-
|
116
|
+
By default the above code will be sampled only once but you can change this by using the `sample` matcher like so:
|
107
117
|
|
108
118
|
```ruby
|
109
119
|
expect { ... }.to perform_under(0.01).sample(10) # repeats measurements 10 times
|
@@ -133,7 +143,7 @@ expect { ... }.to perform_at_least(10000).ips
|
|
133
143
|
|
134
144
|
The `ips` part is optional but its usage clarifies the intent.
|
135
145
|
|
136
|
-
The performance
|
146
|
+
The performance timing of this matcher can be tweaked using the `within` and `warmup` matchers. These are expressed as seconds.
|
137
147
|
|
138
148
|
By default `within` matcher is set to `0.2` second and `warmup` matcher to `0.1` respectively. To change how long measurements are taken, for example, to double the time amount do:
|
139
149
|
|
@@ -170,7 +180,7 @@ expect { ... }.to perform_slower_than { ... }.exactly(5).times
|
|
170
180
|
|
171
181
|
The `times` part is also optional.
|
172
182
|
|
173
|
-
The performance
|
183
|
+
The performance timing of each matcher can be tweaked using the `within` and `warmup` matchers. These are expressed as seconds. By default `within` matcher is set to `0.2` and `warmup` matcher to `0.1` second respectively. To change these matchers values do:
|
174
184
|
|
175
185
|
```ruby
|
176
186
|
expect { ... }.to perform_faster_than.within(0.4).warmup(0.2) { ... }
|
@@ -180,47 +190,124 @@ The higher values for `within` and `warmup` the more accurate average readings a
|
|
180
190
|
|
181
191
|
### 1.4 Complexity
|
182
192
|
|
183
|
-
The `perform_constant`, `
|
193
|
+
The `perform_constant`, `perform_logarithmic`, `perform_linear`, `perform_power` and `perform_exponential` matchers are useful for estimating the asymptotic behaviour of a given block of code. The most basic way to use the expectations to test how your code scales is to use the matchers:
|
184
194
|
|
185
195
|
```ruby
|
186
196
|
expect { ... }.to perform_constant
|
197
|
+
expect { ... }.to perform_logarithmic/perform_log
|
187
198
|
expect { ... }.to perform_linear
|
188
|
-
expect { ... }.to perform_logarithmic
|
189
199
|
expect { ... }.to perform_power
|
190
|
-
expect { ... }.to perform_exponential
|
200
|
+
expect { ... }.to perform_exponential/perform_exp
|
191
201
|
```
|
192
202
|
|
193
|
-
|
203
|
+
To test performance in terms of computation complexity you can follow the algorithm:
|
204
|
+
|
205
|
+
1. Choose a method to profile.
|
206
|
+
2. Choose workloads for the method.
|
207
|
+
3. Describe workloads with input features.
|
208
|
+
4. Assert the performance in terms of Big-O notation.
|
209
|
+
|
210
|
+
Often, before expectation can be set you need to setup some workloads. To create a range of inputs use the `bench_range` helper method.
|
194
211
|
|
195
212
|
For example, to create a power range of inputs from `8` to `100_000` do:
|
196
213
|
|
214
|
+
```ruby
|
215
|
+
sizes = bench_range(8, 100_000) # => [8, 64, 512, 4096, 32768, 100000]
|
216
|
+
```
|
217
|
+
|
218
|
+
Then you can use the sizes to create test data, for example to check Ruby's `max` performance create array of number arrays.
|
219
|
+
|
220
|
+
```ruby
|
221
|
+
number_arrays = sizes.map { |n| Array.new(n) { rand(n) } }
|
222
|
+
```
|
223
|
+
|
224
|
+
Using `in_range` matcher you can inform the expectation about the inputs. Each range value together with its index will be yielded as arguments to the evaluated block.
|
225
|
+
|
226
|
+
You can either specify the range limits:
|
227
|
+
|
197
228
|
```ruby
|
198
229
|
expect { |n, i|
|
199
|
-
|
230
|
+
number_arrays[i].max
|
200
231
|
}.to perform_linear.in_range(8, 100_000)
|
201
232
|
```
|
202
233
|
|
234
|
+
Or use previously generated `sizes` array:
|
235
|
+
|
236
|
+
```ruby
|
237
|
+
expect { |n, i|
|
238
|
+
number_arrays[i].max
|
239
|
+
}.to perform_linear.in_range(sizes)
|
240
|
+
```
|
241
|
+
|
203
242
|
This example will generate and yield input `n` and index `i` pairs `[8, 0]`, `[64, 1]`, `[512, 2]`, `[4K, 3]`, `[32K, 4]` and `[100K, 5]` respectively.
|
204
243
|
|
205
|
-
By default the range will be generated using ratio of 8
|
244
|
+
By default the range will be generated using ratio of `8`. You can change this using `ratio` matcher:
|
206
245
|
|
207
246
|
```ruby
|
208
247
|
expect { |n, i|
|
209
|
-
|
248
|
+
number_arrays[i].max
|
210
249
|
}.to perform_linear.in_range(8, 100_000).ratio(2)
|
211
250
|
```
|
212
251
|
|
213
|
-
The performance
|
252
|
+
The performance measurements for a code block are taken only once per range input. You can increase the stability of your performance test by using the `sample` matcher. For example, to repeat measurements 100 times for each range input do:
|
214
253
|
|
215
254
|
```ruby
|
216
255
|
expect { |n, i|
|
217
|
-
|
256
|
+
number_arrays[i].max
|
218
257
|
}.to perform_linear.in_range(8, 100_000).ratio(2).sample(100).times
|
219
258
|
```
|
220
259
|
|
260
|
+
The overall quality of the performance trend is assessed using a threshold value where `0` means a poor fit and `1` a perfect fit. By default this value is configured to `0.9` as a 'good enough' threshold. To change this use `threshold` matcher:
|
261
|
+
|
262
|
+
```ruby
|
263
|
+
expect { |n, i|
|
264
|
+
number_arrays[i].max
|
265
|
+
}.to perform_linear.in_range(8, 100_000).threshold(0.95)
|
266
|
+
```
|
267
|
+
|
268
|
+
### 1.5 Allocation
|
269
|
+
|
270
|
+
The `perform_allocation` matcher checks how much memory or objects have been allocated during a piece of Ruby code execution.
|
271
|
+
|
272
|
+
By default the matcher verify the number of object allocations. You can also check for memory allocation using the `bytes` matcher.
|
273
|
+
|
274
|
+
To check number of objects allocated do:
|
275
|
+
|
276
|
+
```ruby
|
277
|
+
expect {
|
278
|
+
["foo", "bar", "baz"].sort[1]
|
279
|
+
}.to perform_allocation(3)
|
280
|
+
```
|
281
|
+
|
282
|
+
You can also be more granular with your object allocations and specify which object types you're interested in:
|
283
|
+
|
284
|
+
```ruby
|
285
|
+
expect {
|
286
|
+
_a = [Object.new]
|
287
|
+
_b = {Object.new => 'foo'}
|
288
|
+
}.to perform_allocation({Array => 1, Object => 2}).objects
|
289
|
+
```
|
290
|
+
|
291
|
+
And you can also check how many objects are left when expectation finishes to ensure that `GC` is able to collect them.
|
292
|
+
|
293
|
+
```ruby
|
294
|
+
expect {
|
295
|
+
["foo", "bar", "baz"].sort[1]
|
296
|
+
}.to perform_allocation(3).and_retain(3)
|
297
|
+
```
|
298
|
+
|
299
|
+
You can also set expectations on the memory size. In this case the memory size will serve as upper limit for the expectation:
|
300
|
+
|
301
|
+
```ruby
|
302
|
+
expect {
|
303
|
+
_a = [Object.new]
|
304
|
+
_b = {Object.new => 'foo'}
|
305
|
+
}.to perform_allocation({Array => 40, Hash => 384, Object => 80}).bytes
|
306
|
+
```
|
307
|
+
|
221
308
|
## 2. Compounding
|
222
309
|
|
223
|
-
All the matchers can be used in compound expressions via `and/or`. For example, if you wish to check if a computation performs under certain time
|
310
|
+
All the matchers can be used in compound expressions via `and/or`. For example, if you wish to check if a computation performs under certain time boundary and iterates at least a given number do:
|
224
311
|
|
225
312
|
```ruby
|
226
313
|
expect {
|
@@ -228,7 +315,48 @@ expect {
|
|
228
315
|
}.to perform_under(6).ms and perform_at_least(10000).ips
|
229
316
|
```
|
230
317
|
|
231
|
-
## 3.
|
318
|
+
## 3. Configuration
|
319
|
+
|
320
|
+
By default the following configuration is used:
|
321
|
+
|
322
|
+
```ruby
|
323
|
+
RSpec::Benchmark.configure do |config|
|
324
|
+
config.run_in_subprocess = false
|
325
|
+
config.disable_gc = false
|
326
|
+
end
|
327
|
+
```
|
328
|
+
|
329
|
+
### 3.1. `:disable_gc`
|
330
|
+
|
331
|
+
By default all tests are run with `GC` enabled. We want to measure real performance or Ruby code. However, disabling `GC` may lead to much quicker test execution. You can change this setting:
|
332
|
+
|
333
|
+
```ruby
|
334
|
+
RSpec::Benchmark.configure do |config|
|
335
|
+
config.disable_gc = true
|
336
|
+
end
|
337
|
+
```
|
338
|
+
|
339
|
+
### 3.2 `:run_in_subprocess`
|
340
|
+
|
341
|
+
The `perform_under` matcher can run all the measurements in the subprocess. This will increase isolation from other processes activity. However, the `rspec-rails` gem runs all tests in transactions. Unfortunately, when running tests in child process, database connections are used from connection pool and no data can be accessed. This is only a problem when running specs in Rails. Any other Ruby project can run specs using subprocesses. To enable this behaviour do:
|
342
|
+
|
343
|
+
```ruby
|
344
|
+
RSpec::Benchmark.configure do |config|
|
345
|
+
config.run_in_subprocess = true
|
346
|
+
end
|
347
|
+
```
|
348
|
+
|
349
|
+
### 3.3 `:samples`
|
350
|
+
|
351
|
+
The `perform_under` and computational complexity matchers allow to specify how many times to repeat measurements. You configure it globally for all matchers using the `:samples` option which defaults to `1`:
|
352
|
+
|
353
|
+
```ruby
|
354
|
+
RSpec::Benchmark.configure do |config|
|
355
|
+
config.samples = 10
|
356
|
+
end
|
357
|
+
```
|
358
|
+
|
359
|
+
## 4. Filtering
|
232
360
|
|
233
361
|
Usually performance tests are best left for CI or occasional runs that do not affect TDD/BDD cycle.
|
234
362
|
|
@@ -240,7 +368,7 @@ RSpec.config do |config|
|
|
240
368
|
end
|
241
369
|
```
|
242
370
|
|
243
|
-
|
371
|
+
And then in your example group do:
|
244
372
|
|
245
373
|
```ruby
|
246
374
|
RSpec.describe ..., :perf do
|
@@ -256,7 +384,7 @@ rspec --tag perf
|
|
256
384
|
|
257
385
|
Another option is to simply isolate the performance specs in separate directory such as `spec/performance/...` and add custom rake task to run them.
|
258
386
|
|
259
|
-
##
|
387
|
+
## 5. Caveats
|
260
388
|
|
261
389
|
When writing performance tests things to be mindful are:
|
262
390
|
|
@@ -275,6 +403,10 @@ If you have any other observations please share them!
|
|
275
403
|
4. Push to the branch (`git push origin my-new-feature`)
|
276
404
|
5. Create a new Pull Request
|
277
405
|
|
406
|
+
## Code of Conduct
|
407
|
+
|
408
|
+
Everyone interacting in the Strings project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/piotrmurach/rspec-benchmark/blob/master/CODE_OF_CONDUCT.md).
|
409
|
+
|
278
410
|
## Copyright
|
279
411
|
|
280
|
-
Copyright (c) 2016
|
412
|
+
Copyright (c) 2016 Piotr Murach. See LICENSE for further details.
|
data/lib/rspec-benchmark.rb
CHANGED
data/lib/rspec/benchmark.rb
CHANGED
@@ -1,4 +1,43 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'benchmark/configuration'
|
3
4
|
require_relative 'benchmark/matchers'
|
4
5
|
require_relative 'benchmark/version'
|
6
|
+
|
7
|
+
module RSpec
|
8
|
+
module Benchmark
|
9
|
+
class << self
|
10
|
+
attr_writer :configuration
|
11
|
+
end
|
12
|
+
|
13
|
+
# Current configuration
|
14
|
+
#
|
15
|
+
# @return [RSpec::Benchmark::Configuration]
|
16
|
+
#
|
17
|
+
# @api public
|
18
|
+
def self.configuration
|
19
|
+
@configuration ||= Configuration.new
|
20
|
+
end
|
21
|
+
|
22
|
+
# Reset current configuration to defaults
|
23
|
+
#
|
24
|
+
# @return [RSpec::Benchmark::Configuration]
|
25
|
+
#
|
26
|
+
# @api public
|
27
|
+
def self.reset_configuration
|
28
|
+
@configuration = Configuration.new
|
29
|
+
end
|
30
|
+
|
31
|
+
# Change current configuration
|
32
|
+
#
|
33
|
+
# @example
|
34
|
+
# RSpec::Benchmark.configure do |config|
|
35
|
+
# config.run_in_subprocess = false
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# @api public
|
39
|
+
def self.configure
|
40
|
+
yield configuration
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'benchmark-malloc'
|
4
|
+
|
5
|
+
module RSpec
|
6
|
+
module Benchmark
|
7
|
+
module AllocationMatcher
|
8
|
+
# Implements the `perform_allocation` matcher
|
9
|
+
#
|
10
|
+
# @api private
|
11
|
+
class Matcher
|
12
|
+
def initialize(objects, **options)
|
13
|
+
@objects = objects
|
14
|
+
@retained_objects = nil
|
15
|
+
@warmup = options.fetch(:warmup) { 1 }
|
16
|
+
@bench = ::Benchmark::Malloc
|
17
|
+
@count_type = :objects
|
18
|
+
end
|
19
|
+
|
20
|
+
# Indicates this matcher matches against a block
|
21
|
+
#
|
22
|
+
# @return [True]
|
23
|
+
#
|
24
|
+
# @api private
|
25
|
+
def supports_block_expectations?
|
26
|
+
true
|
27
|
+
end
|
28
|
+
|
29
|
+
# @return [Boolean]
|
30
|
+
#
|
31
|
+
# @api private
|
32
|
+
def matches?(block)
|
33
|
+
@block = block
|
34
|
+
alloc_stats = @bench.run(&block)
|
35
|
+
@actual = nil
|
36
|
+
@actual_retained = nil
|
37
|
+
|
38
|
+
case @objects
|
39
|
+
when Hash
|
40
|
+
case @count_type
|
41
|
+
when :memory
|
42
|
+
@actual = alloc_stats.allocated.count_memory
|
43
|
+
else
|
44
|
+
@actual = alloc_stats.allocated.count_objects
|
45
|
+
end
|
46
|
+
@objects.all? do |name, count|
|
47
|
+
@actual[name] <= count
|
48
|
+
end
|
49
|
+
when Numeric
|
50
|
+
case @count_type
|
51
|
+
when :memory
|
52
|
+
@actual = alloc_stats.allocated.total_memory
|
53
|
+
@actual_retained = alloc_stats.retained.total_memory
|
54
|
+
else
|
55
|
+
@actual = alloc_stats.allocated.total_objects
|
56
|
+
@actual_retained = alloc_stats.retained.total_objects
|
57
|
+
end
|
58
|
+
result = @actual <= @objects
|
59
|
+
result &= @actual_retained <= @retained_objects if @retained_objects
|
60
|
+
result
|
61
|
+
else
|
62
|
+
raise ArgumentError, "'#{@objects}' is not a recognized argument"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def and_retain(objects)
|
67
|
+
@retained_objects = objects
|
68
|
+
self
|
69
|
+
end
|
70
|
+
|
71
|
+
# The time before measurements are taken
|
72
|
+
#
|
73
|
+
# @param [Numeric] value
|
74
|
+
# the time before measurements are taken
|
75
|
+
#
|
76
|
+
# @api public
|
77
|
+
def warmup(value)
|
78
|
+
@warmup = value
|
79
|
+
self
|
80
|
+
end
|
81
|
+
|
82
|
+
def objects
|
83
|
+
@count_type = :objects
|
84
|
+
self
|
85
|
+
end
|
86
|
+
alias object objects
|
87
|
+
|
88
|
+
def memory
|
89
|
+
@count_type = :memory
|
90
|
+
self
|
91
|
+
end
|
92
|
+
alias bytes memory
|
93
|
+
|
94
|
+
def failure_message
|
95
|
+
"expected block to #{description}, but #{positive_failure_reason}"
|
96
|
+
end
|
97
|
+
|
98
|
+
def failure_message_when_negated
|
99
|
+
"expected block not to #{description}, but #{negative_failure_reason}"
|
100
|
+
end
|
101
|
+
|
102
|
+
def description
|
103
|
+
desc = ["perform allocation of #{count_objects(@objects)}"]
|
104
|
+
if @retained_objects
|
105
|
+
desc << " and retain #{count_objects(@retained_objects)}"
|
106
|
+
end
|
107
|
+
desc.join
|
108
|
+
end
|
109
|
+
|
110
|
+
def count_objects(objects)
|
111
|
+
if @count_type == :memory
|
112
|
+
"#{objects_to_s(objects)} #{objects == 1 ? "byte" : "bytes"}"
|
113
|
+
else
|
114
|
+
"#{objects_to_s(objects)} #{pluralize_objects(objects)}"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def positive_failure_reason
|
119
|
+
return "was not a block" unless @block.is_a?(Proc)
|
120
|
+
"allocated #{actual}"
|
121
|
+
end
|
122
|
+
|
123
|
+
def negative_failure_reason
|
124
|
+
"allocated #{actual}"
|
125
|
+
end
|
126
|
+
|
127
|
+
def actual
|
128
|
+
if @count_type == :memory
|
129
|
+
"#{objects_to_s(@actual)} bytes"
|
130
|
+
else
|
131
|
+
desc = ["#{objects_to_s(@actual)} #{pluralize_objects(@actual)}"]
|
132
|
+
if @retained_objects
|
133
|
+
desc << " and retained #{objects_to_s(@actual_retained)}"
|
134
|
+
end
|
135
|
+
desc.join
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def pluralize_objects(value)
|
140
|
+
if value.respond_to?(:to_hash)
|
141
|
+
if value.keys.size == 1 && value.values.reduce(&:+) == 1
|
142
|
+
"object"
|
143
|
+
else
|
144
|
+
"objects"
|
145
|
+
end
|
146
|
+
else
|
147
|
+
value == 1 ? "object" : "objects"
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def objects_to_s(value)
|
152
|
+
if value.respond_to?(:to_hash)
|
153
|
+
value.
|
154
|
+
sort_by { |k,v| k.to_s }.
|
155
|
+
map { |key, val| "#{val} #{key}" if @objects.keys.include?(key) }.
|
156
|
+
compact.join(" and ")
|
157
|
+
else
|
158
|
+
value
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end # AllocationMatcher
|
163
|
+
end # Benchmark
|
164
|
+
end # RSpec
|
@@ -11,8 +11,10 @@ module RSpec
|
|
11
11
|
class Matcher
|
12
12
|
def initialize(fit_type, **options)
|
13
13
|
@fit_type = fit_type
|
14
|
-
@threshold = options.fetch(:threshold) {
|
15
|
-
|
14
|
+
@threshold = options.fetch(:threshold) {
|
15
|
+
RSpec::Benchmark.configuration.fit_quality }
|
16
|
+
@repeat = options.fetch(:repeat) {
|
17
|
+
RSpec::Benchmark.configuration.samples }
|
16
18
|
@start = 8
|
17
19
|
@limit = 8 << 10
|
18
20
|
@ratio = 8
|
@@ -37,12 +39,30 @@ module RSpec
|
|
37
39
|
def matches?(block)
|
38
40
|
range = ::Benchmark::Trend.range(@start, @limit, ratio: @ratio)
|
39
41
|
@trend, trends = ::Benchmark::Trend.infer_trend(range, repeat: @repeat, &block)
|
42
|
+
threshold = trends[@trend][:residual]
|
40
43
|
|
41
|
-
@trend == @fit_type
|
44
|
+
@trend == @fit_type && threshold >= @threshold
|
42
45
|
end
|
43
46
|
|
44
|
-
|
45
|
-
|
47
|
+
# Specify range of inputs
|
48
|
+
#
|
49
|
+
# @api public
|
50
|
+
def in_range(start, limit = (not_set = true))
|
51
|
+
case start
|
52
|
+
when Array
|
53
|
+
@start, *, @limit = *start
|
54
|
+
@ratio = start[1] / start[0]
|
55
|
+
when Numeric
|
56
|
+
@start, @limit = start, limit
|
57
|
+
else
|
58
|
+
raise ArgumentError,
|
59
|
+
"Wrong range argument '#{start}', it expects an array or numeric start value."
|
60
|
+
end
|
61
|
+
self
|
62
|
+
end
|
63
|
+
|
64
|
+
def threshold(threshold)
|
65
|
+
@threshold = threshold
|
46
66
|
self
|
47
67
|
end
|
48
68
|
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RSpec
|
4
|
+
module Benchmark
|
5
|
+
class Configuration
|
6
|
+
# Isolate benchmark time measurement in child process
|
7
|
+
# By default false due to Rails loosing DB connections
|
8
|
+
#
|
9
|
+
# @api public
|
10
|
+
attr_accessor :run_in_subprocess
|
11
|
+
|
12
|
+
# GC is enabled to measure real performance
|
13
|
+
#
|
14
|
+
# @api public
|
15
|
+
attr_accessor :disable_gc
|
16
|
+
|
17
|
+
# How many times to repeat measurements
|
18
|
+
#
|
19
|
+
# @return [Integer]
|
20
|
+
#
|
21
|
+
# @api public
|
22
|
+
attr_accessor :samples
|
23
|
+
|
24
|
+
# The fit quality in computational complexity
|
25
|
+
#
|
26
|
+
# @return [Float]
|
27
|
+
#
|
28
|
+
# @api public
|
29
|
+
attr_accessor :fit_quality
|
30
|
+
|
31
|
+
# @api private
|
32
|
+
def initialize
|
33
|
+
@disable_gc = false
|
34
|
+
@samples = 1
|
35
|
+
@fit_quality = 0.9
|
36
|
+
@run_in_subprocess = false
|
37
|
+
end
|
38
|
+
end # Configuration
|
39
|
+
end # Benchmark
|
40
|
+
end # RSpec
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal
|
2
2
|
|
3
|
+
require_relative 'allocation_matcher'
|
3
4
|
require_relative 'comparison_matcher'
|
4
5
|
require_relative 'complexity_matcher'
|
5
6
|
require_relative 'iteration_matcher'
|
@@ -24,6 +25,19 @@ module RSpec
|
|
24
25
|
#
|
25
26
|
# @api public
|
26
27
|
module Matchers
|
28
|
+
# Passes if code block performs at least iterations
|
29
|
+
#
|
30
|
+
# @param [Integer] iterations
|
31
|
+
#
|
32
|
+
# @example
|
33
|
+
# expect { ... }.to perform_allocation(10000)
|
34
|
+
# expect { ... }.to perform_allocation(10000)
|
35
|
+
#
|
36
|
+
# @api public
|
37
|
+
def perform_allocation(objects, **options)
|
38
|
+
AllocationMatcher::Matcher.new(objects, options)
|
39
|
+
end
|
40
|
+
|
27
41
|
# Passes if code block performs at least iterations
|
28
42
|
#
|
29
43
|
# @param [Integer] iterations
|
@@ -101,6 +115,7 @@ module RSpec
|
|
101
115
|
def perform_logarithmic(**options)
|
102
116
|
ComplexityMatcher::Matcher.new(:logarithmic, options)
|
103
117
|
end
|
118
|
+
alias perform_log perform_logarithmic
|
104
119
|
|
105
120
|
# Pass if code block performs linear
|
106
121
|
#
|
@@ -137,6 +152,7 @@ module RSpec
|
|
137
152
|
def perform_exponential(**options)
|
138
153
|
ComplexityMatcher::Matcher.new(:exponential, options)
|
139
154
|
end
|
155
|
+
alias perform_exp perform_exponential
|
140
156
|
|
141
157
|
# Generate a geometric progression of inputs
|
142
158
|
#
|
@@ -17,8 +17,11 @@ module RSpec
|
|
17
17
|
|
18
18
|
def initialize(threshold, **options)
|
19
19
|
@threshold = threshold
|
20
|
-
@samples = options.fetch(:samples) {
|
20
|
+
@samples = options.fetch(:samples) {
|
21
|
+
RSpec::Benchmark.configuration.samples }
|
21
22
|
@warmup = options.fetch(:warmup) { 1 }
|
23
|
+
@subprocess = options.fetch(:subprocess) {
|
24
|
+
RSpec::Benchmark.configuration.run_in_subprocess }
|
22
25
|
@scale = threshold.to_s.split(/\./).last.size
|
23
26
|
@block = nil
|
24
27
|
@bench = ::Benchmark::Perf::ExecutionTime
|
@@ -39,7 +42,10 @@ module RSpec
|
|
39
42
|
def matches?(block)
|
40
43
|
@block = block
|
41
44
|
return false unless block.is_a?(Proc)
|
42
|
-
@average, @stddev = @bench.run(repeat: @samples,
|
45
|
+
@average, @stddev = @bench.run(repeat: @samples,
|
46
|
+
warmup: @warmup,
|
47
|
+
subprocess: @subprocess,
|
48
|
+
&block)
|
43
49
|
@average <= @threshold
|
44
50
|
end
|
45
51
|
|
data/rspec-benchmark.gemspec
CHANGED
@@ -6,12 +6,18 @@ Gem::Specification.new do |spec|
|
|
6
6
|
spec.name = "rspec-benchmark"
|
7
7
|
spec.version = RSpec::Benchmark::VERSION
|
8
8
|
spec.authors = ["Piotr Murach"]
|
9
|
-
spec.email = [""]
|
9
|
+
spec.email = ["me@piotrmurach.com"]
|
10
10
|
spec.summary = %q{Performance testing matchers for RSpec}
|
11
|
-
spec.description = %q{Performance testing matchers for RSpec
|
12
|
-
spec.homepage = ""
|
11
|
+
spec.description = %q{Performance testing matchers for RSpec to set expectations on speed, resources usage and scalibility.}
|
12
|
+
spec.homepage = "https://github.com/piotrmurach/rspec-benchmark"
|
13
13
|
spec.license = "MIT"
|
14
14
|
|
15
|
+
if spec.respond_to?(:metadata)
|
16
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
17
|
+
spec.metadata["source_code_uri"] = "https://github.com/piotrmurach/rspec-benchmark"
|
18
|
+
spec.metadata["changelog_uri"] = "https://github.com/piotrmurach/rspec-benchmark/blob/master/CHANGELOG.md"
|
19
|
+
end
|
20
|
+
|
15
21
|
spec.files = Dir['{lib,spec}/**/*.rb']
|
16
22
|
spec.files += Dir['tasks/*', 'rspec-benchmark.gemspec']
|
17
23
|
spec.files += Dir['README.md', 'CHANGELOG.md', 'LICENSE.txt', 'Rakefile']
|
@@ -19,12 +25,13 @@ Gem::Specification.new do |spec|
|
|
19
25
|
spec.test_files = spec.files.grep(%r{^spec/})
|
20
26
|
spec.require_paths = ["lib"]
|
21
27
|
|
22
|
-
spec.required_ruby_version = '>= 2.
|
28
|
+
spec.required_ruby_version = '>= 2.1.0'
|
23
29
|
|
24
|
-
spec.add_dependency 'benchmark-
|
25
|
-
spec.add_dependency 'benchmark-
|
30
|
+
spec.add_dependency 'benchmark-malloc', '~> 0.1.0'
|
31
|
+
spec.add_dependency 'benchmark-perf', '~> 0.5.0'
|
32
|
+
spec.add_dependency 'benchmark-trend', '~> 0.3.0'
|
26
33
|
spec.add_dependency 'rspec', '>= 3.0.0', '< 4.0.0'
|
27
34
|
|
28
|
-
spec.add_development_dependency 'bundler', '
|
29
|
-
spec.add_development_dependency 'rake'
|
35
|
+
spec.add_development_dependency 'bundler', '>= 1.5'
|
36
|
+
spec.add_development_dependency 'rake'
|
30
37
|
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.describe RSpec::Benchmark do
|
4
|
+
after(:example) do
|
5
|
+
RSpec::Benchmark.reset_configuration
|
6
|
+
end
|
7
|
+
|
8
|
+
it "defaults :run_in_subprocess option to false" do
|
9
|
+
config = RSpec::Benchmark.configuration
|
10
|
+
|
11
|
+
expect(config.run_in_subprocess).to eq(false)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "defaults :disable_gc option to false" do
|
15
|
+
config = RSpec::Benchmark.configuration
|
16
|
+
|
17
|
+
expect(config.disable_gc).to eq(false)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "defaults :samples option to 1" do
|
21
|
+
config = RSpec::Benchmark.configuration
|
22
|
+
|
23
|
+
expect(config.samples).to eq(1)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "sets :run_in_subprocess option to true" do
|
27
|
+
RSpec::Benchmark.configure do |config|
|
28
|
+
config.run_in_subprocess = true
|
29
|
+
end
|
30
|
+
|
31
|
+
config = RSpec::Benchmark.configuration
|
32
|
+
|
33
|
+
expect(config.run_in_subprocess).to eq(true)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "sets :disable_gc option to true" do
|
37
|
+
RSpec::Benchmark.configure do |config|
|
38
|
+
config.disable_gc = true
|
39
|
+
end
|
40
|
+
|
41
|
+
config = RSpec::Benchmark.configuration
|
42
|
+
|
43
|
+
expect(config.disable_gc).to eq(true)
|
44
|
+
end
|
45
|
+
|
46
|
+
it "sets :samples option to 10" do
|
47
|
+
RSpec::Benchmark.configure do |config|
|
48
|
+
config.samples = 10
|
49
|
+
end
|
50
|
+
|
51
|
+
config = RSpec::Benchmark.configuration
|
52
|
+
|
53
|
+
expect(config.samples).to eq(10)
|
54
|
+
end
|
55
|
+
|
56
|
+
it "uses the :run_in_subprocess option in timing matcher" do
|
57
|
+
RSpec::Benchmark.configure do |config|
|
58
|
+
config.run_in_subprocess = true
|
59
|
+
config.samples = 10
|
60
|
+
end
|
61
|
+
|
62
|
+
bench = [0.005, 0.00001]
|
63
|
+
allow(::Benchmark::Perf::ExecutionTime).to receive(:run).and_return(bench)
|
64
|
+
|
65
|
+
expect { 'x' * 1024 }.to perform_under(0.1)
|
66
|
+
|
67
|
+
expect(::Benchmark::Perf::ExecutionTime).to have_received(:run).with(
|
68
|
+
subprocess: true, warmup: 1, repeat: 10)
|
69
|
+
end
|
70
|
+
|
71
|
+
it "uses the :samples option in complexity matcher" do
|
72
|
+
RSpec::Benchmark.configure do |config|
|
73
|
+
config.samples = 10
|
74
|
+
end
|
75
|
+
|
76
|
+
bench = [:constant, {constant: {residual: 0.9}}]
|
77
|
+
allow(::Benchmark::Trend).to receive(:infer_trend).and_return(bench)
|
78
|
+
|
79
|
+
expect { 'x' * 1024 }.to perform_constant
|
80
|
+
|
81
|
+
expect(::Benchmark::Trend).to have_received(:infer_trend).with(
|
82
|
+
[8, 64, 512, 4096, 8192], repeat: 10)
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.describe "#perform_allocation" do
|
4
|
+
context "expect { ... }.to perform_allocation(...)" do
|
5
|
+
it "passes if the block performs allocations" do
|
6
|
+
expect {
|
7
|
+
_a = [Object.new]
|
8
|
+
}.to perform_allocation(2)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "fails if the block doesn't perform allocation(...)" do
|
12
|
+
expect {
|
13
|
+
expect {
|
14
|
+
_a = [Object.new]
|
15
|
+
}.to perform_allocation(1)
|
16
|
+
}.to raise_error(/expected block to perform allocation of \d object, but allocated \d objects/)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context "expect { ... }.not_to perform_allocation(...)" do
|
21
|
+
it "passes if the block does not perform allocation" do
|
22
|
+
expect {
|
23
|
+
["foo", "bar", "baz"].sort[1]
|
24
|
+
}.to_not perform_allocation(2)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "fails if the block performs allocation" do
|
28
|
+
expect {
|
29
|
+
expect {
|
30
|
+
_a = [Object.new]
|
31
|
+
}.to_not perform_allocation(2)
|
32
|
+
}.to raise_error(/expected block not to perform allocation of \d objects, but allocated \d objects/)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context "expect { ... }.to perform_allocation(Object => ???, ...).objects" do
|
37
|
+
it "splits object allocations by count" do
|
38
|
+
expect {
|
39
|
+
_a = [Object.new]
|
40
|
+
_b = {Object.new => 'foo'}
|
41
|
+
}.to perform_allocation({Object => 2, Array => 1}).objects
|
42
|
+
end
|
43
|
+
|
44
|
+
it "fails to split object allocations by count" do
|
45
|
+
expect {
|
46
|
+
expect {
|
47
|
+
_a = [Object.new]
|
48
|
+
_b = {Object.new => 'bar'}
|
49
|
+
}.to perform_allocation({Object => 1, Array => 1}).objects
|
50
|
+
}.to raise_error("expected block to perform allocation of 1 Array and 1 Object objects, but allocated 1 Array and 2 Object objects")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context "expect { ... }.not_to perform_allocation(Object => ???, ...).objects" do
|
55
|
+
it "passes if the split of object allocations by count is wrong" do
|
56
|
+
expect {
|
57
|
+
_a = [Object.new]
|
58
|
+
_b = {Object.new => 'foo'}
|
59
|
+
}.to_not perform_allocation({Object => 1, Array => 1}).objects
|
60
|
+
end
|
61
|
+
|
62
|
+
it "fails to split object allocations by count" do
|
63
|
+
expect {
|
64
|
+
expect {
|
65
|
+
_a = [Object.new]
|
66
|
+
_b = {Object.new => 'bar'}
|
67
|
+
}.to_not perform_allocation({Object => 2, Array => 1}).objects
|
68
|
+
}.to raise_error("expected block not to perform allocation of 1 Array and 2 Object objects, but allocated 1 Array and 2 Object objects")
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context "expect { ... }.to perform_allocation(...).bytes" do
|
73
|
+
it "passes if the block performs allocations" do
|
74
|
+
expect {
|
75
|
+
_a = [Object.new]
|
76
|
+
_b = {Object.new => 'foo'}
|
77
|
+
}.to perform_allocation(600).bytes
|
78
|
+
end
|
79
|
+
|
80
|
+
it "fails if the block doesn't perform allocation" do
|
81
|
+
expect {
|
82
|
+
expect {
|
83
|
+
_a = {Object.new => 'foo'}
|
84
|
+
}.to perform_allocation(10).bytes
|
85
|
+
}.to raise_error(/expected block to perform allocation of \d+ bytes, but allocated \d+ bytes/)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
context "expect { ... }.not_to perform_allocation(...).bytes" do
|
90
|
+
it "passes if the block does not perform allocation" do
|
91
|
+
expect {
|
92
|
+
_a = {Object.new => 'foo'}
|
93
|
+
}.to_not perform_allocation(10).bytes
|
94
|
+
end
|
95
|
+
|
96
|
+
it "fails if the block performs allocation" do
|
97
|
+
expect {
|
98
|
+
expect {
|
99
|
+
_a = [Object.new]
|
100
|
+
}.to_not perform_allocation(200).bytes
|
101
|
+
}.to raise_error(/expected block not to perform allocation of \d+ bytes, but allocated \d+ bytes/)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
context "expect { ... }.to perform_allocation(Object => ???, ...).bytes" do
|
106
|
+
it "splits object allocations by memory size" do
|
107
|
+
expect {
|
108
|
+
_a = [Object.new]
|
109
|
+
_b = {Object.new => 'foo'}
|
110
|
+
}.to perform_allocation({Object => 80, Array => 40, Hash => 500}).bytes
|
111
|
+
end
|
112
|
+
|
113
|
+
it "fails to split object allocations by memory size" do
|
114
|
+
expect {
|
115
|
+
expect {
|
116
|
+
_a = [Object.new]
|
117
|
+
_b = {Object.new => 'bar'}
|
118
|
+
}.to perform_allocation({Object => 80, Hash => 0}).bytes
|
119
|
+
}.to raise_error(/expected block to perform allocation of \d+ Hash and \d+ Object bytes, but allocated \d+ Hash and \d+ Object bytes/)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
it "fails to recognize expectation type" do
|
124
|
+
expect {
|
125
|
+
expect { Object.new }.to perform_allocation(:error)
|
126
|
+
}.to raise_error(ArgumentError, "'error' is not a recognized argument")
|
127
|
+
end
|
128
|
+
end
|
@@ -16,7 +16,7 @@ RSpec.describe 'RSpec::Benchmark::ComplexityMatcher', '#perform_exponential' do
|
|
16
16
|
it "passes if the block performs exponential" do
|
17
17
|
expect { |n, i|
|
18
18
|
fibonacci(n)
|
19
|
-
}.to
|
19
|
+
}.to perform_exp.in_range(1, 15).ratio(2).sample(100)
|
20
20
|
end
|
21
21
|
|
22
22
|
it "fails if the block doesn't perform exponential" do
|
@@ -14,8 +14,9 @@ RSpec.describe 'RSpec::Benchmark::ComplexityMatcher', '#perform_linear' do
|
|
14
14
|
|
15
15
|
it "provides a default range" do
|
16
16
|
range = [1,2,3]
|
17
|
+
trend = [:linear, {linear: {residual: 0.95}}]
|
17
18
|
allow(::Benchmark::Trend).to receive(:range).and_return(range)
|
18
|
-
allow(::Benchmark::Trend).to receive(:infer_trend).and_return(
|
19
|
+
allow(::Benchmark::Trend).to receive(:infer_trend).and_return(trend)
|
19
20
|
|
20
21
|
expect { |n, i| n }.to perform_linear
|
21
22
|
|
@@ -24,14 +25,21 @@ RSpec.describe 'RSpec::Benchmark::ComplexityMatcher', '#perform_linear' do
|
|
24
25
|
|
25
26
|
it "changes default range using in_range and ratio matchers" do
|
26
27
|
range = [1,2,3]
|
28
|
+
trend = [:linear, {linear: {residual: 0.95}}]
|
27
29
|
allow(::Benchmark::Trend).to receive(:range).and_return(range)
|
28
|
-
allow(::Benchmark::Trend).to receive(:infer_trend).and_return(
|
30
|
+
allow(::Benchmark::Trend).to receive(:infer_trend).and_return(trend)
|
29
31
|
|
30
32
|
expect { |n, i| n }.to perform_linear.in_range(3, 33_000).ratio(2)
|
31
33
|
|
32
34
|
expect(::Benchmark::Trend).to have_received(:range).with(3, 33_000, ratio: 2)
|
33
35
|
end
|
34
36
|
|
37
|
+
it "fails when wrong argument for range" do
|
38
|
+
expect {
|
39
|
+
expect { |n, i| }.to perform_linear.in_range(1..10)
|
40
|
+
}.to raise_error(ArgumentError, "Wrong range argument '1..10', it expects an array or numeric start value.")
|
41
|
+
end
|
42
|
+
|
35
43
|
context "expect { ... }.to perfom_linear" do
|
36
44
|
it "passes if the block performs linear" do
|
37
45
|
range = bench_range(1, 8 << 10)
|
@@ -39,7 +47,7 @@ RSpec.describe 'RSpec::Benchmark::ComplexityMatcher', '#perform_linear' do
|
|
39
47
|
|
40
48
|
expect { |n, i|
|
41
49
|
numbers[i].max
|
42
|
-
}.to perform_linear.in_range(range
|
50
|
+
}.to perform_linear.in_range(range).sample(100).threshold(0.85)
|
43
51
|
end
|
44
52
|
|
45
53
|
it "fails if the block doesn't perform linear" do
|
@@ -20,7 +20,7 @@ RSpec.describe 'RSpec::Benchmark::ComplexityMatcher', '#perform_logarithmic' do
|
|
20
20
|
|
21
21
|
expect { |n, i|
|
22
22
|
numbers[i].bsearch { |x| x == 1 }
|
23
|
-
}.to
|
23
|
+
}.to perform_log.in_range(range[0], range[-1]).ratio(2).sample(100).times
|
24
24
|
end
|
25
25
|
|
26
26
|
it "fails if the block doesn't perform logarithmic" do
|
@@ -42,7 +42,7 @@ RSpec.describe 'RSpec::Benchmark::ComplexityMatcher', '#perform_logarithmic' do
|
|
42
42
|
expect {
|
43
43
|
expect { |n, i|
|
44
44
|
numbers[i].bsearch { |x| x == 1 }
|
45
|
-
}.not_to
|
45
|
+
}.not_to perform_log.in_range(range[0], range[-1]).ratio(2).sample(100).times
|
46
46
|
}.to raise_error("expected block not to perform logarithmic, but performed logarithmic")
|
47
47
|
end
|
48
48
|
end
|
@@ -10,8 +10,11 @@ RSpec.describe 'RSpec::Benchmark::TimingMatcher', '#perform_under' do
|
|
10
10
|
it "allows to configure warmup cycles" do
|
11
11
|
bench = [0.005, 0.00001]
|
12
12
|
allow(::Benchmark::Perf::ExecutionTime).to receive(:run).and_return(bench)
|
13
|
+
|
13
14
|
expect { 'x' * 1024 * 10 }.to perform_under(0.006).sec.warmup(2).times.sample(3)
|
14
|
-
|
15
|
+
|
16
|
+
expect(::Benchmark::Perf::ExecutionTime).to have_received(:run).with(
|
17
|
+
subprocess: false, repeat: 3, warmup: 2)
|
15
18
|
end
|
16
19
|
|
17
20
|
it "doesn't allow sample size less than 1" do
|
metadata
CHANGED
@@ -1,43 +1,57 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rspec-benchmark
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Piotr Murach
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2019-04-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: benchmark-malloc
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.1.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.1.0
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: benchmark-perf
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
16
30
|
requirements:
|
17
31
|
- - "~>"
|
18
32
|
- !ruby/object:Gem::Version
|
19
|
-
version: 0.
|
33
|
+
version: 0.5.0
|
20
34
|
type: :runtime
|
21
35
|
prerelease: false
|
22
36
|
version_requirements: !ruby/object:Gem::Requirement
|
23
37
|
requirements:
|
24
38
|
- - "~>"
|
25
39
|
- !ruby/object:Gem::Version
|
26
|
-
version: 0.
|
40
|
+
version: 0.5.0
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: benchmark-trend
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
30
44
|
requirements:
|
31
45
|
- - "~>"
|
32
46
|
- !ruby/object:Gem::Version
|
33
|
-
version: 0.
|
47
|
+
version: 0.3.0
|
34
48
|
type: :runtime
|
35
49
|
prerelease: false
|
36
50
|
version_requirements: !ruby/object:Gem::Requirement
|
37
51
|
requirements:
|
38
52
|
- - "~>"
|
39
53
|
- !ruby/object:Gem::Version
|
40
|
-
version: 0.
|
54
|
+
version: 0.3.0
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: rspec
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -62,34 +76,34 @@ dependencies:
|
|
62
76
|
name: bundler
|
63
77
|
requirement: !ruby/object:Gem::Requirement
|
64
78
|
requirements:
|
65
|
-
- - "
|
79
|
+
- - ">="
|
66
80
|
- !ruby/object:Gem::Version
|
67
|
-
version: '1.
|
81
|
+
version: '1.5'
|
68
82
|
type: :development
|
69
83
|
prerelease: false
|
70
84
|
version_requirements: !ruby/object:Gem::Requirement
|
71
85
|
requirements:
|
72
|
-
- - "
|
86
|
+
- - ">="
|
73
87
|
- !ruby/object:Gem::Version
|
74
|
-
version: '1.
|
88
|
+
version: '1.5'
|
75
89
|
- !ruby/object:Gem::Dependency
|
76
90
|
name: rake
|
77
91
|
requirement: !ruby/object:Gem::Requirement
|
78
92
|
requirements:
|
79
|
-
- - "
|
93
|
+
- - ">="
|
80
94
|
- !ruby/object:Gem::Version
|
81
|
-
version: '
|
95
|
+
version: '0'
|
82
96
|
type: :development
|
83
97
|
prerelease: false
|
84
98
|
version_requirements: !ruby/object:Gem::Requirement
|
85
99
|
requirements:
|
86
|
-
- - "
|
100
|
+
- - ">="
|
87
101
|
- !ruby/object:Gem::Version
|
88
|
-
version: '
|
89
|
-
description: Performance testing matchers for RSpec
|
90
|
-
|
102
|
+
version: '0'
|
103
|
+
description: Performance testing matchers for RSpec to set expectations on speed,
|
104
|
+
resources usage and scalibility.
|
91
105
|
email:
|
92
|
-
-
|
106
|
+
- me@piotrmurach.com
|
93
107
|
executables: []
|
94
108
|
extensions: []
|
95
109
|
extra_rdoc_files: []
|
@@ -100,8 +114,10 @@ files:
|
|
100
114
|
- Rakefile
|
101
115
|
- lib/rspec-benchmark.rb
|
102
116
|
- lib/rspec/benchmark.rb
|
117
|
+
- lib/rspec/benchmark/allocation_matcher.rb
|
103
118
|
- lib/rspec/benchmark/comparison_matcher.rb
|
104
119
|
- lib/rspec/benchmark/complexity_matcher.rb
|
120
|
+
- lib/rspec/benchmark/configuration.rb
|
105
121
|
- lib/rspec/benchmark/format_time.rb
|
106
122
|
- lib/rspec/benchmark/iteration_matcher.rb
|
107
123
|
- lib/rspec/benchmark/matchers.rb
|
@@ -111,7 +127,9 @@ files:
|
|
111
127
|
- spec/spec_helper.rb
|
112
128
|
- spec/unit/comparison_matcher_spec.rb
|
113
129
|
- spec/unit/composable_spec.rb
|
130
|
+
- spec/unit/configuration_spec.rb
|
114
131
|
- spec/unit/format_time_spec.rb
|
132
|
+
- spec/unit/perform_allocation_spec.rb
|
115
133
|
- spec/unit/perform_at_least_spec.rb
|
116
134
|
- spec/unit/perform_constant_spec.rb
|
117
135
|
- spec/unit/perform_exponential_spec.rb
|
@@ -121,10 +139,13 @@ files:
|
|
121
139
|
- spec/unit/perform_under_spec.rb
|
122
140
|
- tasks/coverage.rake
|
123
141
|
- tasks/spec.rake
|
124
|
-
homepage:
|
142
|
+
homepage: https://github.com/piotrmurach/rspec-benchmark
|
125
143
|
licenses:
|
126
144
|
- MIT
|
127
|
-
metadata:
|
145
|
+
metadata:
|
146
|
+
homepage_uri: https://github.com/piotrmurach/rspec-benchmark
|
147
|
+
source_code_uri: https://github.com/piotrmurach/rspec-benchmark
|
148
|
+
changelog_uri: https://github.com/piotrmurach/rspec-benchmark/blob/master/CHANGELOG.md
|
128
149
|
post_install_message:
|
129
150
|
rdoc_options: []
|
130
151
|
require_paths:
|
@@ -133,15 +154,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
133
154
|
requirements:
|
134
155
|
- - ">="
|
135
156
|
- !ruby/object:Gem::Version
|
136
|
-
version: 2.
|
157
|
+
version: 2.1.0
|
137
158
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
138
159
|
requirements:
|
139
160
|
- - ">="
|
140
161
|
- !ruby/object:Gem::Version
|
141
162
|
version: '0'
|
142
163
|
requirements: []
|
143
|
-
|
144
|
-
rubygems_version: 2.7.3
|
164
|
+
rubygems_version: 3.0.3
|
145
165
|
signing_key:
|
146
166
|
specification_version: 4
|
147
167
|
summary: Performance testing matchers for RSpec
|
@@ -149,7 +169,9 @@ test_files:
|
|
149
169
|
- spec/spec_helper.rb
|
150
170
|
- spec/unit/comparison_matcher_spec.rb
|
151
171
|
- spec/unit/composable_spec.rb
|
172
|
+
- spec/unit/configuration_spec.rb
|
152
173
|
- spec/unit/format_time_spec.rb
|
174
|
+
- spec/unit/perform_allocation_spec.rb
|
153
175
|
- spec/unit/perform_at_least_spec.rb
|
154
176
|
- spec/unit/perform_constant_spec.rb
|
155
177
|
- spec/unit/perform_exponential_spec.rb
|