rspec-benchmark 0.3.0 → 0.4.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 +5 -5
- data/CHANGELOG.md +20 -0
- data/README.md +119 -22
- data/lib/rspec/benchmark.rb +3 -6
- data/lib/rspec/benchmark/comparison_matcher.rb +31 -7
- data/lib/rspec/benchmark/complexity_matcher.rb +89 -0
- data/lib/rspec/benchmark/format_time.rb +1 -1
- data/lib/rspec/benchmark/iteration_matcher.rb +31 -5
- data/lib/rspec/benchmark/matchers.rb +83 -4
- data/lib/rspec/benchmark/timing_matcher.rb +35 -9
- data/lib/rspec/benchmark/version.rb +2 -2
- data/rspec-benchmark.gemspec +10 -6
- data/spec/spec_helper.rb +6 -16
- data/spec/unit/comparison_matcher_spec.rb +27 -6
- data/spec/unit/composable_spec.rb +9 -0
- data/spec/unit/perform_at_least_spec.rb +6 -6
- data/spec/unit/perform_constant_spec.rb +46 -0
- data/spec/unit/perform_exponential_spec.rb +42 -0
- data/spec/unit/perform_linear_spec.rb +72 -0
- data/spec/unit/perform_logarithmic_spec.rb +49 -0
- data/spec/unit/perform_power_spec.rb +55 -0
- data/spec/unit/perform_under_spec.rb +17 -11
- metadata +40 -24
- data/.gitignore +0 -14
- data/.rspec +0 -2
- data/.travis.yml +0 -28
- data/Gemfile +0 -9
@@ -1,4 +1,6 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'benchmark-perf'
|
2
4
|
|
3
5
|
module RSpec
|
4
6
|
module Benchmark
|
@@ -9,9 +11,9 @@ module RSpec
|
|
9
11
|
class Matcher
|
10
12
|
def initialize(iterations, **options)
|
11
13
|
@iterations = iterations
|
12
|
-
time
|
13
|
-
warmup
|
14
|
-
@bench
|
14
|
+
@time = options.fetch(:time) { 0.2 }
|
15
|
+
@warmup = options.fetch(:warmup) { 0.1 }
|
16
|
+
@bench = ::Benchmark::Perf::Iteration
|
15
17
|
end
|
16
18
|
|
17
19
|
# Indicates this matcher matches against a block
|
@@ -27,10 +29,34 @@ module RSpec
|
|
27
29
|
#
|
28
30
|
# @api private
|
29
31
|
def matches?(block)
|
30
|
-
@average, @stddev, = @bench.run(&block)
|
32
|
+
@average, @stddev, = @bench.run(time: @time, warmup: @warmup, &block)
|
31
33
|
@iterations <= (@average + 3 * @stddev)
|
32
34
|
end
|
33
35
|
|
36
|
+
# The time before measurements are taken
|
37
|
+
#
|
38
|
+
# @param [Numeric] value
|
39
|
+
# the time before measurements are taken
|
40
|
+
#
|
41
|
+
# @api public
|
42
|
+
def warmup(value)
|
43
|
+
@warmup = value
|
44
|
+
self
|
45
|
+
end
|
46
|
+
|
47
|
+
# Time to measure iteration for
|
48
|
+
#
|
49
|
+
# @param [Numeric] value
|
50
|
+
# the time to take measurements for
|
51
|
+
#
|
52
|
+
# @api public
|
53
|
+
def within(value)
|
54
|
+
@time = value
|
55
|
+
self
|
56
|
+
end
|
57
|
+
|
58
|
+
# Sugar syntax for iterations per second
|
59
|
+
# @api public
|
34
60
|
def ips
|
35
61
|
self
|
36
62
|
end
|
@@ -1,8 +1,9 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
require_relative 'comparison_matcher'
|
4
|
+
require_relative 'complexity_matcher'
|
5
|
+
require_relative 'iteration_matcher'
|
6
|
+
require_relative 'timing_matcher'
|
6
7
|
|
7
8
|
module RSpec
|
8
9
|
module Benchmark
|
@@ -75,6 +76,84 @@ module RSpec
|
|
75
76
|
def perform_slower_than(**options, &sample)
|
76
77
|
ComparisonMatcher::Matcher.new(sample, :slower, options)
|
77
78
|
end
|
79
|
+
|
80
|
+
# Pass if code block performs constant
|
81
|
+
#
|
82
|
+
# @example
|
83
|
+
# expect { ... }.to perform_constant
|
84
|
+
# expect { ... }.to perform_constant.within(1, 100_000)
|
85
|
+
# expect { ... }.to perform_constant.within(1, 100_000, ratio: 4)
|
86
|
+
#
|
87
|
+
# @api public
|
88
|
+
def perform_constant(**options)
|
89
|
+
ComplexityMatcher::Matcher.new(:constant, options)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Pass if code block performs logarithmic
|
93
|
+
#
|
94
|
+
# @example
|
95
|
+
# expect { ... }.to perform_logarithmic
|
96
|
+
# expect { ... }.to perform_logarithmic
|
97
|
+
# expect { ... }.to perform_logarithmic.within(1, 100_000)
|
98
|
+
# expect { ... }.to perform_logarithimic.within(1, 100_000, ratio: 4)
|
99
|
+
#
|
100
|
+
# @api public
|
101
|
+
def perform_logarithmic(**options)
|
102
|
+
ComplexityMatcher::Matcher.new(:logarithmic, options)
|
103
|
+
end
|
104
|
+
|
105
|
+
# Pass if code block performs linear
|
106
|
+
#
|
107
|
+
# @example
|
108
|
+
# expect { ... }.to perform_linear
|
109
|
+
# expect { ... }.to perform_linear.within(1, 100_000)
|
110
|
+
# expect { ... }.to perform_linear.within(1, 100_000, ratio: 4)
|
111
|
+
#
|
112
|
+
# @api public
|
113
|
+
def perform_linear(**options)
|
114
|
+
ComplexityMatcher::Matcher.new(:linear, options)
|
115
|
+
end
|
116
|
+
|
117
|
+
# Pass if code block performs power
|
118
|
+
#
|
119
|
+
# @example
|
120
|
+
# expect { ... }.to perform_power
|
121
|
+
# expect { ... }.to perform_power.within(1, 100_000)
|
122
|
+
# expect { ... }.to perform_power.within(1, 100_000, ratio: 4)
|
123
|
+
#
|
124
|
+
# @api public
|
125
|
+
def perform_power(**options)
|
126
|
+
ComplexityMatcher::Matcher.new(:power, options)
|
127
|
+
end
|
128
|
+
|
129
|
+
# Pass if code block performs exponential
|
130
|
+
#
|
131
|
+
# @example
|
132
|
+
# expect { ... }.to perform_exponential
|
133
|
+
# expect { ... }.to perform_exponential.within(1, 100_000)
|
134
|
+
# expect { ... }.to perform_exponential.within(1, 100_000, ratio: 4)
|
135
|
+
#
|
136
|
+
# @api public
|
137
|
+
def perform_exponential(**options)
|
138
|
+
ComplexityMatcher::Matcher.new(:exponential, options)
|
139
|
+
end
|
140
|
+
|
141
|
+
# Generate a geometric progression of inputs
|
142
|
+
#
|
143
|
+
# The default range is generated in the multiples of 8.
|
144
|
+
#
|
145
|
+
# @example
|
146
|
+
# bench_range(8, 8 << 10)
|
147
|
+
# # => [8, 64, 512, 4096, 8192]
|
148
|
+
#
|
149
|
+
# @param [Integer] start
|
150
|
+
# @param [Integer] limit
|
151
|
+
# @param [Integer] ratio
|
152
|
+
#
|
153
|
+
# @api public
|
154
|
+
def bench_range(*args)
|
155
|
+
::Benchmark::Trend.range(*args)
|
156
|
+
end
|
78
157
|
end # Matchers
|
79
158
|
end # Benchmark
|
80
159
|
end # RSpec
|
@@ -1,4 +1,8 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'benchmark-perf'
|
4
|
+
|
5
|
+
require_relative 'format_time'
|
2
6
|
|
3
7
|
module RSpec
|
4
8
|
module Benchmark
|
@@ -13,12 +17,11 @@ module RSpec
|
|
13
17
|
|
14
18
|
def initialize(threshold, **options)
|
15
19
|
@threshold = threshold
|
16
|
-
@samples
|
17
|
-
@
|
18
|
-
@
|
19
|
-
@
|
20
|
-
|
21
|
-
@bench = ::Benchmark::Perf::ExecutionTime.new(warmup: warmup)
|
20
|
+
@samples = options.fetch(:samples) { 1 }
|
21
|
+
@warmup = options.fetch(:warmup) { 1 }
|
22
|
+
@scale = threshold.to_s.split(/\./).last.size
|
23
|
+
@block = nil
|
24
|
+
@bench = ::Benchmark::Perf::ExecutionTime
|
22
25
|
end
|
23
26
|
|
24
27
|
# Indicates this matcher matches against a block
|
@@ -36,7 +39,7 @@ module RSpec
|
|
36
39
|
def matches?(block)
|
37
40
|
@block = block
|
38
41
|
return false unless block.is_a?(Proc)
|
39
|
-
@average, @stddev = @bench.run(@samples, &block)
|
42
|
+
@average, @stddev = @bench.run(repeat: @samples, warmup: @warmup, &block)
|
40
43
|
@average <= @threshold
|
41
44
|
end
|
42
45
|
|
@@ -44,11 +47,34 @@ module RSpec
|
|
44
47
|
!matches?(block) && block.is_a?(Proc)
|
45
48
|
end
|
46
49
|
|
47
|
-
|
50
|
+
# The time before measurements are taken
|
51
|
+
#
|
52
|
+
# @param [Numeric] value
|
53
|
+
# the time before measurements are taken
|
54
|
+
#
|
55
|
+
# @api public
|
56
|
+
def warmup(value)
|
57
|
+
@warmup = value
|
58
|
+
self
|
59
|
+
end
|
60
|
+
|
61
|
+
# How many times to repeat measurement
|
62
|
+
#
|
63
|
+
# @param [Integer] samples
|
64
|
+
# the number of times to repeat the measurement
|
65
|
+
#
|
66
|
+
# @api public
|
67
|
+
def sample(samples)
|
48
68
|
@samples = samples
|
49
69
|
self
|
50
70
|
end
|
51
71
|
|
72
|
+
# No-op, syntactic sugar.
|
73
|
+
# @api public
|
74
|
+
def times
|
75
|
+
self
|
76
|
+
end
|
77
|
+
|
52
78
|
def secs
|
53
79
|
self
|
54
80
|
end
|
data/rspec-benchmark.gemspec
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
# coding: utf-8
|
2
1
|
lib = File.expand_path('../lib', __FILE__)
|
3
2
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
3
|
require 'rspec/benchmark/version'
|
@@ -9,18 +8,23 @@ Gem::Specification.new do |spec|
|
|
9
8
|
spec.authors = ["Piotr Murach"]
|
10
9
|
spec.email = [""]
|
11
10
|
spec.summary = %q{Performance testing matchers for RSpec}
|
12
|
-
spec.description = %q{Performance testing matchers for RSpec that provide simple way to specify speed benchmark expectations}
|
11
|
+
spec.description = %q{Performance testing matchers for RSpec that provide simple way to specify speed and algorithmic complexity benchmark expectations}
|
13
12
|
spec.homepage = ""
|
14
13
|
spec.license = "MIT"
|
15
14
|
|
16
|
-
spec.files =
|
15
|
+
spec.files = Dir['{lib,spec}/**/*.rb']
|
16
|
+
spec.files += Dir['tasks/*', 'rspec-benchmark.gemspec']
|
17
|
+
spec.files += Dir['README.md', 'CHANGELOG.md', 'LICENSE.txt', 'Rakefile']
|
17
18
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
19
|
spec.test_files = spec.files.grep(%r{^spec/})
|
19
20
|
spec.require_paths = ["lib"]
|
20
21
|
|
21
|
-
spec.
|
22
|
+
spec.required_ruby_version = '>= 2.0.0'
|
23
|
+
|
24
|
+
spec.add_dependency 'benchmark-perf', '~> 0.4.0'
|
25
|
+
spec.add_dependency 'benchmark-trend', '~> 0.2.0'
|
22
26
|
spec.add_dependency 'rspec', '>= 3.0.0', '< 4.0.0'
|
23
27
|
|
24
|
-
spec.add_development_dependency 'bundler', '
|
25
|
-
spec.add_development_dependency 'rake'
|
28
|
+
spec.add_development_dependency 'bundler', '~> 1.16'
|
29
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
26
30
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
if
|
3
|
+
if ENV['COVERAGE'] || ENV['TRAVIS']
|
4
4
|
require 'simplecov'
|
5
5
|
require 'coveralls'
|
6
6
|
|
@@ -15,22 +15,16 @@ if RUBY_VERSION > '1.9' and (ENV['COVERAGE'] || ENV['TRAVIS'])
|
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
|
-
require '
|
18
|
+
require 'bundler/setup'
|
19
|
+
require 'rspec/benchmark'
|
19
20
|
|
20
21
|
RSpec.configure do |config|
|
21
22
|
config.include(RSpec::Benchmark::Matchers)
|
22
23
|
|
23
|
-
config.expect_with :rspec do |
|
24
|
-
|
24
|
+
config.expect_with :rspec do |c|
|
25
|
+
c.syntax = :expect
|
25
26
|
end
|
26
27
|
|
27
|
-
config.mock_with :rspec do |mocks|
|
28
|
-
mocks.verify_partial_doubles = true
|
29
|
-
end
|
30
|
-
|
31
|
-
config.filter_run :focus
|
32
|
-
config.run_all_when_everything_filtered = true
|
33
|
-
|
34
28
|
config.disable_monkey_patching!
|
35
29
|
|
36
30
|
config.warnings = true
|
@@ -40,8 +34,4 @@ RSpec.configure do |config|
|
|
40
34
|
end
|
41
35
|
|
42
36
|
config.profile_examples = 2
|
43
|
-
|
44
|
-
config.order = :random
|
45
|
-
|
46
|
-
Kernel.srand config.seed
|
47
37
|
end
|
@@ -16,13 +16,34 @@ RSpec.describe RSpec::Benchmark::ComparisonMatcher::Matcher do
|
|
16
16
|
end
|
17
17
|
|
18
18
|
it "allows to configure matcher timings" do
|
19
|
-
|
20
|
-
allow(::Benchmark::Perf::Iteration).to receive(:new).and_return(bench)
|
21
|
-
sample = -> { 'x' * 10 * 1024 }
|
22
|
-
expect { 1 << 1 }.to perform_faster_than(warmup: 0.2, time: 0.3, &sample).exactly(1).times
|
19
|
+
allow(::Benchmark::Perf::Iteration).to receive(:run).and_return(100)
|
23
20
|
|
24
|
-
expect
|
25
|
-
|
21
|
+
expect {
|
22
|
+
1 << 1
|
23
|
+
}.to perform_faster_than.within(0.3).warmup(0.2) {
|
24
|
+
'x' * 10 * 1024
|
25
|
+
}.once
|
26
|
+
|
27
|
+
expect(::Benchmark::Perf::Iteration).to have_received(:run).with(time: 0.3, warmup: 0.2).twice
|
28
|
+
end
|
29
|
+
|
30
|
+
context 'algorithms comparison' do
|
31
|
+
def clamp(num, min, max)
|
32
|
+
[num, min, max].sort[1]
|
33
|
+
end
|
34
|
+
|
35
|
+
def clamp_fast(num, min, max)
|
36
|
+
num > max ? max : (num < min ? min : num)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "infers one implementation faster than another" do
|
40
|
+
num = rand(1_000)
|
41
|
+
expect {
|
42
|
+
clamp_fast(num, num / 2, num * 2)
|
43
|
+
}.to perform_faster_than {
|
44
|
+
clamp(num, num / 2, num * 2)
|
45
|
+
}.at_least(1.5).times
|
46
|
+
end
|
26
47
|
end
|
27
48
|
|
28
49
|
describe "expect { ... }.to perform_faster_than(...)" do
|
@@ -1,15 +1,15 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
RSpec.describe '#perform_at_least' do
|
4
|
-
|
5
4
|
it "allows to configure matcher timings" do
|
6
|
-
bench =
|
7
|
-
allow(::Benchmark::Perf::Iteration).to receive(:
|
5
|
+
bench = [10_000, 100]
|
6
|
+
allow(::Benchmark::Perf::Iteration).to receive(:run).and_return(bench)
|
7
|
+
|
8
8
|
expect {
|
9
9
|
'x' * 1024 * 10
|
10
|
-
}.to perform_at_least(10_000
|
10
|
+
}.to perform_at_least(10_000).within(0.3).warmup(0.2)
|
11
11
|
|
12
|
-
expect(::Benchmark::Perf::Iteration).to have_received(:
|
12
|
+
expect(::Benchmark::Perf::Iteration).to have_received(:run).
|
13
13
|
with(time: 0.3, warmup: 0.2)
|
14
14
|
end
|
15
15
|
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.describe 'RSpec::Benchmark::ComplexityMatcher', '#perform_constant' do
|
4
|
+
# exponential
|
5
|
+
def fibonacci(n)
|
6
|
+
n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2)
|
7
|
+
end
|
8
|
+
|
9
|
+
it "propagates error inside expectation" do
|
10
|
+
expect {
|
11
|
+
expect { raise 'boom' }.to perform_constant
|
12
|
+
}.to raise_error(StandardError, /boom/)
|
13
|
+
end
|
14
|
+
|
15
|
+
context "expect { ... }.to perfom_constant" do
|
16
|
+
it "passes if the block performs constant" do
|
17
|
+
expect { |n, i|
|
18
|
+
n * i
|
19
|
+
}.to perform_constant.in_range(1, 1000).sample(100)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "fails if the block doesn't perform constant" do
|
23
|
+
expect {
|
24
|
+
expect { |n, i|
|
25
|
+
fibonacci(n)
|
26
|
+
}.to perform_constant.in_range(1, 15).ratio(2).sample(100)
|
27
|
+
}.to raise_error("expected block to perform constant, but performed exponential")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context "expect { ... }.not_to perfom_constant" do
|
32
|
+
it "passes if the block does not perform constant" do
|
33
|
+
expect { |n, i|
|
34
|
+
fibonacci(n)
|
35
|
+
}.not_to perform_constant.in_range(1, 15).ratio(2).sample(100)
|
36
|
+
end
|
37
|
+
|
38
|
+
it "fails if the block doesn't perform constant" do
|
39
|
+
expect {
|
40
|
+
expect { |n, i|
|
41
|
+
n * i
|
42
|
+
}.not_to perform_constant.in_range(1, 1000).sample(100)
|
43
|
+
}.to raise_error("expected block not to perform constant, but performed constant")
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.describe 'RSpec::Benchmark::ComplexityMatcher', '#perform_exponential' do
|
4
|
+
# exponential
|
5
|
+
def fibonacci(n)
|
6
|
+
n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2)
|
7
|
+
end
|
8
|
+
|
9
|
+
it "propagates error inside expectation" do
|
10
|
+
expect {
|
11
|
+
expect { raise 'boom' }.to perform_exponential
|
12
|
+
}.to raise_error(StandardError, /boom/)
|
13
|
+
end
|
14
|
+
|
15
|
+
context "expect { ... }.to perfom_exponential" do
|
16
|
+
it "passes if the block performs exponential" do
|
17
|
+
expect { |n, i|
|
18
|
+
fibonacci(n)
|
19
|
+
}.to perform_exponential.in_range(1, 15).ratio(2).sample(100)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "fails if the block doesn't perform exponential" do
|
23
|
+
expect {
|
24
|
+
expect { |n| n }.to perform_exponential.in_range(1, 10_000).sample(100)
|
25
|
+
}.to raise_error("expected block to perform exponential, but performed constant")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context "expect { ... }.not_to perfom_exponential" do
|
30
|
+
it "passes if the block does not perform exponential" do
|
31
|
+
expect { |n| n }.not_to perform_exponential.in_range(1, 10_000).sample(100)
|
32
|
+
end
|
33
|
+
|
34
|
+
it "fails if the block doesn't perform exponential" do
|
35
|
+
expect {
|
36
|
+
expect { |n|
|
37
|
+
fibonacci(n)
|
38
|
+
}.not_to perform_exponential.in_range(1, 15).ratio(2).sample(100)
|
39
|
+
}.to raise_error("expected block not to perform exponential, but performed exponential")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|