Persistent expectations
An RSpec example usually consists of some code to set up the situation being tested, and some code called an expectation that defines the expected outcome. As discussed in the Pins page, to write an expectation that the main output pin is driving high you could write:
expect(main_output).to be_driving_high
This is fine, but it is not a great way to test firmware because (unless you put it in a loop or method) it only runs once, at one particular cycle of the simulation. It will not catch any accidental glitches on the main output pin that occur at a later time.
RPicSim helps to address this by adding a new feature to RSpec examples called “persistent expectations”. Persistent expectations are implemented in the module RPicSim::RSpec::PersistentExpectations, which is part of RPicSim's RSpec integration.
Usage
To set a persistent expectation, call the expecting method inside an RSpec example or a before/after hook:
expecting main_output => be_driving_high
The argument to expecting is a hash with the objects being
tested as the keys, and the matchers they are being tested against as the
values. You can specify multiple persistent expectations on different
objects:
expecting main_output => be_driving_high, error_output => be_drving_low
You can not specify multiple persistent expectations that apply to the same object. If you specify a persistent expectation for an object that already had one, the latest one you specify will override the previous one.
To remove a persistent expectation, specify a matcher of nil:
expecting main_output => nil
If expecting is given a block, expectations will only be valid
for the duration of the block:
# Verify that the main output stays high for 10 cycles.
expecting main_output => be_driving_high do
  # The expectation will be checked within the block.
  run_cycles 10
end
# The expectation will not be checked here.
The persistent expectations will not be checked immediately when they are
added, but they will be checked after every step of the simulation. You can
also check them at any time by calling check_expecations
inside your RSPec example.
Persistent expectations, when combined with RSpec's
satisfy matcher, are very powerful. If counter is
a variable in your simulation, you could use this code
to ensure that counter never goes above 120:
expecting counter => satisfy { |c| c.value <= 120 }
Persistent expectations are implemented in a straightforward way: the expectations are stored in a hash that is an instance variable of the RSpec example, and the expectations are checked after every step via a hook that is registered with RPicSim::Sim#every_step when the simulation is started.
Example
The following RSpec example tests that the main output pin is held low (after giving the device some time to start up), but then it goes high after the main input goes high:
it "mirrors the main input onto the main output pin" do
  run_cycles 120    # Give the device time to start up.
  expecting main_output => be_driving_low
  run_cycles 800
  main_input.set true
  # Turn off the persistent expectation temporarily to give the device
  # time to detect the change in the input.
  expecting main_output => nil
  run_cycles 200
  expecting main_output => be_driving_high
  run_cycles 800
end
In the above example, we removed the persistent expectation on
main_output temporarily because the device was in a
transitionary period and we didn't know exactly when the transition
would happen. We chose to stop monitoring the pin for the duration of the
transition and then start monitoring it later, at which point we expect the
pin to be in its new state. We can rewrite that using block arguments
instead of explicitly clearing the expectation:
it "mirrors the main input onto the main output pin" do
  run_cycles 120    # Give the device time to start up.
  expecting main_output => be_driving_low do
    run_cycles 800
  end
  main_input.set true
  # Give the device time to detect the change in the input.
  run_cycles 200
  expecting main_output => be_driving_high do
    run_cycles 800
  end
end
If you need to repeat this patten many times in your tests, you might
consider adding a method in your spec_helper.rb to help you do
it:
def transition(opts={})
  opts = opts.dup
  cycles = opts.delete(:cycles) || 50
  opts.keys.each { |k| expectations.delete k }
  run_cycles cycles
  expectations.merge! opts
  check_expectations
end
Then the test above could become:
it "mirrors the main input onto the main output pin" do
  run_cycles 120    # Give the device time to start up.
  expecting main_output => be_driving_low
  run_cycles 800
  main_input.set true
  transition main_output => be_driving_high
  run_cycles 800
end
The transition method above does not check anything about the
main output during the transition time, so unfortunately it might miss any
glitches that happen during that time. Also, it is not very general. For
these reasons, it has not been integrated into the RPicSim code and you
will need to copy it to your spec_helper.rb file yourself if
you want to use it.