diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b9c948..3c02a29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Change log +## [v0.7.0] + +### Added +* Change ComparisonMatcher to allow using `at_least` and `at_most` together + ## [v0.6.0] - 2020-03-09 ### Added diff --git a/lib/rspec/benchmark/comparison_matcher.rb b/lib/rspec/benchmark/comparison_matcher.rb index b74227f..3edface 100644 --- a/lib/rspec/benchmark/comparison_matcher.rb +++ b/lib/rspec/benchmark/comparison_matcher.rb @@ -9,15 +9,17 @@ module ComparisonMatcher # # @api private class Matcher + DEFAULT_EXPECTED_COUNT_MATCHERS = { at_least: 1 }.freeze + def initialize(expected, comparison_type, **options) check_comparison(comparison_type) @expected = expected @comparison_type = comparison_type @count = 1 - @count_type = :at_least @time = options.fetch(:time) { 0.2 } @warmup = options.fetch(:warmup) { 0.1 } @bench = ::Benchmark::Perf::Iteration + @expected_count_matchers = {} end # Indicates this matcher matches against a block @@ -45,14 +47,7 @@ def matches?(block) @ratio = @actual_ips / @expected_ips.to_f - case @count_type - when :at_most - at_most_comparison - when :exactly - exact_comparison - else - default_comparison - end + expected_count_matchers.all? { |type, count| matches_comparison?(type, count) } end # The time before measurements are taken @@ -139,12 +134,11 @@ def failure_message_when_negated # @api private def description - if @count == 1 - "perform #{@comparison_type} than comparison block" - else - "perform #{@comparison_type} than comparison block " \ - "by #{@count_type} #{@count} times" - end + main_description = "perform #{@comparison_type} than comparison block" + description_extra = comparison_description + return main_description if !description_extra || description_extra.empty? + + [main_description, comparison_description].compact.join(' ') end # @api private @@ -162,6 +156,13 @@ def failure_reason private + def comparison_description + expected_count_matchers + .select { |_, count| count != 1 } + .map { |type, count| "by #{type} #{count} times" } + .join(' and ') + end + def convert_count(n) case n when Numeric then n @@ -175,8 +176,23 @@ def convert_count(n) end def set_expected_times_count(type, n) - @count_type = type - @count = convert_count(n) + @expected_count_matchers ||= {} + @expected_count_matchers[type] = convert_count(n) + end + + def expected_count_matchers + @expected_count_matchers.empty? ? DEFAULT_EXPECTED_COUNT_MATCHERS : @expected_count_matchers + end + + def matches_comparison?(count_type, count) + case count_type + when :at_most + at_most_comparison(count) + when :exactly + exact_comparison(count) + else + default_comparison(count) + end end # At most comparison @@ -201,11 +217,11 @@ def set_expected_times_count(type, n) # @return [Boolean] # # @api private - def at_most_comparison + def at_most_comparison(count) if @comparison_type == :faster - 1 < @ratio && @ratio < @count + 1 < @ratio && @ratio < count else - @count**-1 < @ratio && @ratio < 1 + count**-1 < @ratio && @ratio < 1 end end @@ -224,11 +240,11 @@ def at_most_comparison # @return [Boolean] # # @api private - def exact_comparison + def exact_comparison(count) if @comparison_type == :faster - @count == @ratio.round + count == @ratio.round else - @count == (1.0 / @ratio).round + count == (1.0 / @ratio).round end end @@ -254,11 +270,11 @@ def exact_comparison # @return [Boolean] # # @api private - def default_comparison + def default_comparison(count) if @comparison_type == :faster - @ratio > @count + @ratio > count else - @ratio < (1.0 / @count) + @ratio < (1.0 / count) end end diff --git a/spec/unit/comparison_matcher_spec.rb b/spec/unit/comparison_matcher_spec.rb index 1d751c2..93041ed 100644 --- a/spec/unit/comparison_matcher_spec.rb +++ b/spec/unit/comparison_matcher_spec.rb @@ -94,6 +94,28 @@ def clamp_fast(num, min, max) }.to raise_error(/expected given block to perform faster than comparison block by at_most \d+ times, but performed faster by \d+.\d+ times/) end end + + context 'with both at_least and at_most count' do + it "passes if the block performance falls in to the range" do + expect { 1 << 1 }.to perform_faster_than { 'x' * 10 * 1024 }.at_least(2).at_most(125) + end + + it "fails if the block performance ratio is higher then indicated by at_least" do + expect { + expect { 1 << 1 } + .to perform_faster_than { 'x' * 10 * 1024 } + .at_least(2).at_most(15).times + }.to raise_error(/expected given block to perform faster than comparison block by at_least \d+ times and by at_most \d+ times, but performed faster by \d+.\d+ times/) + end + + it "fails if the block performance ratio is lower then indicated by at_most" do + expect { + expect { + 1 << 1 + }.to perform_faster_than { 'x' * 10 * 1024 }.at_least(200).at_most(300).times + }.to raise_error(/expected given block to perform faster than comparison block by at_least \d+ times and by at_most \d+ times, but performed faster by \d+.\d+ times/) + end + end end context "expect { ... }.not_to perform_faster_than(...)" do diff --git a/spec/unit/perform_under_spec.rb b/spec/unit/perform_under_spec.rb index 93675d8..64fe4b9 100644 --- a/spec/unit/perform_under_spec.rb +++ b/spec/unit/perform_under_spec.rb @@ -23,7 +23,7 @@ }.to raise_error(/Repeat value: 0 needs to be greater than 0/) end - context "expect { ... }.to perfom_under(...).sample" do + context "expect { ... }.to perform_under(...).sample" do it "passes if the block performs under threshold" do expect { 'x' * 1024 * 10 @@ -46,7 +46,7 @@ }.to_not perform_under(0.001).sample(2) end - it "fails if the block perfoms under threshold" do + it "fails if the block performs under threshold" do expect { expect { 'x' * 1024 * 1024 * 10