rspec-benchmark 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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