require "benchmark"
#require "rspec"

module RSpec
  module Benchmark
    class Result
      attr_accessor :slowest, :fastest, :average, :elapsed

      def initialize(elapsed)
        @elapsed = elapsed
        @slowest = elapsed.max
        @fastest = elapsed.min
        @average = elapsed.inject(0) {|b, t| b + t} / elapsed.size
      end

      def to_s
        "[average: #{average}, slowest: #{slowest}, fastest: #{fastest}]"
      end
    end

    # Run a given block and calculate the average execution time.
    # The block will be executed 1 times by default.
    #
    #   benchmark { do something }
    #   benchmark(100) { do something }
    #
    def benchmark(times = 1, &block)
      elapsed = (1..times).collect do
        GC.start
        ::Benchmark.realtime(&block) * times
      end

      Result.new(elapsed)
    end
  end
end

# Check if the slowest execution is less than expected.
#
#   it "should do something fast" do
#     benchmark { do something }.should be_faster_than(1.3)
#   end
#
RSpec::Matchers.define :be_faster_than do |expected|
  match do |result|
    result.slowest < expected
  end
end

# Check if the slowest execution is greater than expected.
#
#   it "should do something slow" do
#     benchmark { do something }.should_not be_slower_than(1.3)
#   end
#
RSpec::Matchers.define :be_slower_than do |expected|
  match do |result|
    result.slowest > expected
  end
end

# Check if the execution average is close to expected.
#
#   it "should do something average time" do
#     benchmark { do something }.should be_on_average(1.3, 0.01)
#   end
#
RSpec::Matchers.define :be_on_average do |expected, delta|
  match do |result|
    (result.average - expected).abs < delta
  end
end

# Include matchers and <tt>benchmark</tt> method into RSpec context.
RSpec.configure do |config|
  config.include(RSpec::Benchmark)
end

=begin
if ARGV[0] == __FILE__
  describe RSpec::Benchmark do
    before do
      GC.stub(:start)
    end

    it "should be faster than expected" do
      stub(:slowest => 0.01).should be_faster_than(0.5)
    end

    it "should not be faster than expected" do
      stub(:slowest => 2).should_not be_faster_than(0.5)
    end

    it "should be slower than expected" do
      stub(:slowest => 2).should be_slower_than(0.5)
    end

    it "should not be slower than expected" do
      stub(:slowest => 0.5).should_not be_slower_than(2)
    end

    it "should be on average" do
      stub(:average => 0.51).should be_on_average(0.5, 0.019)
      stub(:average => 0.49).should be_on_average(0.5, 0.019)
    end

    it "should call garbage collector" do
      GC.should_receive(:start).exactly(5).times
      benchmark(5) { true }
    end

    it "should return result with collected data" do
      result = benchmark(5) { true }

      result.average.should be_kind_of(Float)
      result.slowest.should be_kind_of(Float)
      result.fastest.should be_kind_of(Float)
    end

    it "should run block" do
      object = mock
      object.should_receive(:run).exactly(1000).times

      benchmark { object.run }
    end

    it "should run block with custom range" do
      object = mock
      object.should_receive(:run).exactly(3).times

      benchmark(3) { object.run }
    end
  end
end
=end