# encoding: utf-8
# This file is part of ruby-flores.
# Copyright (C) 2015 Jordan Sissel
# 
require "flores/namespace"
require "flores/rspec"
require "flores/random"

# This module adds helpers useful in doing stress testing within rspec.
#
# The number of iterations in a stress test is random.
#
# By way of example, let's have a silly test for adding two positive numbers and expecting
# the result to not be negative:
#
#     describe "Addition" do
#       context "of two positive numbers" do
#         let(:a) { Flores::Random.number(1..10000) }
#         let(:b) { Flores::Random.number(1..10000) }
#         subject { a + b }
#
#         # Note the use of 'stress_it' here!
#         stress_it "should be greater than zero" do
#           expect(subject).to(be > 0)
#         end
#       end
#     end
#
#  Running this:
#
#     % rspec 
#     <lots of dots>
#
#     Finished in 0.45412 seconds (files took 0.32963 seconds to load)
#     4795 examples, 0 failures
#
#  In this way, instead of testing 1 fixed case or 1 randomized case, we test
#  *many* cases in one rspec run.
module Flores::RSpec::Stress
  # Wraps `it` and runs the block many times. Each run has will clear the `let` cache.
  #
  # The implementation of this is roughly that the given block will be run N times within an `it`:
  #
  #     stress_it_internal "my test" do
  #        expect(...)
  #      end
  #
  # is roughly equivalent to
  #
  #     it "my test" do
  #       1000.times do
  #         expect(...)
  #         __memoized.clear
  #       end
  #     end
  #
  # The intent of this is to allow randomized testing for fuzzing and stress testing
  # of APIs to help find edge cases and weird behavior.
  #
  # The default number of iterations is randomly selected between 1 and 1000 inclusive
  def stress_it_internal(name, options = {}, &block)
    stress__iterations = Flores::Random.iterations(options.delete(:stress_iterations) || Flores::RSpec::DEFAULT_ITERATIONS)
    it(name, options) do
      # Run the block of an example many times
      stress__iterations.each do
        # Run the block within 'it' scope
        instance_eval(&block)

        # clear the internal rspec `let` cache this lets us run a test
        # repeatedly with fresh `let` evaluations.
        # Reference: https://github.com/rspec/rspec-core/blob/5fc29a15b9af9dc1c9815e278caca869c4769767/lib/rspec/core/memoized_helpers.rb#L124-L127
        __memoized.clear
      end
    end # it ...
  end # def stress_it_internal

  # Generate a random number of copies of a given example.
  # The idea is to take 1 `it` and run it N times to help tease out failures.
  # Of course, the teasing requires you have randomized `let` usage, for example:
  #
  #     let(:number) { Flores::Random.number(0..200) }
  #     it "should be less than 100" do
  #       expect(number).to(be < 100)
  #     end
  #
  # This creates N (random) copies of your spec example. Using `stress_it` is
  # preferred instead of `stress_it_internal` because this method will cause
  # before, after, and around clauses to be invoked correctly.
  def stress_it(name, *args, &block)
    Flores::Random.iterations(Flores::RSpec.iterations).each do
      it(name, *args, &block)
    end # each
  end  # def stress_it
end # Flores::RSpec::Stress
