Cordon Sanitaire

From Wikipedia:

Cordon sanitaire -- or quarantine line -- is a French phrase that, literally translated, means "sanitary cordon". Though in French it originally denoted a barrier implemented to stop the spread of disease, it has often been used in English in a metaphorical sense to refer to attempts to prevent the spread of an ideology deemed unwanted or dangerous, such as the containment policy adopted by George F. Kennan against the Soviet Union.

I've never been a big fan of the way RSpec adds #should and #should_not to Kernel, but until recently I'd never been able to articulate why. Then I worked on a project that became Kookaburra, and I found a specific reason to be annoyed.

Basically, putting #should on all objects gives you the freedom to shoot yourself in the foot^H^H^H put RSpec expectations anywhere, not just inside an #it block. So, I went looking for a way to make #should explode if it was called outside a specific context.

After several false starts and horrible ideas, I've got something that actually isn't too bad.

Cordon makes specs look like this:

describe "a toggle switch" do
  it "should be on when flipped up" do
    toggle = ToggleSwitch.new
    toggle.flip_up
    <em><strong>assert_that</strong></em>(toggle).should be_on
  end
end

Cordon lets you declare certain methods as "off-limits" to casual code. Any calls to blacklisted methods will raise a Cordon::Violation exception -- unless they're called on the object thats passed as the single argument to Object#assert_that, as shown above. What #assert_that does is temporarily add its argument to a whitelist. This effectively gives that object permission to call any blacklisted method once (and only once!).

Currently, Cordon ships with shorthand configuration items for RSpec and MiniTest::Spec. You can set these up like so:

RSpec

require 'rspec'
Cordon.embargo :rspec

MiniTest::Spec

require 'minitest/autorun'
Cordon.embargo :minitest_spec

In both examples, note that Cordon's .embargo method must be called after the test framework has been loaded.

TODO

  • Write integration macros (and tests, obviously) for various spec frameworks:
    • RSpec
    • MiniTest::Spec
    • Yoda (Actually, Yoda appears to abuse Ruby syntax badly enough that it may not work with Cordon.)
    • ?
  • Add a declarative API to customize the name of the function that wraps assertions
  • RDoc
  • Add a "detection mode" which rescues Cordon::Violation, records the backtrace, and reports all violations in a Kernel#at_exit callback. (UPDATE: added Cordon.monitor for frameworks, and Cordon.watchlist for methods, but didn't get #at_exit working yet.)
  • MAYBE: Figure out how to raise Cordon::Violation only when in (or not in?) a certain calling context (this could be hairy), which might make #assert_that optional
  • probably some other stuff I can't think of at the moment

Contributing to Cordon

  • Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
  • Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
  • Fork the project.
  • Start a feature/bugfix branch.
  • Commit and push until you are happy with your contribution.
  • Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
  • Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.

Copyright (c) 2012 Sam Livingston-Gray. See LICENSE.txt for further details.