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.
@@ -1,4 +1,6 @@
1
- # encoding: utf-8
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 = options.fetch(:time) { 0.2 }
13
- warmup = options.fetch(:warmup) { 0.1 }
14
- @bench = ::Benchmark::Perf::Iteration.new(time: time, warmup: warmup)
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
- # encoding: utf-8
1
+ # frozen_string_literal
2
2
 
3
- require 'rspec/benchmark/timing_matcher'
4
- require 'rspec/benchmark/iteration_matcher'
5
- require 'rspec/benchmark/comparison_matcher'
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
- # encoding: utf-8
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 = options.fetch(:samples) { 30 }
17
- @scale = threshold.to_s.split(/\./).last.size
18
- @block = nil
19
- @confidence_interval = nil
20
- warmup = options.fetch(:warmup) { 1 }
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
- def and_sample(samples)
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
@@ -1,7 +1,7 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
3
  module RSpec
4
4
  module Benchmark
5
- VERSION = "0.3.0"
5
+ VERSION = "0.4.0"
6
6
  end # Benchmark
7
7
  end # RSpec
@@ -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 = `git ls-files -z`.split("\x0")
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.add_dependency 'benchmark-perf', '~> 0.2.0'
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', '>= 1.5.0', '< 2.0'
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
@@ -1,6 +1,6 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
2
 
3
- if RUBY_VERSION > '1.9' and (ENV['COVERAGE'] || ENV['TRAVIS'])
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 'rspec-benchmark'
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 |expectations|
24
- expectations.include_chain_clauses_in_custom_matcher_descriptions = true
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
- bench = double(run: 100)
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(::Benchmark::Perf::Iteration).to have_received(:new).
25
- with(time: 0.3, warmup: 0.2)
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
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe 'composable specs' do
4
+ it "composes perform_under and perform_at_least matchers" do
5
+ expect {
6
+ 'x' * 1024 * 10
7
+ }.to perform_under(6).ms and perform_at_least(10_000).ips
8
+ end
9
+ end
@@ -1,15 +1,15 @@
1
- # encoding: utf-8
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 = double(run: [10_000, 100])
7
- allow(::Benchmark::Perf::Iteration).to receive(:new).and_return(bench)
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, warmup: 0.2, time: 0.3)
10
+ }.to perform_at_least(10_000).within(0.3).warmup(0.2)
11
11
 
12
- expect(::Benchmark::Perf::Iteration).to have_received(:new).
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