[macruby-changes] [876] MacRuby/branches/experimental

source_changes at macosforge.org source_changes at macosforge.org
Wed Mar 11 16:10:17 PDT 2009


Revision: 876
          http://trac.macosforge.org/projects/ruby/changeset/876
Author:   eloy.de.enige at gmail.com
Date:     2009-03-11 16:10:17 -0700 (Wed, 11 Mar 2009)
Log Message:
-----------
Imported mpspec 65a5dbf1063759eafc42947f697329c638b9a2ce. See http://github.com/rubyspec/mspec/tree/master

Added Paths:
-----------
    MacRuby/branches/experimental/mspec/
    MacRuby/branches/experimental/mspec/LICENSE
    MacRuby/branches/experimental/mspec/README
    MacRuby/branches/experimental/mspec/Rakefile
    MacRuby/branches/experimental/mspec/bin/
    MacRuby/branches/experimental/mspec/bin/mkspec
    MacRuby/branches/experimental/mspec/bin/mkspec.bat
    MacRuby/branches/experimental/mspec/bin/mspec
    MacRuby/branches/experimental/mspec/bin/mspec-ci
    MacRuby/branches/experimental/mspec/bin/mspec-ci.bat
    MacRuby/branches/experimental/mspec/bin/mspec-run
    MacRuby/branches/experimental/mspec/bin/mspec-run.bat
    MacRuby/branches/experimental/mspec/bin/mspec-tag
    MacRuby/branches/experimental/mspec/bin/mspec-tag.bat
    MacRuby/branches/experimental/mspec/bin/mspec.bat
    MacRuby/branches/experimental/mspec/lib/
    MacRuby/branches/experimental/mspec/lib/mspec/
    MacRuby/branches/experimental/mspec/lib/mspec/commands/
    MacRuby/branches/experimental/mspec/lib/mspec/commands/mkspec.rb
    MacRuby/branches/experimental/mspec/lib/mspec/commands/mspec-ci.rb
    MacRuby/branches/experimental/mspec/lib/mspec/commands/mspec-run.rb
    MacRuby/branches/experimental/mspec/lib/mspec/commands/mspec-tag.rb
    MacRuby/branches/experimental/mspec/lib/mspec/commands/mspec.rb
    MacRuby/branches/experimental/mspec/lib/mspec/expectations/
    MacRuby/branches/experimental/mspec/lib/mspec/expectations/expectations.rb
    MacRuby/branches/experimental/mspec/lib/mspec/expectations/should.rb
    MacRuby/branches/experimental/mspec/lib/mspec/expectations.rb
    MacRuby/branches/experimental/mspec/lib/mspec/guards/
    MacRuby/branches/experimental/mspec/lib/mspec/guards/background.rb
    MacRuby/branches/experimental/mspec/lib/mspec/guards/bug.rb
    MacRuby/branches/experimental/mspec/lib/mspec/guards/compliance.rb
    MacRuby/branches/experimental/mspec/lib/mspec/guards/conflict.rb
    MacRuby/branches/experimental/mspec/lib/mspec/guards/endian.rb
    MacRuby/branches/experimental/mspec/lib/mspec/guards/extensions.rb
    MacRuby/branches/experimental/mspec/lib/mspec/guards/guard.rb
    MacRuby/branches/experimental/mspec/lib/mspec/guards/noncompliance.rb
    MacRuby/branches/experimental/mspec/lib/mspec/guards/platform.rb
    MacRuby/branches/experimental/mspec/lib/mspec/guards/quarantine.rb
    MacRuby/branches/experimental/mspec/lib/mspec/guards/runner.rb
    MacRuby/branches/experimental/mspec/lib/mspec/guards/superuser.rb
    MacRuby/branches/experimental/mspec/lib/mspec/guards/support.rb
    MacRuby/branches/experimental/mspec/lib/mspec/guards/tty.rb
    MacRuby/branches/experimental/mspec/lib/mspec/guards/version.rb
    MacRuby/branches/experimental/mspec/lib/mspec/guards.rb
    MacRuby/branches/experimental/mspec/lib/mspec/helpers/
    MacRuby/branches/experimental/mspec/lib/mspec/helpers/argv.rb
    MacRuby/branches/experimental/mspec/lib/mspec/helpers/bignum.rb
    MacRuby/branches/experimental/mspec/lib/mspec/helpers/const_lookup.rb
    MacRuby/branches/experimental/mspec/lib/mspec/helpers/environment.rb
    MacRuby/branches/experimental/mspec/lib/mspec/helpers/fixture.rb
    MacRuby/branches/experimental/mspec/lib/mspec/helpers/flunk.rb
    MacRuby/branches/experimental/mspec/lib/mspec/helpers/io.rb
    MacRuby/branches/experimental/mspec/lib/mspec/helpers/language_version.rb
    MacRuby/branches/experimental/mspec/lib/mspec/helpers/ruby_exe.rb
    MacRuby/branches/experimental/mspec/lib/mspec/helpers/scratch.rb
    MacRuby/branches/experimental/mspec/lib/mspec/helpers/tmp.rb
    MacRuby/branches/experimental/mspec/lib/mspec/helpers.rb
    MacRuby/branches/experimental/mspec/lib/mspec/matchers/
    MacRuby/branches/experimental/mspec/lib/mspec/matchers/base.rb
    MacRuby/branches/experimental/mspec/lib/mspec/matchers/be_an_instance_of.rb
    MacRuby/branches/experimental/mspec/lib/mspec/matchers/be_ancestor_of.rb
    MacRuby/branches/experimental/mspec/lib/mspec/matchers/be_close.rb
    MacRuby/branches/experimental/mspec/lib/mspec/matchers/be_empty.rb
    MacRuby/branches/experimental/mspec/lib/mspec/matchers/be_false.rb
    MacRuby/branches/experimental/mspec/lib/mspec/matchers/be_kind_of.rb
    MacRuby/branches/experimental/mspec/lib/mspec/matchers/be_nil.rb
    MacRuby/branches/experimental/mspec/lib/mspec/matchers/be_true.rb
    MacRuby/branches/experimental/mspec/lib/mspec/matchers/complain.rb
    MacRuby/branches/experimental/mspec/lib/mspec/matchers/eql.rb
    MacRuby/branches/experimental/mspec/lib/mspec/matchers/equal.rb
    MacRuby/branches/experimental/mspec/lib/mspec/matchers/equal_element.rb
    MacRuby/branches/experimental/mspec/lib/mspec/matchers/equal_utf16.rb
    MacRuby/branches/experimental/mspec/lib/mspec/matchers/have_constant.rb
    MacRuby/branches/experimental/mspec/lib/mspec/matchers/have_instance_method.rb
    MacRuby/branches/experimental/mspec/lib/mspec/matchers/have_method.rb
    MacRuby/branches/experimental/mspec/lib/mspec/matchers/have_private_instance_method.rb
    MacRuby/branches/experimental/mspec/lib/mspec/matchers/include.rb
    MacRuby/branches/experimental/mspec/lib/mspec/matchers/match_yaml.rb
    MacRuby/branches/experimental/mspec/lib/mspec/matchers/method.rb
    MacRuby/branches/experimental/mspec/lib/mspec/matchers/output.rb
    MacRuby/branches/experimental/mspec/lib/mspec/matchers/output_to_fd.rb
    MacRuby/branches/experimental/mspec/lib/mspec/matchers/raise_error.rb
    MacRuby/branches/experimental/mspec/lib/mspec/matchers/respond_to.rb
    MacRuby/branches/experimental/mspec/lib/mspec/matchers/stringsymboladapter.rb
    MacRuby/branches/experimental/mspec/lib/mspec/matchers.rb
    MacRuby/branches/experimental/mspec/lib/mspec/mocks/
    MacRuby/branches/experimental/mspec/lib/mspec/mocks/mock.rb
    MacRuby/branches/experimental/mspec/lib/mspec/mocks/object.rb
    MacRuby/branches/experimental/mspec/lib/mspec/mocks/proxy.rb
    MacRuby/branches/experimental/mspec/lib/mspec/mocks.rb
    MacRuby/branches/experimental/mspec/lib/mspec/runner/
    MacRuby/branches/experimental/mspec/lib/mspec/runner/actions/
    MacRuby/branches/experimental/mspec/lib/mspec/runner/actions/debug.rb
    MacRuby/branches/experimental/mspec/lib/mspec/runner/actions/filter.rb
    MacRuby/branches/experimental/mspec/lib/mspec/runner/actions/gdb.rb
    MacRuby/branches/experimental/mspec/lib/mspec/runner/actions/tag.rb
    MacRuby/branches/experimental/mspec/lib/mspec/runner/actions/taglist.rb
    MacRuby/branches/experimental/mspec/lib/mspec/runner/actions/tagpurge.rb
    MacRuby/branches/experimental/mspec/lib/mspec/runner/actions/tally.rb
    MacRuby/branches/experimental/mspec/lib/mspec/runner/actions/timer.rb
    MacRuby/branches/experimental/mspec/lib/mspec/runner/actions.rb
    MacRuby/branches/experimental/mspec/lib/mspec/runner/context.rb
    MacRuby/branches/experimental/mspec/lib/mspec/runner/example.rb
    MacRuby/branches/experimental/mspec/lib/mspec/runner/exception.rb
    MacRuby/branches/experimental/mspec/lib/mspec/runner/filters/
    MacRuby/branches/experimental/mspec/lib/mspec/runner/filters/match.rb
    MacRuby/branches/experimental/mspec/lib/mspec/runner/filters/profile.rb
    MacRuby/branches/experimental/mspec/lib/mspec/runner/filters/regexp.rb
    MacRuby/branches/experimental/mspec/lib/mspec/runner/filters/tag.rb
    MacRuby/branches/experimental/mspec/lib/mspec/runner/filters.rb
    MacRuby/branches/experimental/mspec/lib/mspec/runner/formatters/
    MacRuby/branches/experimental/mspec/lib/mspec/runner/formatters/describe.rb
    MacRuby/branches/experimental/mspec/lib/mspec/runner/formatters/dotted.rb
    MacRuby/branches/experimental/mspec/lib/mspec/runner/formatters/file.rb
    MacRuby/branches/experimental/mspec/lib/mspec/runner/formatters/html.rb
    MacRuby/branches/experimental/mspec/lib/mspec/runner/formatters/method.rb
    MacRuby/branches/experimental/mspec/lib/mspec/runner/formatters/specdoc.rb
    MacRuby/branches/experimental/mspec/lib/mspec/runner/formatters/spinner.rb
    MacRuby/branches/experimental/mspec/lib/mspec/runner/formatters/summary.rb
    MacRuby/branches/experimental/mspec/lib/mspec/runner/formatters/unit.rb
    MacRuby/branches/experimental/mspec/lib/mspec/runner/formatters/yaml.rb
    MacRuby/branches/experimental/mspec/lib/mspec/runner/formatters.rb
    MacRuby/branches/experimental/mspec/lib/mspec/runner/mspec.rb
    MacRuby/branches/experimental/mspec/lib/mspec/runner/object.rb
    MacRuby/branches/experimental/mspec/lib/mspec/runner/shared.rb
    MacRuby/branches/experimental/mspec/lib/mspec/runner/tag.rb
    MacRuby/branches/experimental/mspec/lib/mspec/runner.rb
    MacRuby/branches/experimental/mspec/lib/mspec/utils/
    MacRuby/branches/experimental/mspec/lib/mspec/utils/name_map.rb
    MacRuby/branches/experimental/mspec/lib/mspec/utils/options.rb
    MacRuby/branches/experimental/mspec/lib/mspec/utils/ruby_name.rb
    MacRuby/branches/experimental/mspec/lib/mspec/utils/script.rb
    MacRuby/branches/experimental/mspec/lib/mspec/utils/version.rb
    MacRuby/branches/experimental/mspec/lib/mspec/version.rb
    MacRuby/branches/experimental/mspec/lib/mspec.rb
    MacRuby/branches/experimental/mspec/mspec.gemspec
    MacRuby/branches/experimental/mspec/spec/
    MacRuby/branches/experimental/mspec/spec/commands/
    MacRuby/branches/experimental/mspec/spec/commands/fixtures/
    MacRuby/branches/experimental/mspec/spec/commands/fixtures/four.txt
    MacRuby/branches/experimental/mspec/spec/commands/fixtures/level2/
    MacRuby/branches/experimental/mspec/spec/commands/fixtures/level2/three_spec.rb
    MacRuby/branches/experimental/mspec/spec/commands/fixtures/one_spec.rb
    MacRuby/branches/experimental/mspec/spec/commands/fixtures/three.rb
    MacRuby/branches/experimental/mspec/spec/commands/fixtures/two_spec.rb
    MacRuby/branches/experimental/mspec/spec/commands/mkspec_spec.rb
    MacRuby/branches/experimental/mspec/spec/commands/mspec_ci_spec.rb
    MacRuby/branches/experimental/mspec/spec/commands/mspec_run_spec.rb
    MacRuby/branches/experimental/mspec/spec/commands/mspec_spec.rb
    MacRuby/branches/experimental/mspec/spec/commands/mspec_tag_spec.rb
    MacRuby/branches/experimental/mspec/spec/expectations/
    MacRuby/branches/experimental/mspec/spec/expectations/expectations_spec.rb
    MacRuby/branches/experimental/mspec/spec/expectations/should_spec.rb
    MacRuby/branches/experimental/mspec/spec/guards/
    MacRuby/branches/experimental/mspec/spec/guards/background_spec.rb
    MacRuby/branches/experimental/mspec/spec/guards/bug_spec.rb
    MacRuby/branches/experimental/mspec/spec/guards/compliance_spec.rb
    MacRuby/branches/experimental/mspec/spec/guards/conflict_spec.rb
    MacRuby/branches/experimental/mspec/spec/guards/endian_spec.rb
    MacRuby/branches/experimental/mspec/spec/guards/extensions_spec.rb
    MacRuby/branches/experimental/mspec/spec/guards/guard_spec.rb
    MacRuby/branches/experimental/mspec/spec/guards/noncompliance_spec.rb
    MacRuby/branches/experimental/mspec/spec/guards/platform_spec.rb
    MacRuby/branches/experimental/mspec/spec/guards/quarantine_spec.rb
    MacRuby/branches/experimental/mspec/spec/guards/runner_spec.rb
    MacRuby/branches/experimental/mspec/spec/guards/superuser_spec.rb
    MacRuby/branches/experimental/mspec/spec/guards/support_spec.rb
    MacRuby/branches/experimental/mspec/spec/guards/tty_spec.rb
    MacRuby/branches/experimental/mspec/spec/guards/version_spec.rb
    MacRuby/branches/experimental/mspec/spec/helpers/
    MacRuby/branches/experimental/mspec/spec/helpers/argv_spec.rb
    MacRuby/branches/experimental/mspec/spec/helpers/bignum_spec.rb
    MacRuby/branches/experimental/mspec/spec/helpers/const_lookup_spec.rb
    MacRuby/branches/experimental/mspec/spec/helpers/environment_spec.rb
    MacRuby/branches/experimental/mspec/spec/helpers/fixture_spec.rb
    MacRuby/branches/experimental/mspec/spec/helpers/flunk_spec.rb
    MacRuby/branches/experimental/mspec/spec/helpers/io_spec.rb
    MacRuby/branches/experimental/mspec/spec/helpers/language_version_spec.rb
    MacRuby/branches/experimental/mspec/spec/helpers/ruby_exe_spec.rb
    MacRuby/branches/experimental/mspec/spec/helpers/scratch_spec.rb
    MacRuby/branches/experimental/mspec/spec/helpers/tmp_spec.rb
    MacRuby/branches/experimental/mspec/spec/matchers/
    MacRuby/branches/experimental/mspec/spec/matchers/base_spec.rb
    MacRuby/branches/experimental/mspec/spec/matchers/be_an_instance_of_spec.rb
    MacRuby/branches/experimental/mspec/spec/matchers/be_ancestor_of_spec.rb
    MacRuby/branches/experimental/mspec/spec/matchers/be_close_spec.rb
    MacRuby/branches/experimental/mspec/spec/matchers/be_empty_spec.rb
    MacRuby/branches/experimental/mspec/spec/matchers/be_false_spec.rb
    MacRuby/branches/experimental/mspec/spec/matchers/be_kind_of_spec.rb
    MacRuby/branches/experimental/mspec/spec/matchers/be_nil_spec.rb
    MacRuby/branches/experimental/mspec/spec/matchers/be_true_spec.rb
    MacRuby/branches/experimental/mspec/spec/matchers/complain_spec.rb
    MacRuby/branches/experimental/mspec/spec/matchers/eql_spec.rb
    MacRuby/branches/experimental/mspec/spec/matchers/equal_element_spec.rb
    MacRuby/branches/experimental/mspec/spec/matchers/equal_spec.rb
    MacRuby/branches/experimental/mspec/spec/matchers/equal_utf16_spec.rb
    MacRuby/branches/experimental/mspec/spec/matchers/have_constant_spec.rb
    MacRuby/branches/experimental/mspec/spec/matchers/have_instance_method_spec.rb
    MacRuby/branches/experimental/mspec/spec/matchers/have_method_spec.rb
    MacRuby/branches/experimental/mspec/spec/matchers/have_private_instance_method_spec.rb
    MacRuby/branches/experimental/mspec/spec/matchers/include_spec.rb
    MacRuby/branches/experimental/mspec/spec/matchers/match_yaml_spec.rb
    MacRuby/branches/experimental/mspec/spec/matchers/output_spec.rb
    MacRuby/branches/experimental/mspec/spec/matchers/output_to_fd_spec.rb
    MacRuby/branches/experimental/mspec/spec/matchers/raise_error_spec.rb
    MacRuby/branches/experimental/mspec/spec/matchers/respond_to_spec.rb
    MacRuby/branches/experimental/mspec/spec/matchers/stringsymboladapter_spec.rb
    MacRuby/branches/experimental/mspec/spec/mocks/
    MacRuby/branches/experimental/mspec/spec/mocks/mock_spec.rb
    MacRuby/branches/experimental/mspec/spec/mocks/proxy_spec.rb
    MacRuby/branches/experimental/mspec/spec/runner/
    MacRuby/branches/experimental/mspec/spec/runner/actions/
    MacRuby/branches/experimental/mspec/spec/runner/actions/debug_spec.rb
    MacRuby/branches/experimental/mspec/spec/runner/actions/filter_spec.rb
    MacRuby/branches/experimental/mspec/spec/runner/actions/gdb_spec.rb
    MacRuby/branches/experimental/mspec/spec/runner/actions/tag_spec.rb
    MacRuby/branches/experimental/mspec/spec/runner/actions/taglist_spec.rb
    MacRuby/branches/experimental/mspec/spec/runner/actions/tagpurge_spec.rb
    MacRuby/branches/experimental/mspec/spec/runner/actions/tally_spec.rb
    MacRuby/branches/experimental/mspec/spec/runner/actions/timer_spec.rb
    MacRuby/branches/experimental/mspec/spec/runner/context_spec.rb
    MacRuby/branches/experimental/mspec/spec/runner/example_spec.rb
    MacRuby/branches/experimental/mspec/spec/runner/exception_spec.rb
    MacRuby/branches/experimental/mspec/spec/runner/filters/
    MacRuby/branches/experimental/mspec/spec/runner/filters/a.yaml
    MacRuby/branches/experimental/mspec/spec/runner/filters/b.yaml
    MacRuby/branches/experimental/mspec/spec/runner/filters/match_spec.rb
    MacRuby/branches/experimental/mspec/spec/runner/filters/profile_spec.rb
    MacRuby/branches/experimental/mspec/spec/runner/filters/regexp_spec.rb
    MacRuby/branches/experimental/mspec/spec/runner/filters/tag_spec.rb
    MacRuby/branches/experimental/mspec/spec/runner/formatters/
    MacRuby/branches/experimental/mspec/spec/runner/formatters/describe_spec.rb
    MacRuby/branches/experimental/mspec/spec/runner/formatters/dotted_spec.rb
    MacRuby/branches/experimental/mspec/spec/runner/formatters/file_spec.rb
    MacRuby/branches/experimental/mspec/spec/runner/formatters/html_spec.rb
    MacRuby/branches/experimental/mspec/spec/runner/formatters/method_spec.rb
    MacRuby/branches/experimental/mspec/spec/runner/formatters/specdoc_spec.rb
    MacRuby/branches/experimental/mspec/spec/runner/formatters/spinner_spec.rb
    MacRuby/branches/experimental/mspec/spec/runner/formatters/summary_spec.rb
    MacRuby/branches/experimental/mspec/spec/runner/formatters/unit_spec.rb
    MacRuby/branches/experimental/mspec/spec/runner/formatters/yaml_spec.rb
    MacRuby/branches/experimental/mspec/spec/runner/mspec_spec.rb
    MacRuby/branches/experimental/mspec/spec/runner/shared_spec.rb
    MacRuby/branches/experimental/mspec/spec/runner/tag_spec.rb
    MacRuby/branches/experimental/mspec/spec/runner/tags.txt
    MacRuby/branches/experimental/mspec/spec/spec_helper.rb
    MacRuby/branches/experimental/mspec/spec/utils/
    MacRuby/branches/experimental/mspec/spec/utils/name_map_spec.rb
    MacRuby/branches/experimental/mspec/spec/utils/options_spec.rb
    MacRuby/branches/experimental/mspec/spec/utils/script_spec.rb
    MacRuby/branches/experimental/mspec/spec/utils/version_spec.rb

Added: MacRuby/branches/experimental/mspec/LICENSE
===================================================================
--- MacRuby/branches/experimental/mspec/LICENSE	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/LICENSE	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,22 @@
+Copyright (c) 2008 Engine Yard, Inc. All rights reserved.
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.

Added: MacRuby/branches/experimental/mspec/README
===================================================================
--- MacRuby/branches/experimental/mspec/README	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/README	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,197 @@
+= Overview
+
+MSpec is a specialized framework that is syntax-compatible with RSpec for
+basic things like +describe+, +it+ blocks and +before+, +after+ actions. MSpec
+contains additional features that assist in writing the RubySpecs used by
+multiple Ruby implementations.
+
+MSpec attempts to use the simplest Ruby language features so that beginning
+Ruby implementations can run the Ruby specs. So, for example, there is not
+great concern given to constant clashes. Namespacing (or module scoping) is
+not used because implementing this correctly took a significant amount of work
+in Rubinius and it is likely that other implementations would also face
+difficulties.
+
+MSpec is not intended as a replacement for RSpec. MSpec attempts to provide a
+subset of RSpec's features in some cases and a superset in others. It does not
+provide all the matchers, for instance. However, MSpec provides several
+extensions to facilitate writing the Ruby specs in a manner compatible with
+multiple Ruby implementations.
+
+First, MSpec offers a set of guards to control execution of the specs. These
+guards not only enable or disable execution but also annotate the specs with
+additional information about why they are run or not run. Second, MSpec
+provides a different shared spec implementation specifically designed to ease
+writing specs for the numerous aliased methods in Ruby. The MSpec shared spec
+implementation should not conflict with RSpec's own shared behavior facility.
+Third, MSpec provides various helper methods to simplify some specs, for
+example, creating temporary file names. Finally, MSpec has several specialized
+runner scripts that includes a configuration facility with a default project
+file and user-specific overrides.
+
+Caveats:
+
+* Use RSpec to run the MSpec specs. There are no plans currently to make
+  the MSpec specs runnable by MSpec.
+* Don't mock the #hash method as MSpec's Mock implementation uses Hash
+  internally. This can be replaced if necessary, but at this point there is no
+  compelling need to do so.
+
+
+== Architecture
+
+
+== Matchers
+
+Matchers are additional aids for the verification process. The default
+is of course to #should or #should_not using the #== operator and its
+friends but the matchers add a new set of 'operators' to help in the
+task. They reside in `mspec/matchers/`. There are two broad categories,
+those that apply to an individual object and those that apply to a
+block:
+
+=== Object
+
+- `base` implements the standard #==, #< #<= #>= #> and #=~ with their
+  normal semantics for the objects that you invoke them on.
+
+- `be_ancestor_of` is equivalent to checking `obj.ancestors.include?`.
+
+- `be_close` is a "delta" for floating-point math. Due to the very
+  nature of it, floating-point comparisons should never be treated as
+  exact. By default the tolerance is 0.00003 but it can be altered if
+  so desired. So `0.23154.should be_close(0.23157)` would succeed
+  (which is usually close enough for floating point unless you are
+  doing some scientific computing.)
+
+- `be_empty` checks `obj.empty?`
+
+- `be_kind_of` is equivalent to `obj.kind_of?`
+
+- `include` is `obj.include?`
+
+=== Block
+
+All of these should be applied to a block created with `lambda` or `proc`:
+
+- `complain` is probably clearer stated as `lambda {...}.should complain`;
+  it checks that the block issues a warning. The message can be checked
+  against either a String or a Regexp.
+
+- `output` checks that the block produces the given output (stdout as well
+  as stderr, in that order) matched either to a String or a Regexp. This one
+  uses overrides so if that is a problem (for e.g. speccing Readline or
+  something) see below.
+
+- `output_to_fd` is a lower-level version and actually verifies that output
+  to a certain file descriptor is correct whether from an in-/output stream
+  or an actual file. Also can check with either a String or a Regexp.
+
+- `raise_error` verifies the exception type (if any) raised by the block it
+  is associated with. The exception class can be given for finer-grained
+  control (inheritance works normally so Exception would catch everything.)
+
+== Nested 'describe' blocks
+
+MSpec supports nesting one 'describe' block inside another. The examples in
+the nested block are evaluated with all the before/after blocks of all the
+containing 'describe' blocks. The following example illustrates this:
+
+describe "Some#method" do
+  before :each do
+    @obj = 1
+  end
+
+  describe "when passed String" do
+    before :each do
+      @meth = :to_s
+    end
+
+    it "returns false" do
+      # when this example is evaluated, @obj = 1 and @meth = :to_s
+    end
+  end
+end
+
+The output when using the SpecdocFormatter (selected with -fs to the runners)
+will be as follows:
+
+Some#method when passed String
+- returns false
+
+
+== Shared 'describe' blocks
+
+MSpec supports RSpec-style shared 'describe' blocks. MSpec also provides a
+convenience method to assist in writing specs for the numerous aliased methods
+that Ruby provides. The following example illustrates shared blocks:
+
+describe :someclass_some_method, :shared => true do
+  it "does something" do
+  end
+end
+
+describe "SomeClass#some_method" do
+  it_should_behave_like "someclass_some_method"
+end
+
+The first argument to 'describe' for a shared block is an object that
+duck-types as a String. The representation of the object must be unique. This
+example uses a symbol. This was the convention for the previous facility that
+MSpec provided for aliased method (#it_behaves_like). However, this convention
+is not set in stone (but the uniqueness requirement is). Note that the
+argument to the #it_should_behave_like is a String because at this time RSpec
+will not find the shared block by the symbol.
+
+MSpec continues to support the #it_behaves_like convenience method for
+specifying aliased methods. The syntax is as follows:
+
+it_behaves_like :symbol_matching_shared_describe, :method [, :object]
+
+describe :someclass_some_method, :shared => true do
+  it "returns true" do
+    obj.send(@method).should be_true
+  end
+
+  it "returns something else" do
+    @object.send(@method).should be_something_else
+  end
+end
+
+# example #1
+describe "SomeClass#some_method" do
+  it_behaves_like :someclass_some_method, :other_method
+end
+
+# example #2
+describe "SomeOtherClass#some_method" do
+  it_behaves_like :someclass_some_method, :some_method, OtherClass
+end
+
+The first form above (#1) is used for typical aliases. That is, methods with
+different names on the same class that behave identically. The
+#it_behaves_like helper creates a before(:all) block that sets @method to
+:other_method. The form of the first example block in the shared block
+illustrates the typical form of a spec for an aliased method.
+
+The second form above (#2) is used for methods on different classes that are
+essentially aliases, even though Ruby does not provide a syntax for specifying
+such methods as aliases. Examples are the methods on File, FileTest, and
+File::Stat. In this case, the #it_behaves_like helper sets both @method and
+ at object in the before(:all) block (@method = :some_method, @object =
+OtherClass in this example).
+
+For shared specs that fall outside of either of these two narrow categories,
+use nested or shared 'describe' blocks as appropriate and use the
+#it_should_behave_like method directly.
+
+== Guards
+
+Since Ruby is not completely isolated from its platform or execution environment, the spec files may contain guards: conditions placed around a spec or a set of specs to enable or disable them. 
+
+You can find an overview of the current guards and their usage in: http://rubyspec.org/wiki/mspec/Guards .
+
+== Helpers
+
+
+== Runners

Added: MacRuby/branches/experimental/mspec/Rakefile
===================================================================
--- MacRuby/branches/experimental/mspec/Rakefile	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/Rakefile	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,47 @@
+require 'rubygems'
+require 'spec/rake/spectask'
+require 'rake/gempackagetask'
+
+$:.unshift File.expand_path(File.dirname(__FILE__) + '/lib')
+require 'lib/mspec/version'
+
+Spec::Rake::SpecTask.new
+
+task :default => :spec
+
+
+spec = Gem::Specification.new do |s|
+  s.name                      = %q{mspec}
+  s.version                   = MSpec::VERSION.to_s
+
+  s.specification_version     = 2 if s.respond_to? :specification_version=
+
+  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
+  s.authors                   = ["Brian Ford"]
+  s.date                      = %q{2009-3-1}
+  s.email                     = %q{bford at engineyard.com}
+  s.has_rdoc                  = true
+  s.extra_rdoc_files          = %w[ README LICENSE ]
+  s.executables               = ["mkspec", "mspec", "mspec-ci", "mspec-run", "mspec-tag"]
+  s.files                     = FileList[ '{bin,lib,spec}/**/*.{yaml,txt,rb}', 'Rakefile', *s.extra_rdoc_files ]
+  s.homepage                  = %q{http://rubyspec.org}
+  s.rubyforge_project         = 'http://rubyforge.org/projects/mspec'
+  s.require_paths             = ["lib"]
+  s.rubygems_version          = %q{1.1.1}
+  s.summary                   = <<EOS
+MSpec is a specialized framework that is syntax-compatible
+with RSpec for basic things like describe, it blocks and
+before, after actions.
+
+MSpec contains additional features that assist in writing
+the RubySpecs used by multiple Ruby implementations. Also,
+MSpec attempts to use the simplest Ruby language features
+so that beginning Ruby implementations can run it.
+EOS
+
+  s.rdoc_options << '--title' << 'MSpec Gem' <<
+                   '--main' << 'README' <<
+                   '--line-numbers'
+end
+
+Rake::GemPackageTask.new(spec){ |pkg| pkg.gem_spec = spec }

Added: MacRuby/branches/experimental/mspec/bin/mkspec
===================================================================
--- MacRuby/branches/experimental/mspec/bin/mkspec	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/bin/mkspec	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,7 @@
+#!/usr/bin/env ruby
+
+$:.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
+
+require 'mspec/commands/mkspec'
+
+MkSpec.main


Property changes on: MacRuby/branches/experimental/mspec/bin/mkspec
___________________________________________________________________
Added: svn:executable
   + *

Added: MacRuby/branches/experimental/mspec/bin/mkspec.bat
===================================================================
--- MacRuby/branches/experimental/mspec/bin/mkspec.bat	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/bin/mkspec.bat	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1 @@
+@"ruby.exe" "%~dpn0" %*

Added: MacRuby/branches/experimental/mspec/bin/mspec
===================================================================
--- MacRuby/branches/experimental/mspec/bin/mspec	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/bin/mspec	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,7 @@
+#!/usr/bin/env ruby
+
+$:.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
+
+require 'mspec/commands/mspec'
+
+MSpecMain.main


Property changes on: MacRuby/branches/experimental/mspec/bin/mspec
___________________________________________________________________
Added: svn:executable
   + *

Added: MacRuby/branches/experimental/mspec/bin/mspec-ci
===================================================================
--- MacRuby/branches/experimental/mspec/bin/mspec-ci	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/bin/mspec-ci	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,8 @@
+#!/usr/bin/env ruby
+
+$:.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
+
+require 'mspec/commands/mspec-ci'
+require 'mspec'
+
+MSpecCI.main


Property changes on: MacRuby/branches/experimental/mspec/bin/mspec-ci
___________________________________________________________________
Added: svn:executable
   + *

Added: MacRuby/branches/experimental/mspec/bin/mspec-ci.bat
===================================================================
--- MacRuby/branches/experimental/mspec/bin/mspec-ci.bat	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/bin/mspec-ci.bat	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1 @@
+@"ruby.exe" "%~dpn0" %*

Added: MacRuby/branches/experimental/mspec/bin/mspec-run
===================================================================
--- MacRuby/branches/experimental/mspec/bin/mspec-run	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/bin/mspec-run	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,8 @@
+#!/usr/bin/env ruby
+
+$:.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
+
+require 'mspec/commands/mspec-run'
+require 'mspec'
+
+MSpecRun.main


Property changes on: MacRuby/branches/experimental/mspec/bin/mspec-run
___________________________________________________________________
Added: svn:executable
   + *

Added: MacRuby/branches/experimental/mspec/bin/mspec-run.bat
===================================================================
--- MacRuby/branches/experimental/mspec/bin/mspec-run.bat	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/bin/mspec-run.bat	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1 @@
+@"ruby.exe" "%~dpn0" %*

Added: MacRuby/branches/experimental/mspec/bin/mspec-tag
===================================================================
--- MacRuby/branches/experimental/mspec/bin/mspec-tag	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/bin/mspec-tag	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,8 @@
+#!/usr/bin/env ruby
+
+$:.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
+
+require 'mspec/commands/mspec-tag'
+require 'mspec'
+
+MSpecTag.main


Property changes on: MacRuby/branches/experimental/mspec/bin/mspec-tag
___________________________________________________________________
Added: svn:executable
   + *

Added: MacRuby/branches/experimental/mspec/bin/mspec-tag.bat
===================================================================
--- MacRuby/branches/experimental/mspec/bin/mspec-tag.bat	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/bin/mspec-tag.bat	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1 @@
+@"ruby.exe" "%~dpn0" %*

Added: MacRuby/branches/experimental/mspec/bin/mspec.bat
===================================================================
--- MacRuby/branches/experimental/mspec/bin/mspec.bat	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/bin/mspec.bat	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1 @@
+@"ruby.exe" "%~dpn0" %*

Added: MacRuby/branches/experimental/mspec/lib/mspec/commands/mkspec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/commands/mkspec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/commands/mkspec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,140 @@
+#! /usr/bin/env ruby
+
+MSPEC_HOME = File.expand_path(File.dirname(__FILE__) + '/../../..')
+
+require 'fileutils'
+require 'rbconfig'
+require 'mspec/version'
+require 'mspec/utils/options'
+require 'mspec/utils/name_map'
+
+
+class MkSpec
+  attr_reader :config
+
+  def initialize
+    @config = {
+      :constants => [],
+      :requires  => [],
+      :base      => "spec/ruby/1.8/core"
+    }
+    @map = NameMap.new
+  end
+
+  def options(argv=ARGV)
+    options = MSpecOptions.new "mkspec [options]"
+
+    options.on("-c", "--constant", "CONSTANT",
+               "Class or Module to generate spec stubs for") do |name|
+      config[:constants] << name
+    end
+    options.on("-b", "--base", "DIR",
+               "Directory to generate specs into") do |directory|
+      config[:base] = File.expand_path directory
+    end
+    options.on("-r", "--require", "LIBRARY",
+               "A library to require") do |file|
+      config[:requires] << file
+    end
+    options.version MSpec::VERSION
+    options.help
+
+    options.doc "\n How might this work in the real world?\n"
+    options.doc "   1. To create spec stubs for every class or module in Object\n"
+    options.doc "     $ mkspec\n"
+    options.doc "   2. To create spec stubs for Fixnum\n"
+    options.doc "     $ mkspec -c Fixnum\n"
+    options.doc "   3. To create spec stubs for Complex in 'superspec/complex'\n"
+    options.doc "     $ mkspec -c Complex -rcomplex -b superspec"
+    options.doc ""
+
+    options.parse argv
+  end
+
+  def create_directory(mod)
+    subdir = @map.dir_name mod, config[:base]
+
+    if File.exist? subdir
+      unless File.directory? subdir
+        puts "#{subdir} already exists and is not a directory."
+        return nil
+      end
+    else
+      FileUtils.mkdir_p subdir
+    end
+
+    subdir
+  end
+
+  def write_requires(dir, file)
+    /\A#{Regexp.escape config[:base]}\/?(.*)/ =~ dir
+    parents = '../' * ($1.split('/').length + 1)
+
+    File.open file, 'w' do |f|
+      f.puts "require File.dirname(__FILE__) + '/#{parents}spec_helper'"
+      config[:requires].each do |lib|
+        f.puts "require '#{lib}'"
+      end
+    end
+  end
+
+  def write_spec(file, meth, exists)
+    if exists
+      out = `#{ruby} #{MSPEC_HOME}/bin/mspec-run --dry-run -fs -e '#{meth}' #{file}`
+      return if out =~ /#{Regexp.escape meth}/
+    end
+
+    File.open file, 'a' do |f|
+      f.puts <<-EOS
+
+describe "#{meth}" do
+  it "needs to be reviewed for spec completeness"
+end
+EOS
+    end
+
+    puts file
+  end
+
+  def create_file(dir, mod, meth, name)
+    file = File.join dir, @map.file_name(meth, mod)
+    exists = File.exist? file
+
+    write_requires dir, file unless exists
+    write_spec file, name, exists
+  end
+
+  def run
+    config[:requires].each { |lib| require lib }
+    constants = config[:constants]
+    constants = @map.filter(Object.constants) if constants.empty?
+
+    @map.map({}, constants).each do |mod, methods|
+      name = mod.chop
+      next unless dir = create_directory(name)
+
+      methods.each { |method| create_file dir, name, method, mod + method }
+    end
+  end
+  
+  ##
+  # Determine and return the path of the ruby executable.
+
+  def ruby
+    ruby = File.join(Config::CONFIG['bindir'],
+                     Config::CONFIG['ruby_install_name'])
+
+    ruby.gsub! File::SEPARATOR, File::ALT_SEPARATOR if File::ALT_SEPARATOR
+
+    return ruby
+  end
+
+  def self.main
+    ENV['MSPEC_RUNNER'] = '1'
+
+    script = new
+    script.options
+    script.run
+  end
+end
+

Added: MacRuby/branches/experimental/mspec/lib/mspec/commands/mspec-ci.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/commands/mspec-ci.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/commands/mspec-ci.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,75 @@
+#!/usr/bin/env ruby
+
+$:.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
+
+require 'mspec/version'
+require 'mspec/utils/options'
+require 'mspec/utils/script'
+
+
+class MSpecCI < MSpecScript
+  def options(argv=ARGV)
+    options = MSpecOptions.new "mspec ci [options] (FILE|DIRECTORY|GLOB)+", 30, config
+
+    options.doc " Ask yourself:"
+    options.doc "  1. How to run the specs?"
+    options.doc "  2. How to modify the guard behavior?"
+    options.doc "  2. How to display the output?"
+    options.doc "  3. What action to perform?"
+    options.doc "  4. When to perform it?"
+
+    options.doc "\n How to run the specs"
+    options.chdir
+    options.prefix
+    options.configure { |f| load f }
+    options.name
+    options.pretend
+    options.background
+    options.interrupt
+
+    options.doc "\n How to modify the guard behavior"
+    options.unguarded
+    options.verify
+
+    options.doc "\n How to display their output"
+    options.formatters
+    options.verbose
+
+    options.doc "\n What action to perform"
+    options.actions
+
+    options.doc "\n When to perform it"
+    options.action_filters
+
+    options.doc "\n Help!"
+    options.version MSpec::VERSION
+    options.help
+
+    options.doc "\n Custom options"
+    custom_options options
+
+    options.doc "\n How might this work in the real world?"
+    options.doc "\n   1. To simply run the known good specs"
+    options.doc "\n     $ mspec ci"
+    options.doc "\n   2. To run a subset of the known good specs"
+    options.doc "\n     $ mspec ci path/to/specs"
+    options.doc "\n   3. To start the debugger before the spec matching 'this crashes'"
+    options.doc "\n     $ mspec ci --spec-debug -S 'this crashes'"
+    options.doc ""
+
+    patterns = options.parse argv
+    patterns = config[:ci_files] if patterns.empty?
+    @files = files patterns
+  end
+
+  def run
+    MSpec.register_tags_patterns config[:tags_patterns]
+    MSpec.register_files @files
+    filter = TagFilter.new(:exclude,
+        "fails", "critical", "unstable", "incomplete", "unsupported")
+    filter.register
+
+    MSpec.process
+    exit MSpec.exit_code
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/commands/mspec-run.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/commands/mspec-run.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/commands/mspec-run.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,90 @@
+#!/usr/bin/env ruby
+
+$:.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
+
+require 'mspec/version'
+require 'mspec/utils/options'
+require 'mspec/utils/script'
+
+
+class MSpecRun < MSpecScript
+  def initialize
+    super
+
+    config[:files] = []
+  end
+
+  def options(argv=ARGV)
+    options = MSpecOptions.new "mspec run [options] (FILE|DIRECTORY|GLOB)+", 30, config
+
+    options.doc " Ask yourself:"
+    options.doc "  1. What specs to run?"
+    options.doc "  2. How to modify the execution?"
+    options.doc "  3. How to modify the guard behavior?"
+    options.doc "  4. How to display the output?"
+    options.doc "  5. What action to perform?"
+    options.doc "  6. When to perform it?"
+
+    options.doc "\n What specs to run"
+    options.filters
+
+    options.doc "\n How to modify the execution"
+    options.chdir
+    options.prefix
+    options.configure { |f| load f }
+    options.name
+    options.randomize
+    options.pretend
+    options.background
+    options.interrupt
+
+    options.doc "\n How to modify the guard behavior"
+    options.unguarded
+    options.verify
+
+    options.doc "\n How to display their output"
+    options.formatters
+    options.verbose
+
+    options.doc "\n What action to perform"
+    options.actions
+
+    options.doc "\n When to perform it"
+    options.action_filters
+
+    options.doc "\n Help!"
+    options.version MSpec::VERSION
+    options.help
+
+    options.doc "\n Custom options"
+    custom_options options
+
+    options.doc "\n How might this work in the real world?"
+    options.doc "\n   1. To simply run some specs"
+    options.doc "\n     $ mspec path/to/the/specs"
+    options.doc "     mspec path/to/the_file_spec.rb"
+    options.doc "\n   2. To run specs tagged with 'fails'"
+    options.doc "\n     $ mspec -g fails path/to/the_file_spec.rb"
+    options.doc "\n   3. To start the debugger before the spec matching 'this crashes'"
+    options.doc "\n     $ mspec --spec-debug -S 'this crashes' path/to/the_file_spec.rb"
+    options.doc ""
+
+    patterns = options.parse argv
+    patterns = config[:files] if patterns.empty?
+    if patterns.empty?
+      puts options
+      puts "No files specified."
+      exit 1
+    end
+    @files = files patterns
+  end
+
+  def run
+    MSpec.register_tags_patterns config[:tags_patterns]
+    MSpec.register_files @files
+
+    MSpec.process
+    exit MSpec.exit_code
+  end
+end
+

Added: MacRuby/branches/experimental/mspec/lib/mspec/commands/mspec-tag.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/commands/mspec-tag.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/commands/mspec-tag.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,130 @@
+#!/usr/bin/env ruby
+
+require 'mspec/version'
+require 'mspec/utils/options'
+require 'mspec/utils/script'
+
+
+class MSpecTag < MSpecScript
+  def initialize
+    super
+
+    config[:tagger]  = :add
+    config[:tag]     = 'fails:'
+    config[:outcome] = :fail
+    config[:ltags]   = []
+  end
+
+  def options(argv=ARGV)
+    options = MSpecOptions.new "mspec tag [options] (FILE|DIRECTORY|GLOB)+", 30, config
+
+    options.doc " Ask yourself:"
+    options.doc "  1. What specs to run?"
+    options.doc "  2. How to modify the execution?"
+    options.doc "  3. How to display the output?"
+    options.doc "  4. What tag action to perform?"
+    options.doc "  5. When to perform it?"
+
+    options.doc "\n What specs to run"
+    options.filters
+
+    options.doc "\n How to modify the execution"
+    options.configure { |f| load f }
+    options.name
+    options.pretend
+    options.unguarded
+    options.interrupt
+
+    options.doc "\n How to display their output"
+    options.formatters
+    options.verbose
+
+    options.doc "\n What action to perform and when to perform it"
+    options.on("-N", "--add", "TAG",
+       "Add TAG with format 'tag' or 'tag(comment)' (see -Q, -F, -L)") do |o|
+      config[:tagger] = :add
+      config[:tag] = "#{o}:"
+    end
+    options.on("-R", "--del", "TAG",
+       "Delete TAG (see -Q, -F, -L)") do |o|
+      config[:tagger] = :del
+      config[:tag] = "#{o}:"
+      config[:outcome] = :pass
+    end
+    options.on("-Q", "--pass", "Apply action to specs that pass (default for --del)") do
+      config[:outcome] = :pass
+    end
+    options.on("-F", "--fail", "Apply action to specs that fail (default for --add)") do
+      config[:outcome] = :fail
+    end
+    options.on("-L", "--all", "Apply action to all specs") do
+      config[:outcome] = :all
+    end
+    options.on("--list", "TAG", "Display descriptions of any specs tagged with TAG") do |t|
+      config[:tagger] = :list
+      config[:ltags] << t
+    end
+    options.on("--list-all", "Display descriptions of any tagged specs") do
+      config[:tagger] = :list_all
+    end
+    options.on("--purge", "Remove all tags not matching any specs") do
+      config[:tagger] = :purge
+    end
+
+    options.doc "\n Help!"
+    options.version MSpec::VERSION
+    options.help
+
+    options.doc "\n Custom options"
+    custom_options options
+
+    options.doc "\n How might this work in the real world?"
+    options.doc "\n   1. To add the 'fails' tag to failing specs"
+    options.doc "\n     $ mspec tag path/to/the_file_spec.rb"
+    options.doc "\n   2. To remove the 'fails' tag from passing specs"
+    options.doc "\n     $ mspec tag --del fails path/to/the_file_spec.rb"
+    options.doc "\n   3. To display the descriptions for all specs tagged with 'fails'"
+    options.doc "\n     $ mspec tag --list fails path/to/the/specs"
+    options.doc ""
+
+    patterns = options.parse argv
+    if patterns.empty?
+      puts options
+      puts "No files specified."
+      exit 1
+    end
+    @files = files patterns
+  end
+
+  def register
+    case config[:tagger]
+    when :add, :del
+      tag = SpecTag.new config[:tag]
+      tagger = TagAction.new(config[:tagger], config[:outcome], tag.tag, tag.comment,
+                             config[:atags], config[:astrings])
+    when :list, :list_all
+      tagger = TagListAction.new config[:tagger] == :list_all ? nil : config[:ltags]
+      MSpec.register_mode :pretend
+      config[:formatter] = false
+    when :purge
+      tagger = TagPurgeAction.new
+      MSpec.register_mode :pretend
+      MSpec.register_mode :unguarded
+      config[:formatter] = false
+    else
+      raise ArgumentError, "No recognized action given"
+    end
+    tagger.register
+
+    super
+  end
+
+  def run
+    MSpec.register_tags_patterns config[:tags_patterns]
+    MSpec.register_files @files
+
+    MSpec.process
+    exit MSpec.exit_code
+  end
+end
+

Added: MacRuby/branches/experimental/mspec/lib/mspec/commands/mspec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/commands/mspec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/commands/mspec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,157 @@
+#!/usr/bin/env ruby
+
+MSPEC_HOME = File.expand_path(File.dirname(__FILE__) + '/../../..')
+
+require 'mspec/version'
+require 'mspec/utils/options'
+require 'mspec/utils/script'
+require 'mspec/helpers/tmp'
+require 'mspec/runner/actions/timer'
+
+
+class MSpecMain < MSpecScript
+  def initialize
+    config[:includes] = []
+    config[:requires] = []
+    config[:target]   = ENV['RUBY'] || 'ruby'
+    config[:flags]    = []
+    config[:command]  = nil
+    config[:options]  = []
+  end
+
+  def options(argv=ARGV)
+    config[:command] = argv.shift if ["ci", "run", "tag"].include?(argv[0])
+
+    options = MSpecOptions.new "mspec [COMMAND] [options] (FILE|DIRECTORY|GLOB)+", 30, config
+
+    options.doc " The mspec command sets up and invokes the sub-commands"
+    options.doc " (see below) to enable, for instance, running the specs"
+    options.doc " with different implementations like ruby, jruby, rbx, etc.\n"
+
+    options.configure do |f|
+      load f
+      config[:options] << '-B' << f
+    end
+
+    options.targets
+
+    options.on("-D", "--gdb", "Run under gdb") do
+      config[:flags] << '--gdb'
+    end
+    options.on("-A", "--valgrind", "Run under valgrind") do
+      config[:flags] << '--valgrind'
+    end
+    options.on("--warnings", "Don't supress warnings") do
+      config[:flags] << '-w'
+      ENV['OUTPUT_WARNINGS'] = '1'
+    end
+    options.on("-j", "--multi", "Run multiple (possibly parallel) subprocesses") do
+      config[:multi] = true
+      config[:options] << "-fy"
+    end
+    options.version MSpec::VERSION do
+      if config[:command]
+        config[:options] << "-v"
+      else
+        puts "#{File.basename $0} #{MSpec::VERSION}"
+        exit
+      end
+    end
+    options.help do
+      if config[:command]
+        config[:options] << "-h"
+      else
+        puts options
+        exit 1
+      end
+    end
+
+    options.doc "\n Custom options"
+    custom_options options
+
+    # The rest of the help output
+    options.doc "\n where COMMAND is one of:\n"
+    options.doc "   run - Run the specified specs (default)"
+    options.doc "   ci  - Run the known good specs"
+    options.doc "   tag - Add or remove tags\n"
+    options.doc " mspec COMMAND -h for more options\n"
+
+    options.on_extra { |o| config[:options] << o }
+    config[:options].concat options.parse(argv)
+  end
+
+  def register; end
+
+  def parallel
+    @parallel ||= !(Object.const_defined?(:JRUBY_VERSION) ||
+                  /(mswin|mingw)/ =~ RUBY_PLATFORM)
+  end
+
+  def fork(&block)
+    parallel ? Kernel.fork(&block) : block.call
+  end
+
+  def report(files, timer)
+    require 'yaml'
+
+    exceptions = []
+    tally = Tally.new
+
+    files.each do |file|
+      d = File.open(file, "r") { |f| YAML.load f }
+      File.delete file
+
+      exceptions += Array(d['exceptions'])
+      tally.files!        d['files']
+      tally.examples!     d['examples']
+      tally.expectations! d['expectations']
+      tally.errors!       d['errors']
+      tally.failures!     d['failures']
+    end
+
+    print "\n"
+    exceptions.each_with_index do |exc, index|
+      print "\n#{index+1})\n", exc, "\n"
+    end
+    print "\n#{timer.format}\n\n#{tally.format}\n"
+  end
+
+  def multi_exec(argv)
+    timer = TimerAction.new
+    timer.start
+
+    files = config[:ci_files].inject([]) do |list, item|
+      name = tmp "mspec-ci-multi-#{list.size}"
+
+      rest = argv + ["-o", name, item]
+      fork { system [config[:target], *rest].join(" ") }
+
+      list << name
+    end
+
+    Process.waitall
+    timer.finish
+    report files, timer
+  end
+
+  def run
+    ENV['MSPEC_RUNNER'] = '1'
+    ENV['RUBY_EXE']     = config[:target]
+    ENV['RUBY_FLAGS']   = config[:flags].join " "
+
+    argv = []
+    argv.concat config[:flags]
+    argv.concat config[:includes]
+    argv.concat config[:requires]
+    argv << "-v"
+    argv << "#{MSPEC_HOME}/bin/mspec-#{ config[:command] || "run" }"
+    argv.concat config[:options]
+
+    if config[:multi] and config[:command] == "ci"
+      multi_exec argv
+    else
+      exec config[:target], *argv
+    end
+  end
+end
+

Added: MacRuby/branches/experimental/mspec/lib/mspec/expectations/expectations.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/expectations/expectations.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/expectations/expectations.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,17 @@
+class ExpectationNotMetError < StandardError; end
+class ExpectationNotFoundError < StandardError
+  def message
+    "No behavior expectation was found in the example"
+  end
+end
+
+class Expectation
+  def self.fail_with(expected, actual)
+    if expected.to_s.size + actual.to_s.size > 80
+      message = expected.to_s.chomp + "\n" + actual.to_s
+    else
+      message = expected.to_s + " " + actual.to_s
+    end
+    Kernel.raise ExpectationNotMetError, message
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/expectations/should.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/expectations/should.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/expectations/should.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,25 @@
+class Object
+  def should(matcher=nil)
+    MSpec.expectation
+    MSpec.actions :expectation, MSpec.current.state
+    if matcher
+      unless matcher.matches?(self)
+        Expectation.fail_with(*matcher.failure_message)
+      end
+    else
+      PositiveOperatorMatcher.new(self)
+    end
+  end
+
+  def should_not(matcher=nil)
+    MSpec.expectation
+    MSpec.actions :expectation, MSpec.current.state
+    if matcher
+      if matcher.matches?(self)
+        Expectation.fail_with(*matcher.negative_failure_message)
+      end
+    else
+      NegativeOperatorMatcher.new(self)
+    end
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/expectations.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/expectations.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/expectations.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,2 @@
+require 'mspec/expectations/expectations'
+require 'mspec/expectations/should'

Added: MacRuby/branches/experimental/mspec/lib/mspec/guards/background.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/guards/background.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/guards/background.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,21 @@
+require 'mspec/guards/guard'
+
+# Some specs, notably those for Readline, will block under certain
+# circumstances when run as background or subprocesses. Use this guard for
+# such specs.
+
+class BackgroundGuard < SpecGuard
+  def match?
+    MSpec.mode? :background
+  end
+end
+
+class Object
+  def process_is_foreground
+    g = BackgroundGuard.new
+    g.name = :process_is_foreground
+    yield if g.yield? true
+  ensure
+    g.unregister
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/guards/bug.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/guards/bug.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/guards/bug.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,24 @@
+require 'mspec/guards/version'
+
+class BugGuard < VersionGuard
+  def initialize(bug, version)
+    @bug = bug
+    @version = SpecVersion.new version, true
+    self.parameters = [@bug, @version]
+  end
+
+  def match?
+    return false if MSpec.mode? :no_ruby_bug
+    standard? && ruby_version <= @version
+  end
+end
+
+class Object
+  def ruby_bug(bug, version)
+    g = BugGuard.new bug, version
+    g.name = :ruby_bug
+    yield if g.yield? true
+  ensure
+    g.unregister
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/guards/compliance.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/guards/compliance.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/guards/compliance.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,37 @@
+require 'mspec/guards/guard'
+
+class CompliantOnGuard < SpecGuard
+  def match?
+    if @args.include? :ruby
+      raise Exception, "improper use of compliant_on guard"
+    end
+    standard? or implementation?(*@args)
+  end
+end
+
+class NotCompliantOnGuard < SpecGuard
+  def match?
+    if @args.include? :ruby
+      raise Exception, "improper use of not_compliant_on guard"
+    end
+    standard? or !implementation?(*@args)
+  end
+end
+
+class Object
+  def compliant_on(*args)
+    g = CompliantOnGuard.new(*args)
+    g.name = :compliant_on
+    yield if g.yield?
+  ensure
+    g.unregister
+  end
+
+  def not_compliant_on(*args)
+    g = NotCompliantOnGuard.new(*args)
+    g.name = :not_compliant_on
+    yield if g.yield?
+  ensure
+    g.unregister
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/guards/conflict.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/guards/conflict.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/guards/conflict.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,18 @@
+require 'mspec/guards/guard'
+
+class ConflictsGuard < SpecGuard
+  def match?
+    constants = Object.constants
+    @args.any? { |mod| constants.include? mod.to_s }
+  end
+end
+
+class Object
+  def conflicts_with(*modules)
+    g = ConflictsGuard.new(*modules)
+    g.name = :conflicts_with
+    yield if g.yield? true
+  ensure
+    g.unregister
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/guards/endian.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/guards/endian.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/guards/endian.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,44 @@
+require 'mspec/guards/guard'
+
+# Despite that these are inverses, the two classes are
+# used to simplify MSpec guard reporting modes
+
+class BigEndianGuard < SpecGuard
+  def pattern
+    [1].pack('L')
+  end
+  private :pattern
+
+  def match?
+    pattern[-1] == ?\001
+  end
+end
+
+class LittleEndianGuard < SpecGuard
+  def pattern
+    [1].pack('L')
+  end
+  private :pattern
+
+  def match?
+    pattern[-1] == ?\000
+  end
+end
+
+class Object
+  def big_endian
+    g = BigEndianGuard.new
+    g.name = :big_endian
+    yield if g.yield?
+  ensure
+    g.unregister
+  end
+
+  def little_endian
+    g = LittleEndianGuard.new
+    g.name = :little_endian
+    yield if g.yield?
+  ensure
+    g.unregister
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/guards/extensions.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/guards/extensions.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/guards/extensions.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,20 @@
+require 'mspec/guards/guard'
+
+class ExtensionsGuard < SpecGuard
+  def match?
+    if @args.include? :ruby
+      raise Exception, "improper use of extended_on guard"
+    end
+    !standard? and implementation?(*@args)
+  end
+end
+
+class Object
+  def extended_on(*args)
+    g = ExtensionsGuard.new(*args)
+    g.name = :extended_on
+    yield if g.yield?
+  ensure
+    g.unregister
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/guards/guard.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/guards/guard.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/guards/guard.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,166 @@
+require 'mspec/runner/mspec'
+require 'mspec/runner/actions/tally'
+
+class SpecGuard
+  def self.report
+    @report ||= Hash.new { |h,k| h[k] = [] }
+  end
+
+  def self.clear
+    @report = nil
+  end
+
+  def self.finish
+    report.keys.sort.each do |key|
+      desc = report[key]
+      size = desc.size
+      spec = size == 1 ? "spec" : "specs"
+      print "\n\n#{size} #{spec} omitted by guard: #{key}:\n"
+      desc.each { |description| print "\n", description; }
+    end
+
+    print "\n\n"
+  end
+
+  def self.guards
+    @guards ||= []
+  end
+
+  def self.clear_guards
+    @guards = []
+  end
+
+  # Returns a partial Ruby version string based on +which+. For example,
+  # if RUBY_VERSION = 8.2.3 and RUBY_PATCHLEVEL = 71:
+  #
+  #  :major  => "8"
+  #  :minor  => "8.2"
+  #  :tiny   => "8.2.3"
+  #  :teeny  => "8.2.3"
+  #  :full   => "8.2.3.71"
+  def self.ruby_version(which = :minor)
+    case which
+    when :major
+      n = 1
+    when :minor
+      n = 2
+    when :tiny, :teeny
+      n = 3
+    else
+      n = 4
+    end
+
+    patch = RUBY_PATCHLEVEL.to_i
+    patch = 0 if patch < 0
+    version = "#{RUBY_VERSION}.#{patch}"
+    version.split('.')[0,n].join('.')
+  end
+
+  def self.windows?(key = RUBY_PLATFORM)
+    !!key.match(/(mswin|mingw)/)
+  end
+
+  attr_accessor :name, :parameters
+
+  def initialize(*args)
+    self.parameters = @args = args
+  end
+
+  def yield?(invert=false)
+    return true if MSpec.mode? :unguarded
+
+    allow = match? ^ invert
+
+    if not allow and reporting?
+      MSpec.guard
+      MSpec.register :finish, SpecGuard
+      MSpec.register :add,    self
+      return true
+    elsif MSpec.mode? :verify
+      return true
+    end
+
+    allow
+  end
+
+  def ===(other)
+    true
+  end
+
+  def reporting?
+    MSpec.mode?(:report) or
+      (MSpec.mode?(:report_on) and SpecGuard.guards.include?(name))
+  end
+
+  def report_key
+    "#{name} #{parameters.join(", ")}"
+  end
+
+  def record(description)
+    SpecGuard.report[report_key] << description
+  end
+
+  def add(example)
+    record example.description
+    MSpec.retrieve(:formatter).tally.counter.guards!
+  end
+
+  def unregister
+    MSpec.unguard
+    MSpec.unregister :add, self
+  end
+
+  def implementation?(*args)
+    args.any? do |name|
+      !!case name
+      when :rubinius
+        RUBY_NAME =~ /^rbx/
+      when :ruby
+        RUBY_NAME =~ /^ruby/
+      when :jruby
+        RUBY_NAME =~ /^jruby/
+      when :ironruby
+        RUBY_NAME =~ /^ironruby/
+      when :macruby
+        RUBY_NAME =~ /^macruby/
+      else
+        false
+      end
+    end
+  end
+
+  def standard?
+    implementation? :ruby
+  end
+
+  def windows?(sym, key)
+    sym == :windows && SpecGuard.windows?(key)
+  end
+
+  def platform?(*args)
+    args.any? do |platform|
+      if platform != :java && RUBY_PLATFORM.match('java') && os?(platform)
+        true
+      else
+        RUBY_PLATFORM.match(platform.to_s) || windows?(platform, RUBY_PLATFORM)
+      end
+    end
+  end
+
+  def wordsize?(size)
+    size == 8 * 1.size
+  end
+
+  def os?(*oses)
+    require 'rbconfig'
+    oses.any? do |os|
+      host_os = Config::CONFIG['host_os'] || RUBY_PLATFORM
+      host_os.downcase!
+      host_os.match(os.to_s) || windows?(os, host_os)
+    end
+  end
+
+  def match?
+    implementation?(*@args) or platform?(*@args)
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/guards/noncompliance.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/guards/noncompliance.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/guards/noncompliance.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,20 @@
+require 'mspec/guards/guard'
+
+class NonComplianceGuard < SpecGuard
+  def match?
+    if @args.include? :ruby
+      raise Exception, "improper use of deviates_on guard"
+    end
+    !standard? and implementation?(*@args)
+  end
+end
+
+class Object
+  def deviates_on(*args)
+    g = NonComplianceGuard.new(*args)
+    g.name = :deviates_on
+    yield if g.yield?
+  ensure
+    g.unregister
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/guards/platform.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/guards/platform.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/guards/platform.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,43 @@
+require 'mspec/guards/guard'
+
+class PlatformGuard < SpecGuard
+  def initialize(*args)
+    if args.last.is_a?(Hash)
+      @options, @platforms = args.last, args[0..-2]
+    else
+      @options, @platforms = {}, args
+    end
+    self.parameters = args
+  end
+
+  def match?
+    match = @platforms.empty? ? true : platform?(*@platforms)
+    @options.each do |key, value|
+      case key
+      when :os
+        match &&= os?(*value)
+      when :wordsize
+        match &&= wordsize? value
+      end
+    end
+    match
+  end
+end
+
+class Object
+  def platform_is(*args)
+    g = PlatformGuard.new(*args)
+    g.name = :platform_is
+    yield if g.yield?
+  ensure
+    g.unregister
+  end
+
+  def platform_is_not(*args)
+    g = PlatformGuard.new(*args)
+    g.name = :platform_is_not
+    yield if g.yield? true
+  ensure
+    g.unregister
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/guards/quarantine.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/guards/quarantine.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/guards/quarantine.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,17 @@
+require 'mspec/guards/guard'
+
+class QuarantineGuard < SpecGuard
+  def match?
+    false
+  end
+end
+
+class Object
+  def quarantine!
+    g = QuarantineGuard.new
+    g.name = :quarantine!
+    yield if g.yield?
+  ensure
+    g.unregister
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/guards/runner.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/guards/runner.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/guards/runner.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,34 @@
+require 'mspec/guards/guard'
+
+class RunnerGuard < SpecGuard
+  def match?
+    @args.any? do |runner|
+      case runner
+      when :mspec
+        ENV['MSPEC_RUNNER'] == '1'
+      when :rspec
+        ENV['RSPEC_RUNNER'] == '1' or Object.const_defined?(:Spec)
+      else
+        false
+      end
+    end
+  end
+end
+
+class Object
+  def runner_is(*args)
+    g = RunnerGuard.new(*args)
+    g.name = :runner_is
+    yield if g.yield?
+  ensure
+    g.unregister
+  end
+
+  def runner_is_not(*args)
+    g = RunnerGuard.new(*args)
+    g.name = :runner_is_not
+    yield if g.yield? true
+  ensure
+    g.unregister
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/guards/superuser.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/guards/superuser.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/guards/superuser.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,17 @@
+require 'mspec/guards/guard'
+
+class SuperUserGuard < SpecGuard
+  def match?
+    Process.euid == 0
+  end
+end
+
+class Object
+  def as_superuser
+    g = SuperUserGuard.new
+    g.name = :as_superuser
+    yield if g.yield?
+  ensure
+    g.unregister
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/guards/support.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/guards/support.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/guards/support.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,20 @@
+require 'mspec/guards/guard'
+
+class SupportedGuard < SpecGuard
+  def match?
+    if @args.include? :ruby
+      raise Exception, "improper use of not_supported_on guard"
+    end
+    standard? or !implementation?(*@args)
+  end
+end
+
+class Object
+  def not_supported_on(*args)
+    g = SupportedGuard.new(*args)
+    g.name = :not_supported_on
+    yield if g.yield?
+  ensure
+    g.unregister
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/guards/tty.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/guards/tty.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/guards/tty.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,20 @@
+require 'mspec/guards/guard'
+
+# If a spec depends on STDOUT being a tty, use this guard. For specs that may
+# block if run as a background process, see BackgroundGuard.
+
+class TTYGuard < SpecGuard
+  def match?
+    STDOUT.tty?
+  end
+end
+
+class Object
+  def with_tty
+    g = TTYGuard.new
+    g.name = :with_tty
+    yield if g.yield?
+  ensure
+    g.unregister
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/guards/version.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/guards/version.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/guards/version.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,38 @@
+require 'mspec/utils/version'
+require 'mspec/guards/guard'
+
+class VersionGuard < SpecGuard
+  def initialize(version)
+    case version
+    when String
+      @version = SpecVersion.new version
+    when Range
+      a = SpecVersion.new version.first
+      b = SpecVersion.new version.last
+      @version = version.exclude_end? ? a...b : a..b
+    end
+    self.parameters = [version]
+  end
+
+  def ruby_version
+    @ruby_version ||= SpecVersion.new self.class.ruby_version(:full)
+  end
+
+  def match?
+    if Range === @version
+      @version.include? ruby_version
+    else
+      ruby_version >= @version
+    end
+  end
+end
+
+class Object
+  def ruby_version_is(*args)
+    g = VersionGuard.new(*args)
+    g.name = :ruby_version_is
+    yield if g.yield?
+  ensure
+    g.unregister
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/guards.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/guards.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/guards.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,16 @@
+require 'mspec/utils/ruby_name'
+require 'mspec/guards/background'
+require 'mspec/guards/bug'
+require 'mspec/guards/compliance'
+require 'mspec/guards/conflict'
+require 'mspec/guards/endian'
+require 'mspec/guards/extensions'
+require 'mspec/guards/guard'
+require 'mspec/guards/noncompliance'
+require 'mspec/guards/platform'
+require 'mspec/guards/quarantine'
+require 'mspec/guards/runner'
+require 'mspec/guards/support'
+require 'mspec/guards/superuser'
+require 'mspec/guards/tty'
+require 'mspec/guards/version'

Added: MacRuby/branches/experimental/mspec/lib/mspec/helpers/argv.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/helpers/argv.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/helpers/argv.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,43 @@
+class Object
+  # Convenience helper for altering ARGV. Saves the
+  # value of ARGV and sets it to +args+. If a block
+  # is given, yields to the block and then restores
+  # the value of ARGV. The previously saved value of
+  # ARGV can be restored by passing +:restore+. The
+  # former is useful in a single spec. The latter is
+  # useful in before/after actions. For example:
+  #
+  #   describe "This" do
+  #     before do
+  #       argv ['a', 'b']
+  #     end
+  #
+  #     after do
+  #       argv :restore
+  #     end
+  #
+  #     it "does something" do
+  #       # do something
+  #     end
+  #   end
+  #
+  #   describe "That" do
+  #     it "does something" do
+  #       argv ['a', 'b'] do
+  #         # do something
+  #       end
+  #     end
+  #   end
+  def argv(args)
+    if args == :restore
+      ARGV.replace(@__mspec_saved_argv__ || [])
+    else
+      @__mspec_saved_argv__ = ARGV
+      ARGV.replace args
+      if block_given?
+        yield
+        argv :restore
+      end
+    end
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/helpers/bignum.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/helpers/bignum.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/helpers/bignum.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,5 @@
+class Object
+  def bignum_value(plus=0)
+    0x8000_0000_0000_0000 + plus
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/helpers/const_lookup.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/helpers/const_lookup.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/helpers/const_lookup.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,9 @@
+module Kernel
+  def const_lookup(c)
+    names = c.split '::'
+    names.shift if names.first.empty?
+    names.inject(Object) do |m, n|
+      m.const_defined?(n) ? m.const_get(n) : m.const_missing(n)
+    end
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/helpers/environment.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/helpers/environment.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/helpers/environment.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,23 @@
+require 'mspec/guards/guard'
+
+class Object
+  def env
+    env = ""
+    if SpecGuard.windows?
+      env = Hash[*`cmd.exe /C set`.split("\n").map { |e| e.split("=", 2) }.flatten]
+    else
+      env = Hash[*`env`.split("\n").map { |e| e.split("=", 2) }.flatten]
+    end
+    env
+  end
+
+  def username
+    user = ""
+    if SpecGuard.windows?
+      user = `cmd.exe /C ECHO %USERNAME%`.strip
+    else
+      user = `whoami`.strip
+    end
+    user
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/helpers/fixture.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/helpers/fixture.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/helpers/fixture.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,20 @@
+class Object
+  # Returns the name of a fixture file by adjoining the directory
+  # of the +dir+ argument with "fixtures" and the contents of the
+  # +args+ array. For example,
+  #
+  #   +dir+ == "some/path"
+  #
+  # and
+  #
+  #   +args+ == ["dir", "file.txt"]
+  #
+  # then the result is the expanded path of
+  #
+  #   "some/fixtures/dir/file.txt".
+  def fixture(dir, *args)
+    path = File.dirname(dir)
+    path = path[0..-7] if path[-7..-1] == "/shared"
+    File.expand_path(File.join(path, "fixtures", args))
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/helpers/flunk.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/helpers/flunk.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/helpers/flunk.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,5 @@
+class Object
+  def flunk(msg="This example is a failure")
+    Expectation.fail_with "Failed:", msg
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/helpers/io.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/helpers/io.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/helpers/io.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,17 @@
+class IOStub < String
+  def write(*str)
+    self << str.to_s
+  end
+
+  def print(*str)
+    write(str.to_s + $\.to_s)
+  end
+
+  def puts(*str)
+    write(str.collect { |s| s.to_s.chomp }.concat([nil]).join("\n"))
+  end
+
+  def flush
+    self
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/helpers/language_version.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/helpers/language_version.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/helpers/language_version.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,20 @@
+require 'mspec/guards/guard'
+
+class Object
+  # Helper for syntax-sensitive specs. The specs should be placed in a file in
+  # the +versions+ subdirectory. For example, suppose language/method_spec.rb
+  # contains specs whose syntax depends on the Ruby version. In the
+  # language/method_spec.rb use the helper as follows:
+  #
+  #   language_version __FILE__, "method"
+  #
+  # Then add a file "language/versions/method_1.8.rb" for the specs that are
+  # syntax-compatible with Ruby 1.8.x.
+  def language_version(dir, name)
+    path = File.dirname(File.expand_path(dir))
+    name = "#{name}_#{SpecGuard.ruby_version}.rb"
+    file = File.join path, "versions", name
+
+    require file if File.exists? file
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/helpers/ruby_exe.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/helpers/ruby_exe.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/helpers/ruby_exe.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,123 @@
+require 'mspec/utils/ruby_name'
+require 'mspec/guards/guard'
+
+# The ruby_exe helper provides a wrapper for invoking the
+# same Ruby interpreter as the one running the specs and
+# getting the output from running the code. If +code+ is a
+# file that exists, it will be run. Otherwise, +code+ should
+# be Ruby code that will be run with the -e command line
+# option. For example:
+#
+#   ruby_exe('path/to/some/file.rb')
+#
+# will be executed as
+#
+#   `#{RUBY_EXE} #{'path/to/some/file.rb'}`
+#
+# while
+#
+#   ruby_exe('puts "hello, world."')
+#
+# will be executed as
+#
+#   `#{RUBY_EXE} -e #{'puts "hello, world."'}`
+#
+# The ruby_exe helper also accepts an options hash with two
+# keys: :options and :args. For example:
+#
+#   ruby_exe('file.rb', :options => "-w", :args => "> file.txt")
+#
+# will be executed as
+#
+#   `#{RUBY_EXE} -w #{'file.rb'} > file.txt`
+#
+# If +nil+ is passed for the first argument, the command line
+# will be built only from the options hash.
+#
+# The RUBY_EXE constant can be set explicitly since the value
+# is used each time ruby_exe is invoked. The mspec runner script
+# will set ENV['RUBY_EXE'] to the name of the executable used
+# to invoke the mspec-run script. The value of RUBY_EXE will be
+# constructed as follows:
+#
+#   1. the value of ENV['RUBY_EXE']
+#   2. an explicit value based on RUBY_NAME
+#   3. cwd/(RUBY_NAME + $(EXEEXT) || $(exeext) || '')
+#   4. $(bindir)/$(RUBY_INSTALL_NAME)
+#
+# The value will only be used if the file exists and is executable.
+#
+# These 4 ways correspond to the following scenarios:
+#
+#   1. Using the MSpec runner scripts, the name of the
+#      executable is explicitly passed by ENV['RUBY_EXE']
+#      so there is no ambiguity.
+#
+#  Otherwise, if using RSpec (or something else)
+#
+#   2. Running the specs while developing an alternative
+#      Ruby implementation. This explicitly names the
+#      executable in the development directory based on
+#      the value of RUBY_NAME, which is probably initialized
+#      from the value of RUBY_ENGINE.
+#   3. Running the specs within the source directory for
+#      some implementation. (E.g. a local build directory.)
+#   4. Running the specs against some installed Ruby
+#      implementation.
+
+class Object
+  def ruby_exe_options(option)
+    case option
+    when :env
+      ENV['RUBY_EXE']
+    when :engine
+      case RUBY_NAME
+      when 'rbx'
+        "bin/rbx"
+      when 'jruby'
+        "bin/jruby"
+      when 'ironruby'
+        "ir"
+      end
+    when :name
+      bin = RUBY_NAME + (Config::CONFIG['EXEEXT'] || Config::CONFIG['exeext'] || '')
+      File.join(".", bin)
+    when :install_name
+      bin = Config::CONFIG["RUBY_INSTALL_NAME"] || Config::CONFIG["ruby_install_name"]
+      bin << (Config::CONFIG['EXEEXT'] || Config::CONFIG['exeext'] || '')
+      File.join(Config::CONFIG['bindir'], bin)
+    end
+  end
+
+  def resolve_ruby_exe
+    [:env, :engine, :name, :install_name].each do |option|
+      next unless cmd = ruby_exe_options(option)
+      exe = cmd.split.first
+
+      # It has been reported that File.executable is not reliable
+      # on Windows platforms (see commit 56bc555c). So, we check the
+      # platform. 
+      if File.exists?(exe) and (SpecGuard.windows? or File.executable?(exe))
+        return cmd
+      end
+    end
+    nil
+  end
+
+  def ruby_exe(code, opts = {})
+    body = code
+    working_dir = opts[:dir] || "."
+    Dir.chdir(working_dir) do
+      body = "-e #{code.inspect}" if code and not File.exists?(code)
+      cmd = [RUBY_EXE, ENV['RUBY_FLAGS'], opts[:options], body, opts[:args]]
+      `#{cmd.compact.join(' ')}`
+    end
+  end
+
+  unless Object.const_defined?(:RUBY_EXE) and RUBY_EXE
+    require 'rbconfig'
+
+    RUBY_EXE = resolve_ruby_exe or
+      raise Exception, "Unable to find a suitable ruby executable."
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/helpers/scratch.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/helpers/scratch.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/helpers/scratch.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,17 @@
+module ScratchPad
+  def self.clear
+    @record = nil
+  end
+
+  def self.record(arg)
+    @record = arg
+  end
+
+  def self.<<(arg)
+    @record << arg
+  end
+
+  def self.recorded
+    @record
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/helpers/tmp.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/helpers/tmp.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/helpers/tmp.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,32 @@
+# The #tmp method provides a similar functionality
+# to that of Dir.tmpdir. This helper can be overridden
+# by different implementations to provide a more useful
+# behavior if needed.
+#
+# Usage in a spec:
+#
+#   File.open(tmp("tags.txt"), "w") { |f| f.puts "" }
+#
+# The order of directories below with "/private/tmp"
+# preceding "/tmp" is significant. On OS X, the directory
+# "/tmp" is a symlink to "private/tmp" with no leading
+# "/". Rather than futzing with what constitutes an
+# absolute path, we just look for "/private/tmp" first.
+
+class Object
+  def tmp(name)
+    unless @spec_temp_directory
+      [ "/private/tmp", "/tmp", "/var/tmp", ENV["TMPDIR"], ENV["TMP"],
+        ENV["TEMP"], ENV["USERPROFILE"] ].each do |dir|
+        if dir and File.directory?(dir) and File.writable?(dir)
+          temp = File.expand_path dir
+          temp = File.readlink temp if File.symlink? temp
+          @spec_temp_directory = temp
+          break
+        end
+      end
+    end
+
+    File.join @spec_temp_directory, name
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/helpers.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/helpers.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/helpers.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,11 @@
+require 'mspec/helpers/argv'
+require 'mspec/helpers/bignum'
+require 'mspec/helpers/const_lookup'
+require 'mspec/helpers/environment'
+require 'mspec/helpers/fixture'
+require 'mspec/helpers/flunk'
+require 'mspec/helpers/io'
+require 'mspec/helpers/language_version'
+require 'mspec/helpers/ruby_exe'
+require 'mspec/helpers/scratch'
+require 'mspec/helpers/tmp'

Added: MacRuby/branches/experimental/mspec/lib/mspec/matchers/base.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/matchers/base.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/matchers/base.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,95 @@
+class PositiveOperatorMatcher
+  def initialize(actual)
+    @actual = actual
+  end
+
+  def ==(expected)
+    unless @actual == expected
+      Expectation.fail_with("Expected #{@actual.pretty_inspect}",
+                            "to equal #{expected.pretty_inspect}")
+    end
+  end
+
+  def <(expected)
+    unless @actual < expected
+      Expectation.fail_with("Expected #{@actual.pretty_inspect}",
+                            "to be less than #{expected.pretty_inspect}")
+    end
+  end
+
+  def <=(expected)
+    unless @actual <= expected
+      Expectation.fail_with("Expected #{@actual.pretty_inspect}",
+                            "to be less than or equal to #{expected.pretty_inspect}")
+    end
+  end
+
+  def >(expected)
+    unless @actual > expected
+      Expectation.fail_with("Expected #{@actual.pretty_inspect}",
+                            "to be greater than #{expected.pretty_inspect}")
+    end
+  end
+
+  def >=(expected)
+    unless @actual >= expected
+      Expectation.fail_with("Expected #{@actual.pretty_inspect}",
+                            "to be greater than or equal to #{expected.pretty_inspect}")
+    end
+  end
+
+  def =~(expected)
+    unless @actual =~ expected
+      Expectation.fail_with("Expected #{@actual.pretty_inspect}",
+                            "to match #{expected.pretty_inspect}")
+    end
+  end
+end
+
+class NegativeOperatorMatcher
+  def initialize(actual)
+    @actual = actual
+  end
+
+  def ==(expected)
+    if @actual == expected
+      Expectation.fail_with("Expected #{@actual.pretty_inspect}",
+                            "not to equal #{expected.pretty_inspect}")
+    end
+  end
+
+  def <(expected)
+    if @actual < expected
+      Expectation.fail_with("Expected #{@actual.pretty_inspect}",
+                            "not to be less than #{expected.pretty_inspect}")
+    end
+  end
+
+  def <=(expected)
+    if @actual <= expected
+      Expectation.fail_with("Expected #{@actual.pretty_inspect}",
+                            "not to be less than or equal to #{expected.pretty_inspect}")
+    end
+  end
+
+  def >(expected)
+    if @actual > expected
+      Expectation.fail_with("Expected #{@actual.pretty_inspect}",
+                            "not to be greater than #{expected.pretty_inspect}")
+    end
+  end
+
+  def >=(expected)
+    if @actual >= expected
+      Expectation.fail_with("Expected #{@actual.pretty_inspect}",
+                            "not to be greater than or equal to #{expected.pretty_inspect}")
+    end
+  end
+
+  def =~(expected)
+    if @actual =~ expected
+      Expectation.fail_with("Expected #{@actual.pretty_inspect}",
+                            "not to match #{expected.pretty_inspect}")
+    end
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/matchers/be_an_instance_of.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/matchers/be_an_instance_of.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/matchers/be_an_instance_of.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,26 @@
+class BeAnInstanceOfMatcher
+  def initialize(expected)
+    @expected = expected
+  end
+
+  def matches?(actual)
+    @actual = actual
+    @actual.instance_of?(@expected)
+  end
+
+  def failure_message
+    ["Expected #{@actual.inspect} (#{@actual.class})",
+     "to be an instance of #{@expected}"]
+  end
+
+  def negative_failure_message
+    ["Expected #{@actual.inspect} (#{@actual.class})",
+     "not to be an instance of #{@expected}"]
+  end
+end
+
+class Object
+  def be_an_instance_of(expected)
+    BeAnInstanceOfMatcher.new(expected)
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/matchers/be_ancestor_of.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/matchers/be_ancestor_of.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/matchers/be_ancestor_of.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,24 @@
+class BeAncestorOfMatcher
+  def initialize(expected)
+    @expected = expected
+  end
+
+  def matches?(actual)
+    @actual = actual
+    @expected.ancestors.include? @actual
+  end
+
+  def failure_message
+    ["Expected #{@actual}", "to be an ancestor of #{@expected}"]
+  end
+
+  def negative_failure_message
+    ["Expected #{@actual}", "not to be an ancestor of #{@expected}"]
+  end
+end
+
+class Object
+  def be_ancestor_of(expected)
+    BeAncestorOfMatcher.new(expected)
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/matchers/be_close.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/matchers/be_close.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/matchers/be_close.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,27 @@
+TOLERANCE = 0.00003 unless Object.const_defined?(:TOLERANCE)
+
+class BeCloseMatcher
+  def initialize(expected, tolerance)
+    @expected = expected
+    @tolerance = tolerance
+  end
+
+  def matches?(actual)
+    @actual = actual
+    (@actual - @expected).abs < @tolerance
+  end
+
+  def failure_message
+    ["Expected #{@expected}", "to be within +/- #{@tolerance} of #{@actual}"]
+  end
+
+  def negative_failure_message
+    ["Expected #{@expected}", "not to be within +/- #{@tolerance} of #{@actual}"]
+  end
+end
+
+class Object
+  def be_close(expected, tolerance)
+    BeCloseMatcher.new(expected, tolerance)
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/matchers/be_empty.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/matchers/be_empty.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/matchers/be_empty.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,20 @@
+class BeEmptyMatcher
+  def matches?(actual)
+    @actual = actual
+    @actual.empty?
+  end
+
+  def failure_message
+    ["Expected #{@actual.inspect}", "to be empty"]
+  end
+
+  def negative_failure_message
+    ["Expected #{@actual.inspect}", "not to be empty"]
+  end
+end
+
+class Object
+  def be_empty
+    BeEmptyMatcher.new
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/matchers/be_false.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/matchers/be_false.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/matchers/be_false.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,20 @@
+class BeFalseMatcher
+  def matches?(actual)
+    @actual = actual
+    @actual == false
+  end
+
+  def failure_message
+    ["Expected #{@actual.inspect}", "to be false"]
+  end
+  
+  def negative_failure_message
+    ["Expected #{@actual.inspect}", "not to be false"]
+  end
+end
+
+class Object
+  def be_false
+    BeFalseMatcher.new
+  end
+end
\ No newline at end of file

Added: MacRuby/branches/experimental/mspec/lib/mspec/matchers/be_kind_of.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/matchers/be_kind_of.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/matchers/be_kind_of.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,24 @@
+class BeKindOfMatcher
+  def initialize(expected)
+    @expected = expected
+  end
+
+  def matches?(actual)
+    @actual = actual
+    @actual.is_a?(@expected)
+  end
+
+  def failure_message
+    ["Expected #{@actual.inspect} (#{@actual.class})", "to be kind of #{@expected}"]
+  end
+
+  def negative_failure_message
+    ["Expected #{@actual.inspect} (#{@actual.class})", "not to be kind of #{@expected}"]
+  end
+end
+
+class Object
+  def be_kind_of(expected)
+    BeKindOfMatcher.new(expected)
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/matchers/be_nil.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/matchers/be_nil.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/matchers/be_nil.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,20 @@
+class BeNilMatcher
+  def matches?(actual)
+    @actual = actual
+    @actual.nil?
+  end
+
+  def failure_message
+    ["Expected #{@actual.inspect}", "to be nil"]
+  end
+  
+  def negative_failure_message
+    ["Expected #{@actual.inspect}", "not to be nil"]
+  end
+end
+
+class Object
+  def be_nil
+    BeNilMatcher.new
+  end
+end
\ No newline at end of file

Added: MacRuby/branches/experimental/mspec/lib/mspec/matchers/be_true.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/matchers/be_true.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/matchers/be_true.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,20 @@
+class BeTrueMatcher
+  def matches?(actual)
+    @actual = actual
+    @actual == true
+  end
+
+  def failure_message
+    ["Expected #{@actual.inspect}", "to be true"]
+  end
+  
+  def negative_failure_message
+    ["Expected #{@actual.inspect}", "not to be true"]
+  end
+end
+
+class Object
+  def be_true
+    BeTrueMatcher.new
+  end
+end
\ No newline at end of file

Added: MacRuby/branches/experimental/mspec/lib/mspec/matchers/complain.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/matchers/complain.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/matchers/complain.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,56 @@
+require 'mspec/helpers/io'
+
+class ComplainMatcher
+  def initialize(complaint)
+    @complaint = complaint
+  end
+
+  def matches?(proc)
+    @saved_err = $stderr
+    @stderr = $stderr = IOStub.new
+    @verbose = $VERBOSE
+    $VERBOSE = false
+
+    proc.call
+
+    unless @complaint.nil?
+      case @complaint
+      when Regexp
+        return false unless $stderr =~ @complaint
+      else
+        return false unless $stderr == @complaint
+      end
+    end
+
+    return $stderr.empty? ? false : true
+  ensure
+    $VERBOSE = @verbose
+    $stderr = @saved_err
+  end
+
+  def failure_message
+    if @complaint.nil?
+      ["Expected a warning", "but received none"]
+    elsif @complaint.kind_of? Regexp
+      ["Expected warning to match:", @complaint.inspect]
+    else
+      ["Expected warning: #{@complaint.inspect}", "but got: #{@stderr.chomp.inspect}"]
+    end
+  end
+
+  def negative_failure_message
+    if @complaint.nil?
+      ["Unexpected warning: ", @stderr.chomp.inspect]
+    elsif @complaint.kind_of? Regexp
+      ["Expected warning not to match:", @complaint.inspect]
+    else
+      ["Expected warning: #{@complaint.inspect}", "but got: #{@stderr.chomp.inspect}"]
+    end
+  end
+end
+
+class Object
+  def complain(complaint=nil)
+    ComplainMatcher.new(complaint)
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/matchers/eql.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/matchers/eql.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/matchers/eql.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,26 @@
+class EqlMatcher
+  def initialize(expected)
+    @expected = expected
+  end
+
+  def matches?(actual)
+    @actual = actual
+    @actual.eql?(@expected)
+  end
+
+  def failure_message
+    ["Expected #{@actual.pretty_inspect}",
+     "to have same value and type as #{@expected.pretty_inspect}"]
+  end
+
+  def negative_failure_message
+    ["Expected #{@actual.pretty_inspect}",
+     "not to have same value or type as #{@expected.pretty_inspect}"]
+  end
+end
+
+class Object
+  def eql(expected)
+    EqlMatcher.new(expected)
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/matchers/equal.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/matchers/equal.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/matchers/equal.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,26 @@
+class EqualMatcher
+  def initialize(expected)
+    @expected = expected
+  end
+
+  def matches?(actual)
+    @actual = actual
+    @actual.equal?(@expected)
+  end
+
+  def failure_message
+    ["Expected #{@actual.pretty_inspect}",
+     "to be identical to #{@expected.pretty_inspect}"]
+  end
+
+  def negative_failure_message
+    ["Expected #{@actual.pretty_inspect}",
+     "not to be identical to #{@expected.pretty_inspect}"]
+  end
+end
+
+class Object
+  def equal(expected)
+    EqualMatcher.new(expected)
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/matchers/equal_element.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/matchers/equal_element.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/matchers/equal_element.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,78 @@
+class EqualElementMatcher
+  def initialize(element, attributes = nil, content = nil, options = {})
+    @element = element
+    @attributes = attributes
+    @content = content
+    @options = options
+  end
+ 
+  def matches?(actual)
+    @actual = actual
+    
+    matched = true
+    
+    if @options[:not_closed]
+      matched &&= actual =~ /^#{Regexp.quote("<" + @element)}.*#{Regexp.quote(">" + (@content || ''))}$/
+    else
+      matched &&= actual =~ /^#{Regexp.quote("<" + @element)}/
+      matched &&= actual =~ /#{Regexp.quote("</" + @element + ">")}$/
+      matched &&= actual =~ /#{Regexp.quote(">" + @content + "</")}/ if @content      
+    end
+    
+    if @attributes
+      if @attributes.empty?
+        matched &&= actual.scan(/\w+\=\"(.*)\"/).size == 0
+      else
+        @attributes.each do |key, value|
+          if value == true
+            matched &&= (actual.scan(/#{Regexp.quote(key)}(\s|>)/).size == 1)
+          else
+            matched &&= (actual.scan(%Q{ #{key}="#{value}"}).size == 1)
+          end
+        end        
+      end
+    end
+    
+    !!matched
+  end
+ 
+  def failure_message
+    ["Expected #{@actual.pretty_inspect}",
+     "to be a '#{@element}' element with #{attributes_for_failure_message} and #{content_for_failure_message}"]
+  end
+ 
+  def negative_failure_message
+    ["Expected #{@actual.pretty_inspect}",
+      "not to be a '#{@element}' element with #{attributes_for_failure_message} and #{content_for_failure_message}"]
+  end
+
+  def attributes_for_failure_message
+    if @attributes
+      if @attributes.empty?
+        "no attributes"
+      else
+        @attributes.inject([]) { |memo, n| memo << %Q{#{n[0]}="#{n[1]}"} }.join(" ")
+      end
+    else
+      "any attributes"
+    end
+  end
+ 
+  def content_for_failure_message
+    if @content
+      if @content.empty?
+        "no content"
+      else  
+        "#{@content.inspect} as content"
+      end
+    else
+      "any content"
+    end
+  end
+end
+ 
+class Object
+  def equal_element(*args)
+    EqualElementMatcher.new(*args)
+  end
+end
\ No newline at end of file

Added: MacRuby/branches/experimental/mspec/lib/mspec/matchers/equal_utf16.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/matchers/equal_utf16.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/matchers/equal_utf16.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,34 @@
+class EqualUtf16Matcher
+  def initialize(expected)
+    @expected = expected
+  end
+
+  def matches?(actual)
+    @actual = actual
+    @actual == @expected || @actual == expected_swapped
+  end
+
+  def expected_swapped
+    if @expected.respond_to?(:to_str)
+      @expected_swapped ||= @expected.to_str.gsub(/(.)(.)/, '\2\1')
+    else
+      @expected_swapped ||= @expected.collect { |s| s.to_str.gsub(/(.)(.)/, '\2\1') }
+    end
+  end
+
+  def failure_message
+    ["Expected #{@actual.pretty_inspect}",
+     "to equal #{@expected.pretty_inspect} or #{expected_swapped.pretty_inspect}"]
+  end
+
+  def negative_failure_message
+    ["Expected #{@actual.pretty_inspect}",
+     "not to equal #{@expected.pretty_inspect} nor #{expected_swapped.pretty_inspect}"]
+  end
+end
+
+class Object
+  def equal_utf16(expected)
+    EqualUtf16Matcher.new(expected)
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/matchers/have_constant.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/matchers/have_constant.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/matchers/have_constant.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,30 @@
+require 'mspec/matchers/stringsymboladapter'
+
+class HaveConstantMatcher
+  include StringSymbolAdapter
+
+  def initialize(name)
+    @name = convert_name name
+  end
+
+  def matches?(mod)
+    @mod = mod
+    @mod.constants.include? @name
+  end
+
+  def failure_message
+    ["Expected #{@mod} to have constant '#{@name.to_s}'",
+     "but it does not"]
+  end
+
+  def negative_failure_message
+    ["Expected #{@mod} NOT to have constant '#{@name.to_s}'",
+     "but it does"]
+  end
+end
+
+class Object
+  def have_constant(name)
+    HaveConstantMatcher.new name
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/matchers/have_instance_method.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/matchers/have_instance_method.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/matchers/have_instance_method.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,24 @@
+require 'mspec/matchers/method'
+
+class HaveInstanceMethodMatcher < MethodMatcher
+  def matches?(mod)
+    @mod = mod
+    mod.instance_methods(@include_super).include? @method
+  end
+
+  def failure_message
+    ["Expected #{@mod} to have instance method '#{@method.to_s}'",
+     "but it does not"]
+  end
+
+  def negative_failure_message
+    ["Expected #{@mod} NOT to have instance method '#{@method.to_s}'",
+     "but it does"]
+  end
+end
+
+class Object
+  def have_instance_method(method, include_super=true)
+    HaveInstanceMethodMatcher.new method, include_super
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/matchers/have_method.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/matchers/have_method.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/matchers/have_method.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,24 @@
+require 'mspec/matchers/method'
+
+class HaveMethodMatcher < MethodMatcher
+  def matches?(mod)
+    @mod = mod
+    @mod.methods(@include_super).include? @method
+  end
+
+  def failure_message
+    ["Expected #{@mod} to have method '#{@method.to_s}'",
+     "but it does not"]
+  end
+
+  def negative_failure_message
+    ["Expected #{@mod} NOT to have method '#{@method.to_s}'",
+     "but it does"]
+  end
+end
+
+class Object
+  def have_method(method, include_super=true)
+    HaveMethodMatcher.new method, include_super
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/matchers/have_private_instance_method.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/matchers/have_private_instance_method.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/matchers/have_private_instance_method.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,24 @@
+require 'mspec/matchers/method'
+
+class HavePrivateInstanceMethodMatcher < MethodMatcher
+  def matches?(mod)
+    @mod = mod
+    mod.private_instance_methods(@include_super).include? @method
+  end
+
+  def failure_message
+    ["Expected #{@mod} to have private instance method '#{@method.to_s}'",
+     "but it does not"]
+  end
+
+  def negative_failure_message
+    ["Expected #{@mod} NOT to have private instance method '#{@method.to_s}'",
+     "but it does"]
+  end
+end
+
+class Object
+  def have_private_instance_method(method, include_super=true)
+    HavePrivateInstanceMethodMatcher.new method, include_super
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/matchers/include.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/matchers/include.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/matchers/include.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,32 @@
+class IncludeMatcher
+  def initialize(*expected)
+    @expected = expected
+  end
+
+  def matches?(actual)
+    @actual = actual
+    @expected.each do |e|
+      @element = e
+      unless @actual.include?(e)
+        return false
+      end
+    end
+    return true
+  end
+
+  def failure_message
+    ["Expected #{@actual.inspect}", "to include #{@element.inspect}"]
+  end
+
+  def negative_failure_message
+    ["Expected #{@actual.inspect}", "not to include #{@element.inspect}"]
+  end
+end
+
+# Cannot override #include at the toplevel in MRI
+module MSpec
+  def include(*expected)
+    IncludeMatcher.new(*expected)
+  end
+  module_function :include
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/matchers/match_yaml.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/matchers/match_yaml.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/matchers/match_yaml.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,47 @@
+class MatchYAMLMatcher
+
+  def initialize(expected)
+    if valid_yaml?(expected)
+      @expected = expected
+    else
+      @expected = expected.to_yaml
+    end
+  end
+
+  def matches?(actual)
+    @actual = actual    
+    clean_yaml(@actual) == @expected
+  end
+
+  def failure_message
+    ["Expected #{@actual.inspect}", " to match #{@expected.inspect}"]
+  end
+
+  def negative_failure_message
+    ["Expected #{@actual.inspect}", " to match #{@expected.inspect}"]
+  end
+  
+  protected
+  
+  def clean_yaml(yaml)
+    yaml.gsub(/([^-])\s+\n/, "\\1\n")
+  end
+
+  def valid_yaml?(obj)
+    require 'yaml'
+    begin
+      YAML.load(obj)
+    rescue
+      false
+    else
+      true
+    end
+  end
+end
+
+class Object
+  def match_yaml(expected)
+    MatchYAMLMatcher.new(expected)
+  end
+end
+

Added: MacRuby/branches/experimental/mspec/lib/mspec/matchers/method.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/matchers/method.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/matchers/method.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,14 @@
+require 'mspec/matchers/stringsymboladapter'
+
+class MethodMatcher
+  include StringSymbolAdapter
+
+  def initialize(method, include_super=true)
+    @include_super = include_super
+    @method = convert_name method
+  end
+
+  def matches?(mod)
+    raise Exception, "define #matches? in the subclass"
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/matchers/output.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/matchers/output.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/matchers/output.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,67 @@
+require 'mspec/helpers/io'
+
+class OutputMatcher
+  def initialize(stdout, stderr)
+    @out = stdout
+    @err = stderr
+  end
+
+  def matches?(proc)
+    @saved_out = $stdout
+    @saved_err = $stderr
+    @stdout = $stdout = IOStub.new
+    @stderr = $stderr = IOStub.new
+
+    proc.call
+
+    unless @out.nil?
+      case @out
+      when Regexp
+        return false unless $stdout =~ @out
+      else
+        return false unless $stdout == @out
+      end
+    end
+
+    unless @err.nil?
+      case @err
+      when Regexp
+        return false unless $stderr =~ @err
+      else
+        return false unless $stderr == @err
+      end
+    end
+
+    return true
+  ensure
+    $stdout = @saved_out
+    $stderr = @saved_err
+  end
+
+  def failure_message
+    expected_out = "\n"
+    actual_out = "\n"
+    unless @out.nil?
+      expected_out << "  $stdout: #{@out.inspect}\n"
+      actual_out << "  $stdout: #{@stdout.chomp.inspect}\n"
+    end
+    unless @err.nil?
+      expected_out << "  $stderr: #{@err.inspect}\n"
+      actual_out << "  $stderr: #{@stderr.chomp.inspect}\n"
+    end
+    ["Expected:#{expected_out}", "     got:#{actual_out}"]
+  end
+
+  def negative_failure_message
+    out = ""
+    out << "  $stdout: #{@stdout.chomp.dump}\n" unless @out.nil?
+    out << "  $stderr: #{@stderr.chomp.dump}\n" unless @err.nil?
+    ["Expected output not to be:\n", out]
+  end
+end
+
+class Object
+  def output(stdout=nil, stderr=nil)
+    OutputMatcher.new(stdout, stderr)
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/matchers/output_to_fd.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/matchers/output_to_fd.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/matchers/output_to_fd.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,71 @@
+require 'mspec/helpers/tmp'
+require 'fileutils'
+
+# Lower-level output speccing mechanism for a single
+# output stream. Unlike OutputMatcher which provides
+# methods to capture the output, we actually replace
+# the FD itself so that there is no reliance on a
+# certain method being used.
+class OutputToFDMatcher
+  def initialize(expected, to)
+    @to, @expected = to, expected
+
+    case @to
+    when STDOUT
+      @to_name = "STDOUT"
+    when STDERR
+      @to_name = "STDERR"
+    when IO
+      @to_name = @to.object_id.to_s
+    else
+      raise ArgumentError, "#{@to.inspect} is not a supported output target"
+    end
+  end
+
+  def matches?(block)
+    old_to = @to.dup
+    out = File.open(tmp("mspec_output_to_#{$$}_#{Time.now.to_i}"), 'w+')
+
+    # Replacing with a file handle so that Readline etc. work
+    @to.reopen out
+
+    block.call
+
+  ensure
+    begin
+      @to.reopen old_to
+
+      out.rewind
+      @actual = out.read
+
+      case @expected
+        when Regexp
+          return !(@actual =~ @expected).nil?
+        else
+          return @actual == @expected
+      end
+
+    # Clean up
+    ensure
+      out.close unless out.closed?
+      FileUtils.rm out.path
+    end
+
+    return true
+  end
+
+  def failure_message()
+    ["Expected (#{@to_name}): #{@expected.inspect}\n",
+     "#{'but got'.rjust(@to_name.length + 10)}: #{@actual.inspect}\nBacktrace"]
+  end
+
+  def negative_failure_message()
+    ["Expected output (#{@to_name}) to NOT be:\n", @actual.inspect]
+  end
+end
+
+class Object
+  def output_to_fd(what, where = STDOUT)
+    OutputToFDMatcher.new what, where
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/matchers/raise_error.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/matchers/raise_error.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/matchers/raise_error.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,48 @@
+class RaiseErrorMatcher
+  def initialize(exception, message, &block)
+    @exception = exception
+    @message = message
+    @block = block
+  end
+
+  def matches?(proc)
+    proc.call
+    return false
+  rescue Exception => @actual
+    return false unless @exception === @actual
+    if @message then
+      case @message
+      when String then
+        return false if @message != @actual.message
+      when Regexp then
+        return false if @message !~ @actual.message
+      end
+    end
+
+    @block[@actual] if @block
+
+    return true
+  end
+
+  def failure_message
+    message = ["Expected #{@exception}#{%[ (#{@message})] if @message}"]
+
+    if @actual then
+      message << "but got #{@actual.class}#{%[ (#{@actual.message})] if @actual.message}"
+    else
+      message << "but no exception was raised"
+    end
+
+    message
+  end
+
+  def negative_failure_message
+    ["Expected to not get #{@exception}#{%[ (#{@message})] if @message}", ""]
+  end
+end
+
+class Object
+  def raise_error(exception=Exception, message=nil, &block)
+    RaiseErrorMatcher.new(exception, message, &block)
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/matchers/respond_to.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/matchers/respond_to.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/matchers/respond_to.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,24 @@
+class RespondToMatcher
+  def initialize(expected)
+    @expected = expected
+  end
+
+  def matches?(actual)
+    @actual = actual
+    @actual.respond_to?(@expected)
+  end
+
+  def failure_message
+    ["Expected #{@actual.inspect} (#{@actual.class})", "to respond to #{@expected}"]
+  end
+
+  def negative_failure_message
+    ["Expected #{@actual.inspect} (#{@actual.class})", "not to respond to #{@expected}"]
+  end
+end
+
+class Object
+  def respond_to(expected)
+    RespondToMatcher.new(expected)
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/matchers/stringsymboladapter.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/matchers/stringsymboladapter.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/matchers/stringsymboladapter.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,8 @@
+require 'mspec/utils/version'
+
+module StringSymbolAdapter
+  def convert_name(name)
+    version = SpecVersion.new(RUBY_VERSION) <=> "1.9"
+    version < 0 ? name.to_s : name
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/matchers.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/matchers.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/matchers.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,23 @@
+require 'mspec/matchers/base'
+require 'mspec/matchers/be_ancestor_of'
+require 'mspec/matchers/be_close'
+require 'mspec/matchers/be_empty'
+require 'mspec/matchers/be_false'
+require 'mspec/matchers/be_kind_of'
+require 'mspec/matchers/be_nil'
+require 'mspec/matchers/be_true'
+require 'mspec/matchers/complain'
+require 'mspec/matchers/eql'
+require 'mspec/matchers/equal'
+require 'mspec/matchers/equal_element'
+require 'mspec/matchers/equal_utf16'
+require 'mspec/matchers/have_constant'
+require 'mspec/matchers/have_instance_method'
+require 'mspec/matchers/have_method'
+require 'mspec/matchers/have_private_instance_method'
+require 'mspec/matchers/include'
+require 'mspec/matchers/match_yaml'
+require 'mspec/matchers/raise_error'
+require 'mspec/matchers/output'
+require 'mspec/matchers/output_to_fd'
+require 'mspec/matchers/respond_to'

Added: MacRuby/branches/experimental/mspec/lib/mspec/mocks/mock.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/mocks/mock.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/mocks/mock.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,155 @@
+require 'mspec/expectations/expectations'
+
+module Mock
+  def self.reset
+    @mocks = @stubs = nil
+  end
+
+  def self.mocks
+    @mocks ||= Hash.new { |h,k| h[k] = [] }
+  end
+
+  def self.stubs
+    @stubs ||= Hash.new { |h,k| h[k] = [] }
+  end
+
+  def self.replaced_name(obj, sym)
+    :"__ms_#{obj.__id__}_#{sym}__"
+  end
+
+  def self.replaced_key(obj, sym)
+    [replaced_name(obj, sym), obj, sym]
+  end
+
+  def self.replaced?(key)
+    !!(mocks.keys + stubs.keys).find { |k| k.first == key.first }
+  end
+
+  def self.install_method(obj, sym, type=nil)
+    meta = class << obj; self; end
+
+    key = replaced_key obj, sym
+    if (sym.to_sym == :respond_to? or obj.respond_to?(sym)) and !replaced?(key)
+      meta.__send__ :alias_method, key.first, sym.to_sym
+    end
+
+    meta.class_eval <<-END
+      def #{sym}(*args, &block)
+        Mock.verify_call self, :#{sym}, *args, &block
+      end
+    END
+
+    proxy = MockProxy.new type
+
+    if proxy.mock?
+      MSpec.expectation
+      MSpec.actions :expectation, MSpec.current.state
+    end
+
+    if proxy.stub?
+      stubs[key].unshift proxy
+    else
+      mocks[key] << proxy
+    end
+
+    proxy
+  end
+
+  def self.name_or_inspect(obj)
+    obj.instance_variable_get(:@name) || obj.inspect
+  end
+
+  def self.verify_count
+    mocks.each do |key, proxies|
+      replaced, obj, sym = *key
+      proxies.each do |proxy|
+        qualifier, count = proxy.count
+        pass = case qualifier
+        when :at_least
+          proxy.calls >= count
+        when :at_most
+          proxy.calls <= count
+        when :exactly
+          proxy.calls == count
+        when :any_number_of_times
+          true
+        else
+          false
+        end
+        unless pass
+          Expectation.fail_with(
+            "Mock '#{name_or_inspect obj}' expected to receive '#{sym}' " \
+            "#{qualifier.to_s.sub('_', ' ')} #{count} times",
+            "but received it #{proxy.calls} times")
+        end
+      end
+    end
+  end
+
+  def self.verify_call(obj, sym, *args, &block)
+    compare = *args
+    if RUBY_VERSION >= '1.9'
+      compare = compare.first if compare.length <= 1
+    end
+
+    key = replaced_key obj, sym
+    proxies = mocks[key] + stubs[key]
+    proxies.each do |proxy|
+      pass = case proxy.arguments
+      when :any_args
+        true
+      when :no_args
+        compare.nil?
+      else
+        proxy.arguments == compare
+      end
+
+      if proxy.yielding?
+        if block
+          proxy.yielding.each do |args_to_yield|
+            if block.arity == -1 || block.arity == args_to_yield.size
+              block.call(*args_to_yield)
+            else
+              Expectation.fail_with(
+                "Mock '#{name_or_inspect obj}' asked to yield " \
+                "|#{proxy.yielding.join(', ')}| on #{sym}\n",
+                "but a block with arity #{block.arity} was passed")
+            end
+          end
+        else
+          Expectation.fail_with(
+            "Mock '#{name_or_inspect obj}' asked to yield " \
+            "|[#{proxy.yielding.join('], [')}]| on #{sym}\n",
+            "but no block was passed")
+        end
+      end
+
+      if pass
+        proxy.called
+        return proxy.returning
+      end
+    end
+
+    if sym.to_sym == :respond_to?
+      return obj.__send__(replaced_name(obj, sym), compare)
+    else
+      Expectation.fail_with("Mock '#{name_or_inspect obj}': method #{sym}\n",
+                            "called with unexpected arguments (#{Array(compare).join(' ')})")
+    end
+  end
+
+  def self.cleanup
+    symbols = mocks.keys + stubs.keys
+    symbols.uniq.each do |replaced, obj, sym|
+      meta = class << obj; self; end
+
+      if meta.instance_methods.include?(replaced.to_s)
+        meta.__send__ :alias_method, sym.to_sym, replaced
+        meta.__send__ :remove_method, replaced
+      else
+        meta.__send__ :remove_method, sym.to_sym
+      end
+    end
+    reset
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/mocks/object.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/mocks/object.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/mocks/object.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,20 @@
+require 'mspec/mocks/proxy'
+
+class Object
+  def stub!(sym)
+    Mock.install_method self, sym, :stub
+  end
+
+  def should_receive(sym)
+    Mock.install_method self, sym
+  end
+
+  def should_not_receive(sym)
+    proxy = Mock.install_method self, sym
+    proxy.exactly(0).times
+  end
+
+  def mock(name, options={})
+    MockObject.new name, options
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/mocks/proxy.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/mocks/proxy.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/mocks/proxy.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,136 @@
+class MockObject
+  def initialize(name, options={})
+    @name = name
+    @null = options[:null_object]
+  end
+
+  def method_missing(sym, *args, &block)
+    @null ? self : super
+  end
+end
+
+class MockProxy
+  def initialize(type=nil)
+    @multiple_returns = nil
+    @returning = nil
+    @yielding  = []
+    @arguments = :any_args
+    @type      = type || :mock
+  end
+
+  def mock?
+    @type == :mock
+  end
+
+  def stub?
+    @type == :stub
+  end
+
+  def count
+    @count ||= mock? ? [:exactly, 1] : [:any_number_of_times, 0]
+  end
+
+  def arguments
+    @arguments
+  end
+
+  def returning
+    if @multiple_returns
+      if @returning.size == 1
+        @multiple_returns = false
+        return @returning = @returning.shift
+      end
+      return @returning.shift
+    end
+    @returning
+  end
+
+  def times
+    self
+  end
+
+  def calls
+    @calls ||= 0
+  end
+
+  def called
+    @calls = calls + 1
+  end
+
+  def exactly(n)
+    @count = [:exactly, n_times(n)]
+    self
+  end
+
+  def at_least(n)
+    @count = [:at_least, n_times(n)]
+    self
+  end
+
+  def at_most(n)
+    @count = [:at_most, n_times(n)]
+    self
+  end
+
+  def once
+    exactly 1
+  end
+
+  def twice
+    exactly 2
+  end
+
+  def any_number_of_times
+    @count = [:any_number_of_times, 0]
+    self
+  end
+
+  def with(*args)
+    raise ArgumentError, "you must specify the expected arguments" if args.empty?
+    @arguments = *args
+    if RUBY_VERSION >= '1.9'
+      @arguments = @arguments.first if @arguments.length <= 1
+    end
+    self
+  end
+
+  def and_return(*args)
+    case args.size
+    when 0
+      @returning = nil
+    when 1
+      @returning = args[0]
+    else
+      @multiple_returns = true
+      @returning = args
+      count[1] = args.size if count[1] < args.size
+    end
+    self
+  end
+
+  def and_yield(*args)
+    @yielding << args
+    self
+  end
+  
+  def yielding
+    @yielding
+  end
+  
+  def yielding?
+    !@yielding.empty?
+  end
+
+  private
+
+  def n_times(n)
+    case n
+    when :once
+      1
+    when :twice
+      2
+    else
+      Integer n
+    end
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/mocks.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/mocks.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/mocks.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,3 @@
+require 'mspec/mocks/mock'
+require 'mspec/mocks/proxy'
+require 'mspec/mocks/object'

Added: MacRuby/branches/experimental/mspec/lib/mspec/runner/actions/debug.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/runner/actions/debug.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/runner/actions/debug.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,17 @@
+require 'mspec/runner/actions/filter'
+
+class DebugAction < ActionFilter
+  def before(state)
+    Kernel.debugger if self === state.description
+  end
+
+  def register
+    super
+    MSpec.register :before, self
+  end
+
+  def unregister
+    super
+    MSpec.unregister :before, self
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/runner/actions/filter.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/runner/actions/filter.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/runner/actions/filter.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,40 @@
+require 'mspec/runner/filters/match'
+
+# ActionFilter is a base class for actions that are triggered by
+# specs that match the filter. The filter may be specified by
+# strings that match spec descriptions or by tags for strings
+# that match spec descriptions.
+#
+# Unlike TagFilter and RegexpFilter, ActionFilter instances do
+# not affect the specs that are run. The filter is only used to
+# trigger the action.
+
+class ActionFilter
+  def initialize(tags=nil, descs=nil)
+    @tags = Array(tags)
+    descs = Array(descs)
+    @sfilter = MatchFilter.new(nil, *descs) unless descs.empty?
+  end
+
+  def ===(string)
+    @sfilter === string or @tfilter === string
+  end
+
+  def load
+    @tfilter = nil
+    return if @tags.empty?
+
+    desc = MSpec.read_tags(@tags).map { |t| t.description }
+    return if desc.empty?
+
+    @tfilter = MatchFilter.new(nil, *desc)
+  end
+
+  def register
+    MSpec.register :load, self
+  end
+
+  def unregister
+    MSpec.unregister :load, self
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/runner/actions/gdb.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/runner/actions/gdb.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/runner/actions/gdb.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,17 @@
+require 'mspec/runner/actions/filter'
+
+class GdbAction < ActionFilter
+  def before(state)
+    Kernel.yield_gdb(true) if self === state.description
+  end
+
+  def register
+    super
+    MSpec.register :before, self
+  end
+
+  def unregister
+    super
+    MSpec.unregister :before, self
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/runner/actions/tag.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/runner/actions/tag.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/runner/actions/tag.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,133 @@
+require 'mspec/runner/actions/filter'
+
+# TagAction - Write tagged spec description string to a
+# tag file associated with each spec file.
+#
+# The action is triggered by specs whose descriptions
+# match the filter created with 'tags' and/or 'desc'
+#
+# The action fires in the :after event, after the spec
+# had been run. The action fires if the outcome of
+# running the spec matches 'outcome'.
+#
+# The arguments are:
+#
+#   action:  :add, :del
+#   outcome: :pass, :fail, :all
+#   tag:     the tag to create/delete
+#   comment: the comment to create
+#   tags:    zero or more tags to get matching
+#            spec description strings from
+#   desc:    zero or more strings to match the
+#            spec description strings
+
+class TagAction < ActionFilter
+  def initialize(action, outcome, tag, comment, tags=nil, descs=nil)
+    super tags, descs
+    @action = action
+    @outcome = outcome
+    @tag = tag
+    @comment = comment
+    @report = []
+    @exception = false
+  end
+
+  # Returns true if there are no _tag_ or _description_ filters. This
+  # means that a TagAction matches any example by default. Otherwise,
+  # returns true if either the _tag_ or the _description_ filter
+  # matches +string+.
+  def ===(string)
+    return true unless @sfilter or @tfilter
+    @sfilter === string or @tfilter === string
+  end
+
+  # Callback for the MSpec :before event. Resets the +#exception?+
+  # flag to false.
+  def before(state)
+    @exception = false
+  end
+
+  # Callback for the MSpec :exception event. Sets the +#exception?+
+  # flag to true.
+  def exception(exception)
+    @exception = true
+  end
+
+  # Callback for the MSpec :after event. Performs the tag action
+  # depending on the type of action and the outcome of evaluating
+  # the example. See +TagAction+ for a description of the actions.
+  def after(state)
+    if self === state.description and outcome?
+      tag = SpecTag.new
+      tag.tag = @tag
+      tag.comment = @comment
+      tag.description = state.description
+
+      case @action
+      when :add
+        changed = MSpec.write_tag tag
+      when :del
+        changed = MSpec.delete_tag tag
+      end
+
+      @report << state.description if changed
+    end
+  end
+
+  # Returns true if the result of evaluating the example matches
+  # the _outcome_ registered for this tag action. See +TagAction+
+  # for a description of the _outcome_ types.
+  def outcome?
+    @outcome == :all or
+        (@outcome == :pass and not exception?) or
+        (@outcome == :fail and exception?)
+  end
+
+  # Returns true if an exception was raised while evaluating the
+  # current example.
+  def exception?
+    @exception
+  end
+
+  def report
+    @report.join("\n") + "\n"
+  end
+  private :report
+
+  # Callback for the MSpec :finish event. Prints the actions
+  # performed while evaluating the examples.
+  def finish
+    case @action
+    when :add
+      if @report.empty?
+        print "\nTagAction: no specs were tagged with '#{@tag}'\n"
+      else
+        print "\nTagAction: specs tagged with '#{@tag}':\n\n"
+        print report
+      end
+    when :del
+      if @report.empty?
+        print "\nTagAction: no tags '#{@tag}' were deleted\n"
+      else
+        print "\nTagAction: tag '#{@tag}' deleted for specs:\n\n"
+        print report
+      end
+    end
+  end
+
+  def register
+    super
+    MSpec.register :before,    self
+    MSpec.register :exception, self
+    MSpec.register :after,     self
+    MSpec.register :finish,    self
+  end
+
+  def unregister
+    super
+    MSpec.unregister :before,    self
+    MSpec.unregister :exception, self
+    MSpec.unregister :after,     self
+    MSpec.unregister :finish,    self
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/runner/actions/taglist.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/runner/actions/taglist.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/runner/actions/taglist.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,56 @@
+require 'mspec/runner/actions/filter'
+
+# TagListAction - prints out the descriptions for any specs
+# tagged with +tags+. If +tags+ is an empty list, prints out
+# descriptions for any specs that are tagged.
+class TagListAction
+  def initialize(tags=nil)
+    @tags = tags.nil? || tags.empty? ? nil : Array(tags)
+    @filter = nil
+  end
+
+  # Returns true. This enables us to match any tag when loading
+  # tags from the file.
+  def include?(arg)
+    true
+  end
+
+  # Returns true if any tagged descriptions matches +string+.
+  def ===(string)
+    @filter === string
+  end
+
+  # Prints a banner about matching tagged specs.
+  def start
+    if @tags
+      print "\nListing specs tagged with #{@tags.map { |t| "'#{t}'" }.join(", ") }\n\n"
+    else
+      print "\nListing all tagged specs\n\n"
+    end
+  end
+
+  # Creates a MatchFilter for specific tags or for all tags.
+  def load
+    @filter = nil
+    desc = MSpec.read_tags(@tags || self).map { |t| t.description }
+    @filter = MatchFilter.new(nil, *desc) unless desc.empty?
+  end
+
+  # Prints the spec description if it matches the filter.
+  def after(state)
+    return unless self === state.description
+    print state.description, "\n"
+  end
+
+  def register
+    MSpec.register :start, self
+    MSpec.register :load,  self
+    MSpec.register :after, self
+  end
+
+  def unregister
+    MSpec.unregister :start, self
+    MSpec.unregister :load,  self
+    MSpec.unregister :after, self
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/runner/actions/tagpurge.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/runner/actions/tagpurge.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/runner/actions/tagpurge.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,56 @@
+require 'mspec/runner/actions/filter'
+require 'mspec/runner/actions/taglist'
+
+# TagPurgeAction - removes all tags not matching any spec
+# descriptions.
+class TagPurgeAction < TagListAction
+  attr_reader :matching
+
+  def initialize
+    @matching = []
+    @filter   = nil
+    @tags     = nil
+  end
+
+  # Prints a banner about purging tags.
+  def start
+    print "\nRemoving tags not matching any specs\n\n"
+  end
+
+  # Creates a MatchFilter for all tags.
+  def load
+    @filter = nil
+    @tags = MSpec.read_tags self
+    desc = @tags.map { |t| t.description }
+    @filter = MatchFilter.new(nil, *desc) unless desc.empty?
+  end
+
+  # Saves any matching tags
+  def after(state)
+    @matching << state.description if self === state.description
+  end
+
+  # Rewrites any matching tags. Prints non-matching tags.
+  # Deletes the tag file if there were no tags (this cleans
+  # up empty or malformed tag files).
+  def unload
+    if @filter
+      matched = @tags.select { |t| @matching.any? { |s| s == t.description } }
+      MSpec.write_tags matched
+
+      (@tags - matched).each { |t| print t.description, "\n" }
+    else
+      MSpec.delete_tags
+    end
+  end
+
+  def register
+    super
+    MSpec.register :unload, self
+  end
+
+  def unregister
+    super
+    MSpec.unregister :unload, self
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/runner/actions/tally.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/runner/actions/tally.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/runner/actions/tally.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,116 @@
+class Tally
+  attr_accessor :files, :examples, :expectations, :failures, :errors, :guards
+
+  def initialize
+    @files = @examples = @expectations = @failures = @errors = @guards = 0
+  end
+
+  def files!(add=1)
+    @files += add
+  end
+
+  def examples!(add=1)
+    @examples += add
+  end
+
+  def expectations!(add=1)
+    @expectations += add
+  end
+
+  def failures!(add=1)
+    @failures += add
+  end
+
+  def errors!(add=1)
+    @errors += add
+  end
+
+  def guards!(add=1)
+    @guards += add
+  end
+
+  def file
+    pluralize files, "file"
+  end
+
+  def example
+    pluralize examples, "example"
+  end
+
+  def expectation
+    pluralize expectations, "expectation"
+  end
+
+  def failure
+    pluralize failures, "failure"
+  end
+
+  def error
+    pluralize errors, "error"
+  end
+
+  def guard
+    pluralize guards, "guard"
+  end
+
+  def format
+    results = [ file, example, expectation, failure, error ]
+    results << guard if [:report, :report_on, :verify].any? { |m| MSpec.mode? m }
+    results.join(", ")
+  end
+
+  alias_method :to_s, :format
+
+  def pluralize(count, singular)
+    "#{count} #{singular}#{'s' unless count == 1}"
+  end
+  private :pluralize
+end
+
+class TallyAction
+  attr_reader :counter
+
+  def initialize
+    @counter = Tally.new
+  end
+
+  def register
+    MSpec.register :load,        self
+    MSpec.register :exception,   self
+    MSpec.register :example,     self
+    MSpec.register :expectation, self
+  end
+
+  def unregister
+    MSpec.unregister :load,        self
+    MSpec.unregister :exception,   self
+    MSpec.unregister :example,     self
+    MSpec.unregister :expectation, self
+  end
+
+  def load
+    @counter.files!
+  end
+
+  # Callback for the MSpec :expectation event. Increments the
+  # tally of expectations (e.g. #should, #should_receive, etc.).
+  def expectation(state)
+    @counter.expectations!
+  end
+
+  # Callback for the MSpec :exception event. Increments the
+  # tally of errors and failures.
+  def exception(exception)
+    exception.failure? ? @counter.failures! : @counter.errors!
+  end
+
+  # Callback for the MSpec :example event. Increments the tally
+  # of examples.
+  def example(state, block)
+    @counter.examples!
+  end
+
+  def format
+    @counter.format
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/runner/actions/timer.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/runner/actions/timer.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/runner/actions/timer.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,22 @@
+class TimerAction
+  def register
+    MSpec.register :start, self
+    MSpec.register :finish, self
+  end
+
+  def start
+    @start = Time.now
+  end
+
+  def finish
+    @stop = Time.now
+  end
+
+  def elapsed
+    @stop - @start
+  end
+
+  def format
+    "Finished in %f seconds" % elapsed
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/runner/actions.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/runner/actions.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/runner/actions.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,8 @@
+require 'mspec/runner/actions/tally'
+require 'mspec/runner/actions/timer'
+require 'mspec/runner/actions/filter'
+require 'mspec/runner/actions/tag'
+require 'mspec/runner/actions/taglist'
+require 'mspec/runner/actions/tagpurge'
+require 'mspec/runner/actions/debug'
+require 'mspec/runner/actions/gdb'

Added: MacRuby/branches/experimental/mspec/lib/mspec/runner/context.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/runner/context.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/runner/context.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,186 @@
+# Holds the state of the +describe+ block that is being
+# evaluated. Every example (i.e. +it+ block) is evaluated
+# in a context, which may include state set up in <tt>before
+# :each</tt> or <tt>before :all</tt> blocks.
+#
+#--
+# A note on naming: this is named _ContextState_ rather
+# than _DescribeState_ because +describe+ is the keyword
+# in the DSL for refering to the context in which an example
+# is evaluated, just as +it+ refers to the example itself.
+#++
+class ContextState
+  attr_reader :state, :parent, :parents, :children, :examples, :to_s
+
+  def initialize(mod, options=nil)
+    @to_s = mod.to_s
+    if options.is_a? Hash
+      @options = options
+    else
+      @to_s += "#{".:#".include?(options[0,1]) ? "" : " "}#{options}" if options
+      @options = { }
+    end
+    @options[:shared] ||= false
+
+    @parsed   = false
+    @before   = { :all => [], :each => [] }
+    @after    = { :all => [], :each => [] }
+    @pre      = {}
+    @post     = {}
+    @examples = []
+    @parent   = nil
+    @parents  = [self]
+    @children = []
+
+    @mock_verify         = lambda { Mock.verify_count }
+    @mock_cleanup        = lambda { Mock.cleanup }
+    @expectation_missing = lambda { raise ExpectationNotFoundError }
+  end
+
+  # Returns true if this is a shared +ContextState+. Essentially, when
+  # created with: describe "Something", :shared => true { ... }
+  def shared?
+    return @options[:shared]
+  end
+
+  # Set the parent (enclosing) +ContextState+ for this state. Creates
+  # the +parents+ list.
+  def parent=(parent)
+    @description = nil
+    @parent = parent
+    parent.child self if parent and not shared?
+
+    state = parent
+    while state
+      parents.unshift state
+      state = state.parent
+    end
+  end
+
+  # Add the ContextState instance +child+ to the list of nested
+  # describe blocks.
+  def child(child)
+    @children << child
+  end
+
+  # Returns a list of all before(+what+) blocks from self and any parents.
+  def pre(what)
+    @pre[what] ||= parents.inject([]) { |l, s| l.push(*s.before(what)) }
+  end
+
+  # Returns a list of all after(+what+) blocks from self and any parents.
+  # The list is in reverse order. In other words, the blocks defined in
+  # inner describes are in the list before those defined in outer describes,
+  # and in a particular describe block those defined later are in the list
+  # before those defined earlier.
+  def post(what)
+    @post[what] ||= parents.inject([]) { |l, s| l.unshift(*s.after(what)) }
+  end
+
+  # Records before(:each) and before(:all) blocks.
+  def before(what, &block)
+    return if MSpec.guarded?
+    block ? @before[what].push(block) : @before[what]
+  end
+
+  # Records after(:each) and after(:all) blocks.
+  def after(what, &block)
+    return if MSpec.guarded?
+    block ? @after[what].unshift(block) : @after[what]
+  end
+
+  # Creates an ExampleState instance for the block and stores it
+  # in a list of examples to evaluate unless the example is filtered.
+  def it(desc, &block)
+    example = ExampleState.new(self, desc, block)
+    MSpec.actions :add, example
+    return if MSpec.guarded?
+    @examples << example
+  end
+
+  # Evaluates the block and resets the toplevel +ContextState+ to #parent.
+  def describe(&block)
+    @parsed = protect @to_s, block, false
+    MSpec.register_current parent
+    MSpec.register_shared self if shared?
+  end
+
+  # Returns a description string generated from self and all parents
+  def description
+    @description ||= parents.map { |p| p.to_s }.join(" ")
+  end
+
+  # Injects the before/after blocks and examples from the shared
+  # describe block into this +ContextState+ instance.
+  def it_should_behave_like(desc)
+    return if MSpec.guarded?
+
+    unless state = MSpec.retrieve_shared(desc)
+      raise Exception, "Unable to find shared 'describe' for #{desc}"
+    end
+
+    state.examples.each { |ex| ex.context = self; @examples << ex }
+    state.before(:all).each { |b| before :all, &b }
+    state.before(:each).each { |b| before :each, &b }
+    state.after(:each).each { |b| after :each, &b }
+    state.after(:all).each { |b| after :all, &b }
+  end
+
+  # Evaluates each block in +blocks+ using the +MSpec.protect+ method
+  # so that exceptions are handled and tallied. Returns true and does
+  # NOT evaluate any blocks if +check+ is true and
+  # <tt>MSpec.mode?(:pretend)</tt> is true.
+  def protect(what, blocks, check=true)
+    return true if check and MSpec.mode? :pretend
+    Array(blocks).all? { |block| MSpec.protect what, &block }
+  end
+
+  # Removes filtered examples. Returns true if there are examples
+  # left to evaluate.
+  def filter_examples
+    @examples.reject! { |ex| ex.filtered? }
+    not @examples.empty?
+  end
+
+  # Evaluates the examples in a +ContextState+. Invokes the MSpec events
+  # for :enter, :before, :after, :leave.
+  def process
+    MSpec.register_current self
+
+    if @parsed and filter_examples
+      MSpec.shuffle @examples if MSpec.randomize?
+      MSpec.actions :enter, description
+
+      if protect "before :all", pre(:all)
+        @examples.each do |state|
+          @state  = state
+          example = state.example
+          MSpec.actions :before, state
+
+          if protect "before :each", pre(:each)
+            MSpec.clear_expectations
+            if example
+              passed = protect nil, example
+              MSpec.actions :example, state, example
+              protect nil, @expectation_missing unless MSpec.expectation? or not passed
+            end
+            protect "after :each", post(:each)
+            protect "Mock.verify_count", @mock_verify
+          end
+
+          protect "Mock.cleanup", @mock_cleanup
+          MSpec.actions :after, state
+          @state = nil
+        end
+        protect "after :all", post(:all)
+      else
+        protect "Mock.cleanup", @mock_cleanup
+      end
+
+      MSpec.actions :leave
+    end
+
+    MSpec.register_current nil
+    children.each { |child| child.process }
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/runner/example.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/runner/example.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/runner/example.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,34 @@
+require 'mspec/runner/mspec'
+
+# Holds some of the state of the example (i.e. +it+ block) that is
+# being evaluated. See also +ContextState+.
+class ExampleState
+  attr_reader   :context, :it, :example
+
+  def initialize(context, it, example=nil)
+    @context  = context
+    @it       = it
+    @example  = example
+  end
+
+  def context=(context)
+    @description = nil
+    @context = context
+  end
+
+  def describe
+    @context.description
+  end
+
+  def description
+    @description ||= "#{describe} #{@it}"
+  end
+
+  def filtered?
+    incl = MSpec.retrieve(:include) || []
+    excl = MSpec.retrieve(:exclude) || []
+    included   = incl.empty? || incl.any? { |f| f === description }
+    included &&= excl.empty? || !excl.any? { |f| f === description }
+    not included
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/runner/exception.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/runner/exception.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/runner/exception.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,43 @@
+class ExceptionState
+  attr_reader :description, :describe, :it, :exception
+
+  PATH = /#{File.expand_path(File.dirname(__FILE__) + '/../../..')}/
+
+  def initialize(state, location, exception)
+    @exception = exception
+
+    @description = location ? "An exception occurred during: #{location}" : ""
+    if state
+      @description << "\n" unless @description.empty?
+      @description << state.description
+      @describe = state.describe
+      @it = state.it
+    else
+      @describe = @it = ""
+    end
+  end
+
+  def failure?
+    [ExpectationNotMetError, ExpectationNotFoundError].any? { |e| @exception.is_a? e }
+  end
+
+  def message
+    if @exception.message.empty?
+      "<No message>"
+    elsif @exception.class == ExpectationNotMetError ||
+          @exception.class == ExpectationNotFoundError
+      @exception.message
+    else
+      "#{@exception.class}: #{@exception.message}"
+    end
+  end
+
+  def backtrace
+    begin
+      bt = @exception.awesome_backtrace.show.split "\n"
+    rescue Exception
+      bt = @exception.backtrace || []
+    end
+    bt.reject { |line| PATH =~ line }.join("\n")
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/runner/filters/match.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/runner/filters/match.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/runner/filters/match.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,22 @@
+class MatchFilter
+  def initialize(what, *strings)
+    @what = what
+    @descriptions = to_regexp(*strings)
+  end
+
+  def to_regexp(*strings)
+    strings.map { |str| Regexp.new Regexp.escape(str) }
+  end
+
+  def ===(string)
+    @descriptions.any? { |d| d === string }
+  end
+
+  def register
+    MSpec.register @what, self
+  end
+
+  def unregister
+    MSpec.unregister @what, self
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/runner/filters/profile.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/runner/filters/profile.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/runner/filters/profile.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,54 @@
+class ProfileFilter
+  def initialize(what, *files)
+    @what = what
+    @methods = load(*files)
+    @pattern = /([^ .#]+[.#])([^ ]+)/
+  end
+
+  def find(name)
+    return name if File.exist?(File.expand_path(name))
+
+    ["spec/profiles", "spec", "profiles", "."].each do |dir|
+      file = File.join dir, name
+      return file if File.exist? file
+    end
+  end
+
+  def parse(file)
+    pattern = /(\S+):\s*/
+    key = ""
+    file.inject(Hash.new { |h,k| h[k] = [] }) do |hash, line|
+      line.chomp!
+      if line[0,2] == "- "
+        hash[key] << line[2..-1].gsub(/[ '"]/, "")
+      elsif m = pattern.match(line)
+        key = m[1]
+      end
+      hash
+    end
+  end
+
+  def load(*files)
+    files.inject({}) do |hash, file|
+      next hash unless name = find(file)
+
+      File.open name, "r" do |f|
+        hash.merge parse(f)
+      end
+    end
+  end
+
+  def ===(string)
+    return false unless m = @pattern.match(string)
+    return false unless l = @methods[m[1]]
+    l.include? m[2]
+  end
+
+  def register
+    MSpec.register @what, self
+  end
+
+  def unregister
+    MSpec.unregister @what, self
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/runner/filters/regexp.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/runner/filters/regexp.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/runner/filters/regexp.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,7 @@
+require 'mspec/runner/filters/match'
+
+class RegexpFilter < MatchFilter
+  def to_regexp(*strings)
+    strings.map { |str| Regexp.new str }
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/runner/filters/tag.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/runner/filters/tag.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/runner/filters/tag.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,29 @@
+require 'mspec/runner/filters/match'
+
+class TagFilter
+  def initialize(what, *tags)
+    @what = what
+    @tags = tags
+  end
+
+  def load
+    desc = MSpec.read_tags(@tags).map { |t| t.description }
+
+    @filter = MatchFilter.new(@what, *desc)
+    @filter.register
+  end
+
+  def unload
+    @filter.unregister if @filter
+  end
+
+  def register
+    MSpec.register :load, self
+    MSpec.register :unload, self
+  end
+
+  def unregister
+    MSpec.unregister :load, self
+    MSpec.unregister :unload, self
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/runner/filters.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/runner/filters.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/runner/filters.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,4 @@
+require 'mspec/runner/filters/match'
+require 'mspec/runner/filters/regexp'
+require 'mspec/runner/filters/tag'
+require 'mspec/runner/filters/profile'

Added: MacRuby/branches/experimental/mspec/lib/mspec/runner/formatters/describe.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/runner/formatters/describe.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/runner/formatters/describe.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,24 @@
+require 'mspec/runner/formatters/dotted'
+require 'mspec/runner/actions/tally'
+
+class DescribeFormatter < DottedFormatter
+  # Callback for the MSpec :finish event. Prints a summary of
+  # the number of errors and failures for each +describe+ block.
+  def finish
+    describes = Hash.new { |h,k| h[k] = Tally.new }
+
+    @exceptions.each do |exc|
+      desc = describes[exc.describe]
+      exc.failure? ? desc.failures! : desc.errors!
+    end
+
+    print "\n"
+    describes.each do |d, t|
+      text = d.size > 40 ? "#{d[0,37]}..." : d.ljust(40)
+      print "\n#{text} #{t.failure}, #{t.error}"
+    end
+    print "\n" unless describes.empty?
+
+    print "\n#{@timer.format}\n\n#{@tally.format}\n"
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/runner/formatters/dotted.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/runner/formatters/dotted.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/runner/formatters/dotted.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,98 @@
+require 'mspec/expectations/expectations'
+require 'mspec/runner/actions/timer'
+require 'mspec/runner/actions/tally'
+
+class DottedFormatter
+  attr_reader :exceptions, :timer, :tally
+
+  def initialize(out=nil)
+    @exception = @failure = false
+    @exceptions = []
+    @count = 0
+    if out.nil?
+      @out = $stdout
+    else
+      @out = File.open out, "w"
+    end
+  end
+
+  # Creates the +TimerAction+ and +TallyAction+ instances and
+  # registers them. Registers +self+ for the +:exception+,
+  # +:before+, +:after+, and +:finish+ actions.
+  def register
+    (@timer = TimerAction.new).register
+    (@tally = TallyAction.new).register
+    @counter = @tally.counter
+
+    MSpec.register :exception, self
+    MSpec.register :before,    self
+    MSpec.register :after,     self
+    MSpec.register :finish,    self
+  end
+
+  # Returns true if any exception is raised while running
+  # an example. This flag is reset before each example
+  # is evaluated.
+  def exception?
+    @exception
+  end
+
+  # Returns true if all exceptions during the evaluation
+  # of an example are failures rather than errors. See
+  # <tt>ExceptionState#failure</tt>. This flag is reset
+  # before each example is evaluated.
+  def failure?
+    @failure
+  end
+
+  # Callback for the MSpec :before event. Resets the
+  # +#exception?+ and +#failure+ flags.
+  def before(state = nil)
+    @failure = @exception = false
+  end
+
+  # Callback for the MSpec :exception event. Stores the
+  # +ExceptionState+ object to generate the list of backtraces
+  # after all the specs are run. Also updates the internal
+  # +#exception?+ and +#failure?+ flags.
+  def exception(exception)
+    @count += 1
+    @failure = @exception ? @failure && exception.failure? : exception.failure?
+    @exception = true
+    @exceptions << exception
+  end
+
+  # Callback for the MSpec :after event. Prints an indicator
+  # for the result of evaluating this example as follows:
+  #   . = No failure or error
+  #   F = An ExpectationNotMetError was raised
+  #   E = Any exception other than ExpectationNotMetError
+  def after(state = nil)
+    unless exception?
+      print "."
+    else
+      print failure? ? "F" : "E"
+    end
+  end
+
+  # Callback for the MSpec :finish event. Prints a description
+  # and backtrace for every exception that occurred while
+  # evaluating the examples.
+  def finish
+    print "\n"
+    count = 0
+    @exceptions.each do |exc|
+      outcome = exc.failure? ? "FAILED" : "ERROR"
+      print "\n#{count += 1})\n#{exc.description} #{outcome}\n"
+      print exc.message, "\n"
+      print exc.backtrace, "\n"
+    end
+    print "\n#{@timer.format}\n\n#{@tally.format}\n"
+  end
+
+  # A convenience method to allow printing to different outputs.
+  def print(*args)
+    @out.print(*args)
+    @out.flush rescue nil #IronRuby throws a .NET exception on IO.flush
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/runner/formatters/file.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/runner/formatters/file.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/runner/formatters/file.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,19 @@
+require 'mspec/runner/formatters/dotted'
+
+class FileFormatter < DottedFormatter
+  # Unregisters DottedFormatter#before, #after methods and
+  # registers #load, #unload, which perform the same duties
+  # as #before, #after in DottedFormatter.
+  def register
+    super
+
+    MSpec.unregister :before,    self
+    MSpec.unregister :after,     self
+
+    MSpec.register   :load,      self
+    MSpec.register   :unload,    self
+  end
+
+  alias_method :load, :before
+  alias_method :unload, :after
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/runner/formatters/html.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/runner/formatters/html.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/runner/formatters/html.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,81 @@
+require 'mspec/expectations/expectations'
+require 'mspec/runner/formatters/dotted'
+
+class HtmlFormatter < DottedFormatter
+  def register
+    super
+    MSpec.register :start, self
+    MSpec.register :enter, self
+    MSpec.register :leave, self
+  end
+
+  def start
+    print <<-EOH
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
+    "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+<title>Spec Output For #{RUBY_NAME} (#{RUBY_VERSION})</title>
+<style type="text/css">
+ul {
+  list-style: none;
+}
+.fail {
+  color: red;
+}
+.pass {
+  color: green;
+}
+#details :target {
+  background-color: #ffffe0;
+}
+</style>
+</head>
+<body>
+EOH
+  end
+
+  def enter(describe)
+    print "<div><p>#{describe}</p>\n<ul>\n"
+  end
+
+  def leave
+    print "</ul>\n</div>\n"
+  end
+
+  def exception(exception)
+    super
+    outcome = exception.failure? ? "FAILED" : "ERROR"
+    print %[<li class="fail">- #{exception.it} (<a href="#details-#{@count}">]
+    print %[#{outcome} - #{@count}</a>)</li>\n]
+  end
+
+  def after(state)
+    print %[<li class="pass">- #{state.it}</li>\n] unless exception?
+  end
+
+  def finish
+    success = @exceptions.empty?
+    unless success
+      print "<hr>\n"
+      print %[<ol id="details">]
+      count = 0
+      @exceptions.each do |exc|
+        outcome = exc.failure? ? "FAILED" : "ERROR"
+        print %[\n<li id="details-#{count += 1}"><p>#{escape(exc.description)} #{outcome}</p>\n<p>]
+        print escape(exc.message)
+        print "</p>\n<pre>\n"
+        print escape(exc.backtrace)
+        print "</pre>\n</li>\n"
+      end
+      print "</ol>\n"
+    end
+    print %[<p>#{@timer.format}</p>\n]
+    print %[<p class="#{success ? "pass" : "fail"}">#{@tally.format}</p>\n]
+    print "</body>\n</html>\n"
+  end
+
+  def escape(string)
+    string.gsub("&", "&nbsp;").gsub("<", "&lt;").gsub(">", "&gt;")
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/runner/formatters/method.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/runner/formatters/method.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/runner/formatters/method.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,93 @@
+require 'mspec/runner/formatters/dotted'
+
+class MethodFormatter < DottedFormatter
+  attr_accessor :methods
+
+  def initialize(out=nil)
+    super
+    @methods = Hash.new do |h, k|
+      hash = {}
+      hash[:examples]     = 0
+      hash[:expectations] = 0
+      hash[:failures]     = 0
+      hash[:errors]       = 0
+      hash[:exceptions]   = []
+      h[k] = hash
+    end
+  end
+
+  # Returns the type of method as a "class", "instance",
+  # or "unknown".
+  def method_type(sep)
+    case sep
+    when '.', '::'
+      "class"
+    when '#'
+      "instance"
+    else
+      "unknown"
+    end
+  end
+
+  # Callback for the MSpec :before event. Parses the
+  # describe string into class and method if possible.
+  # Resets the tallies so the counts are only for this
+  # example.
+  def before(state)
+    super
+
+    # The pattern for a method name is not correctly
+    # restrictive but it is simplistic and useful
+    # for our purpose.
+    /^([A-Za-z_]+\w*)(\.|#|::)([^ ]+)/ =~ state.describe
+    @key = $1 && $2 && $3 ? "#{$1}#{$2}#{$3}" : state.describe
+
+    unless methods.key? @key
+      h = methods[@key]
+      h[:class]       = "#{$1}"
+      h[:method]      = "#{$3}"
+      h[:type]        = method_type $2
+      h[:description] = state.description
+    end
+
+    tally.counter.examples     = 0
+    tally.counter.expectations = 0
+    tally.counter.failures     = 0
+    tally.counter.errors       = 0
+
+    @exceptions = []
+  end
+
+  # Callback for the MSpec :after event. Sets or adds to
+  # tallies for the example block.
+  def after(state)
+    h = methods[@key]
+    h[:examples]     += tally.counter.examples
+    h[:expectations] += tally.counter.expectations
+    h[:failures]     += tally.counter.failures
+    h[:errors]       += tally.counter.errors
+    @exceptions.each do |exc|
+      h[:exceptions] << "#{exc.message}\n#{exc.backtrace}\n"
+    end
+  end
+
+  # Callback for the MSpec :finish event. Prints out the
+  # summary information in YAML format for all the methods.
+  def finish
+    print "---\n"
+
+    methods.each do |key, hash|
+      print key.inspect, ":\n"
+      print "  class: ",        hash[:class].inspect,        "\n"
+      print "  method: ",       hash[:method].inspect,       "\n"
+      print "  type: ",         hash[:type],                 "\n"
+      print "  description: ",  hash[:description].inspect,  "\n"
+      print "  examples: ",     hash[:examples],             "\n"
+      print "  expectations: ", hash[:expectations],         "\n"
+      print "  failures: ",     hash[:failures],             "\n"
+      print "  errors: ",       hash[:errors],               "\n"
+      print "  exceptions:\n"
+      hash[:exceptions].each { |exc| print "  - ", exc.inspect, "\n" }
+    end
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/runner/formatters/specdoc.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/runner/formatters/specdoc.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/runner/formatters/specdoc.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,41 @@
+require 'mspec/expectations/expectations'
+require 'mspec/runner/formatters/dotted'
+
+class SpecdocFormatter < DottedFormatter
+  def register
+    super
+    MSpec.register :enter, self
+  end
+
+  # Callback for the MSpec :enter event. Prints the
+  # +describe+ block string.
+  def enter(describe)
+    print "\n#{describe}\n"
+  end
+
+  # Callback for the MSpec :before event. Prints the
+  # +it+ block string.
+  def before(state)
+    super
+    print "- #{state.it}"
+  end
+
+  # Callback for the MSpec :exception event. Prints
+  # either 'ERROR - X' or 'FAILED - X' where _X_ is
+  # the sequential number of the exception raised. If
+  # there has already been an exception raised while
+  # evaluating this example, it prints another +it+
+  # block description string so that each discription
+  # string has an associated 'ERROR' or 'FAILED'
+  def exception(exception)
+    print "\n- #{exception.it}" if exception?
+    super
+    print " (#{exception.failure? ? 'FAILED' : 'ERROR'} - #{@count})"
+  end
+
+  # Callback for the MSpec :after event. Prints a
+  # newline to finish the description string output.
+  def after(state)
+    print "\n"
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/runner/formatters/spinner.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/runner/formatters/spinner.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/runner/formatters/spinner.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,99 @@
+require 'mspec/expectations/expectations'
+require 'mspec/runner/formatters/dotted'
+
+class SpinnerFormatter < DottedFormatter
+  attr_reader :length
+
+  Spins = %w!| / - \\!
+  HOUR = 3600
+  MIN = 60
+
+  def initialize(out=nil)
+    @exception = @failure = false
+    @exceptions = []
+    @count = 0
+    @out = $stdout
+
+    @which = 0
+    @loaded = 0
+    self.length = 40
+    @percent = 0
+    @start = Time.now
+
+    term = ENV['TERM']
+    @color = (term != "dumb")
+    @fail_color  = "32"
+    @error_color = "32"
+  end
+
+  def register
+    super
+
+    MSpec.register :start, self
+    MSpec.register :load, self
+  end
+
+  def length=(length)
+    @length = length
+    @ratio = 100.0 / length
+    @position = length / 2 - 2
+  end
+
+  def etr
+    return "00:00:00" if @percent == 0
+    elapsed = Time.now - @start
+    remain = (100 * elapsed / @percent) - elapsed
+
+    hour = remain >= HOUR ? (remain / HOUR).to_i : 0
+    remain -= hour * HOUR
+    min = remain >= MIN ? (remain / MIN).to_i : 0
+    sec = remain - min * MIN
+
+    "%02d:%02d:%02d" % [hour, min, sec]
+  end
+
+  def percentage
+    @percent = @loaded * 100 / @total
+    bar = ("=" * (@percent / @ratio)).ljust @length
+    label = "%d%%" % @percent
+    bar[@position, label.size] = label
+    bar
+  end
+
+  def spin
+    @which = (@which + 1) % Spins.size
+    if @color
+      print "\r[%s | %s | %s] \033[0;#{@fail_color}m%6dF \033[0;#{@error_color}m%6dE\033[0m" %
+          [Spins[@which], percentage, etr, @counter.failures, @counter.errors]
+    else
+      print "\r[%s | %s | %s] %6dF %6dE" %
+          [Spins[@which], percentage, etr, @counter.failures, @counter.errors]
+    end
+  end
+
+  # Callback for the MSpec :start event. Stores the total
+  # number of files that will be processed.
+  def start
+    @total = MSpec.retrieve(:files).size
+  end
+
+  # Callback for the MSpec :load event. Increments the number
+  # of files that have been loaded.
+  def load
+    @loaded += 1
+  end
+
+  # Callback for the MSpec :exception event. Changes the color
+  # used to display the tally of errors and failures
+  def exception(exception)
+    super
+    @fail_color =  "31" if exception.failure?
+    @error_color = "33" unless exception.failure?
+  end
+
+  # Callback for the MSpec :after event. Updates the spinner
+  # and progress bar.
+  def after(state)
+    spin
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/runner/formatters/summary.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/runner/formatters/summary.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/runner/formatters/summary.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,11 @@
+require 'mspec/expectations/expectations'
+require 'mspec/runner/formatters/dotted'
+
+class SummaryFormatter < DottedFormatter
+  # Callback for the MSpec :after event. Overrides the
+  # callback provided by +DottedFormatter+ and does not
+  # print any output for each example evaluated.
+  def after(state)
+    # do nothing
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/runner/formatters/unit.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/runner/formatters/unit.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/runner/formatters/unit.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,21 @@
+require 'mspec/expectations/expectations'
+require 'mspec/runner/formatters/dotted'
+
+class UnitdiffFormatter < DottedFormatter
+  def finish
+    print "\n\n#{@timer.format}\n"
+    count = 0
+    @exceptions.each do |exc|
+      outcome = exc.failure? ? "FAILED" : "ERROR"
+      print "\n#{count += 1})\n#{exc.description} #{outcome}\n"
+      print exc.message, ": \n"
+      print exc.backtrace, "\n"
+    end
+    print "\n#{@tally.format}\n"
+  end
+
+  def backtrace(exc)
+    exc.backtrace && exc.backtrace.join("\n")
+  end
+  private :backtrace
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/runner/formatters/yaml.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/runner/formatters/yaml.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/runner/formatters/yaml.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,44 @@
+require 'mspec/expectations/expectations'
+require 'mspec/runner/formatters/dotted'
+
+class YamlFormatter < DottedFormatter
+  def initialize(out=nil)
+    @exception = @failure = false
+    @exceptions = []
+    @count = 0
+    @out = $stdout
+
+    if out.nil?
+      @finish = $stdout
+    else
+      @finish = File.open out, "w"
+    end
+  end
+
+  def switch
+    @out = @finish
+  end
+
+  def after(state)
+  end
+
+  def finish
+    switch
+
+    print "---\n"
+    print "exceptions:\n"
+    @exceptions.each do |exc|
+      outcome = exc.failure? ? "FAILED" : "ERROR"
+      str =  "#{exc.description} #{outcome}\n"
+      str << exc.message << "\n" << exc.backtrace
+      print "- ", str.inspect, "\n"
+    end
+
+    print "time: ",         @timer.elapsed,              "\n"
+    print "files: ",        @tally.counter.files,        "\n"
+    print "examples: ",     @tally.counter.examples,     "\n"
+    print "expectations: ", @tally.counter.expectations, "\n"
+    print "failures: ",     @tally.counter.failures,     "\n"
+    print "errors: ",       @tally.counter.errors,       "\n"
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/runner/formatters.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/runner/formatters.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/runner/formatters.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,10 @@
+require 'mspec/runner/formatters/describe'
+require 'mspec/runner/formatters/dotted'
+require 'mspec/runner/formatters/file'
+require 'mspec/runner/formatters/specdoc'
+require 'mspec/runner/formatters/html'
+require 'mspec/runner/formatters/summary'
+require 'mspec/runner/formatters/unit'
+require 'mspec/runner/formatters/spinner'
+require 'mspec/runner/formatters/method'
+require 'mspec/runner/formatters/yaml'

Added: MacRuby/branches/experimental/mspec/lib/mspec/runner/mspec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/runner/mspec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/runner/mspec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,335 @@
+require 'mspec/runner/context'
+require 'mspec/runner/exception'
+require 'mspec/runner/tag'
+require 'fileutils'
+
+module MSpec
+
+  @exit    = nil
+  @start   = nil
+  @enter   = nil
+  @before  = nil
+  @after   = nil
+  @leave   = nil
+  @finish  = nil
+  @exclude = nil
+  @include = nil
+  @leave   = nil
+  @load    = nil
+  @unload  = nil
+  @current = nil
+  @modes   = []
+  @shared  = {}
+  @guarded = []
+  @exception    = nil
+  @randomize    = nil
+  @expectation  = nil
+  @expectations = false
+
+  def self.describe(mod, options=nil, &block)
+    state = ContextState.new mod, options
+    state.parent = current
+
+    MSpec.register_current state
+    state.describe(&block)
+
+    state.process unless state.shared? or current
+  end
+
+  def self.process
+    actions :start
+    files
+    actions :finish
+  end
+
+  def self.files
+    return unless files = retrieve(:files)
+
+    shuffle files if randomize?
+    files.each do |file|
+      @env = Object.new
+      @env.extend MSpec
+
+      store :file, file
+      actions :load
+      protect("loading #{file}") { Kernel.load file }
+      actions :unload
+    end
+  end
+
+  def self.actions(action, *args)
+    actions = retrieve(action)
+    actions.each { |obj| obj.send action, *args } if actions
+  end
+
+  def self.protect(location, &block)
+    begin
+      @env.instance_eval(&block)
+      return true
+    rescue SystemExit
+      raise
+    rescue Exception => exc
+      register_exit 1
+      actions :exception, ExceptionState.new(current && current.state, location, exc)
+      return false
+    end
+  end
+
+  # Guards can be nested, so a stack is necessary to know when we have
+  # exited the toplevel guard.
+  def self.guard
+    @guarded << true
+  end
+
+  def self.unguard
+    @guarded.pop
+  end
+
+  def self.guarded?
+    not @guarded.empty?
+  end
+
+  # Sets the toplevel ContextState to +state+.
+  def self.register_current(state)
+    store :current, state
+  end
+
+  # Sets the toplevel ContextState to +nil+.
+  def self.clear_current
+    store :current, nil
+  end
+
+  # Returns the toplevel ContextState.
+  def self.current
+    retrieve :current
+  end
+
+  # Stores the shared ContextState keyed by description.
+  def self.register_shared(state)
+    @shared[state.to_s] = state
+  end
+
+  # Returns the shared ContextState matching description.
+  def self.retrieve_shared(desc)
+    @shared[desc.to_s]
+  end
+
+  # Stores the exit code used by the runner scripts.
+  def self.register_exit(code)
+    store :exit, code
+  end
+
+  # Retrieves the stored exit code.
+  def self.exit_code
+    retrieve(:exit).to_i
+  end
+
+  # Stores the list of files to be evaluated.
+  def self.register_files(files)
+    store :files, files
+  end
+
+  # Stores one or more substitution patterns for transforming
+  # a spec filename into a tags filename, where each pattern
+  # has the form:
+  #
+  #   [Regexp, String]
+  #
+  # See also +tags_file+.
+  def self.register_tags_patterns(patterns)
+    store :tags_patterns, patterns
+  end
+
+  # Registers an operating mode. Modes recognized by MSpec:
+  #
+  #   :pretend - actions execute but specs are not run
+  #   :verify - specs are run despite guards and the result is
+  #             verified to match the expectation of the guard
+  #   :report - specs that are guarded are reported
+  #   :unguarded - all guards are forced off
+  def self.register_mode(mode)
+    modes = retrieve :modes
+    modes << mode unless modes.include? mode
+  end
+
+  # Clears all registered modes.
+  def self.clear_modes
+    store :modes, []
+  end
+
+  # Returns +true+ if +mode+ is registered.
+  def self.mode?(mode)
+    retrieve(:modes).include? mode
+  end
+
+  def self.retrieve(symbol)
+    instance_variable_get :"@#{symbol}"
+  end
+
+  def self.store(symbol, value)
+    instance_variable_set :"@#{symbol}", value
+  end
+
+  # This method is used for registering actions that are
+  # run at particular points in the spec cycle:
+  #   :start        before any specs are run
+  #   :load         before a spec file is loaded
+  #   :enter        before a describe block is run
+  #   :before       before a single spec is run
+  #   :add          while a describe block is adding examples to run later
+  #   :expectation  before a 'should', 'should_receive', etc.
+  #   :example      after an example block is run, passed the block
+  #   :exception    after an exception is rescued
+  #   :after        after a single spec is run
+  #   :leave        after a describe block is run
+  #   :unload       after a spec file is run
+  #   :finish       after all specs are run
+  #
+  # Objects registered as actions above should respond to
+  # a method of the same name. For example, if an object
+  # is registered as a :start action, it should respond to
+  # a #start method call.
+  #
+  # Additionally, there are two "action" lists for
+  # filtering specs:
+  #   :include  return true if the spec should be run
+  #   :exclude  return true if the spec should NOT be run
+  #
+  def self.register(symbol, action)
+    unless value = retrieve(symbol)
+      value = store symbol, []
+    end
+    value << action unless value.include? action
+  end
+
+  def self.unregister(symbol, action)
+    if value = retrieve(symbol)
+      value.delete action
+    end
+  end
+
+  def self.randomize(flag=true)
+    @randomize = flag
+  end
+
+  def self.randomize?
+    @randomize == true
+  end
+
+  def self.shuffle(ary)
+    return if ary.empty?
+
+    size = ary.size
+    size.times do |i|
+      r = rand(size - i - 1)
+      ary[i], ary[r] = ary[r], ary[i]
+    end
+  end
+
+  # Records that an expectation has been encountered in an example.
+  def self.expectation
+    store :expectations, true
+  end
+
+  # Returns true if an expectation has been encountered
+  def self.expectation?
+    retrieve :expectations
+  end
+
+  # Resets the flag that an expectation has been encountered in an example.
+  def self.clear_expectations
+    store :expectations, false
+  end
+
+  # Transforms a spec filename into a tags filename by applying each
+  # substitution pattern in :tags_pattern. The default patterns are:
+  #
+  #   [%r(/spec/), '/spec/tags/'], [/_spec.rb$/, '_tags.txt']
+  #
+  # which will perform the following transformation:
+  #
+  #   path/to/spec/class/method_spec.rb => path/to/spec/tags/class/method_tags.txt
+  #
+  # See also +register_tags_patterns+.
+  def self.tags_file
+    patterns = retrieve(:tags_patterns) ||
+               [[%r(spec/), 'spec/tags/'], [/_spec.rb$/, '_tags.txt']]
+    patterns.inject(retrieve(:file).dup) do |file, pattern|
+      file.gsub(*pattern)
+    end
+  end
+
+  # Returns a list of tags matching any tag string in +keys+ based
+  # on the return value of <tt>keys.include?("tag_name")</tt>
+  def self.read_tags(keys)
+    tags = []
+    file = tags_file
+    if File.exist? file
+      File.open(file, "r") do |f|
+        f.each_line do |line|
+          line.chomp!
+          next if line.empty?
+          tag = SpecTag.new line.chomp
+          tags << tag if keys.include? tag.tag
+        end
+      end
+    end
+    tags
+  end
+
+  # Writes each tag in +tags+ to the tag file. Overwrites the
+  # tag file if it exists.
+  def self.write_tags(tags)
+    file = tags_file
+    path = File.dirname file
+    FileUtils.mkdir_p path unless File.exist? path
+    File.open(file, "w") do |f|
+      tags.each { |t| f.puts t }
+    end
+  end
+
+  # Writes +tag+ to the tag file if it does not already exist.
+  # Returns +true+ if the tag is written, +false+ otherwise.
+  def self.write_tag(tag)
+    string = tag.to_s
+    file = tags_file
+    path = File.dirname file
+    FileUtils.mkdir_p path unless File.exist? path
+    if File.exist? file
+      File.open(file, "r") do |f|
+        f.each_line { |line| return false if line.chomp == string }
+      end
+    end
+    File.open(file, "a") { |f| f.puts string }
+    return true
+  end
+
+  # Deletes +tag+ from the tag file if it exists. Returns +true+
+  # if the tag is deleted, +false+ otherwise. Deletes the tag
+  # file if it is empty.
+  def self.delete_tag(tag)
+    deleted = false
+    pattern = /#{tag.tag}.*#{Regexp.escape(tag.escape(tag.description))}/
+    file = tags_file
+    if File.exist? file
+      lines = IO.readlines(file)
+      File.open(file, "w") do |f|
+        lines.each do |line|
+          unless pattern =~ line.chomp
+            f.puts line unless line.empty?
+          else
+            deleted = true
+          end
+        end
+      end
+      File.delete file unless File.size? file
+    end
+    return deleted
+  end
+
+  # Removes the tag file associated with a spec file.
+  def self.delete_tags
+    file = tags_file
+    File.delete file if File.exists? file
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/runner/object.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/runner/object.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/runner/object.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,24 @@
+class Object
+  def before(at=:each, &block)
+    MSpec.current.before at, &block
+  end
+
+  def after(at=:each, &block)
+    MSpec.current.after at, &block
+  end
+
+  def describe(mod, msg=nil, options=nil, &block)
+    MSpec.describe mod, msg, &block
+  end
+
+  def it(msg, &block)
+    MSpec.current.it msg, &block
+  end
+
+  def it_should_behave_like(desc)
+    MSpec.current.it_should_behave_like desc
+  end
+
+  alias_method :context, :describe
+  alias_method :specify, :it
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/runner/shared.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/runner/shared.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/runner/shared.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,12 @@
+require 'mspec/runner/mspec'
+
+class Object
+  def it_behaves_like(desc, meth, obj=nil)
+    send :before, :all do
+      @method = meth
+      @object = obj if obj
+    end
+
+    send :it_should_behave_like, desc.to_s
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/runner/tag.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/runner/tag.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/runner/tag.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,32 @@
+class SpecTag
+  attr_accessor :tag, :comment, :description
+
+  def initialize(string=nil)
+    parse(string) if string
+  end
+
+  def parse(string)
+    m = /^([^()#:]+)(\(([^)]+)?\))?:(.*)$/.match string
+    @tag, @comment, description = m.values_at(1, 3, 4) if m
+    @description = unescape description
+  end
+
+  def unescape(str)
+    return unless str
+    str = str[1..-2] if str[0] == ?" and str[-1] == ?"
+    str.gsub(/\\n/, "\n")
+  end
+
+  def escape(str)
+    str = %["#{str.gsub(/\n/, '\n')}"] if /\n/ =~ str
+    str
+  end
+
+  def to_s
+    "#{@tag}#{ "(#{@comment})" if @comment }:#{escape @description}"
+  end
+
+  def ==(o)
+    @tag == o.tag and @comment == o.comment and @description == o.description
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/runner.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/runner.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/runner.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,15 @@
+require 'mspec/mocks'
+require 'mspec/runner/mspec'
+require 'mspec/runner/context'
+require 'mspec/runner/example'
+require 'mspec/runner/object'
+require 'mspec/runner/formatters'
+require 'mspec/runner/actions'
+require 'mspec/runner/filters'
+require 'mspec/runner/shared'
+require 'mspec/runner/tag'
+
+def $stderr.write(str)
+  # The 'useless use of' warnings are a crime against OO.
+  str =~ /useless use of/ ? nil : super
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/utils/name_map.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/utils/name_map.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/utils/name_map.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,129 @@
+require 'mspec/helpers/const_lookup'
+
+class NameMap
+  MAP = {
+    '`'   => 'backtick',
+    '+'   => 'plus',
+    '-'   => 'minus',
+    '+@'  => 'uplus',
+    '-@'  => 'uminus',
+    '*'   => 'multiply',
+    '/'   => 'divide',
+    '%'   => 'modulo',
+    '<<'  => {'Bignum' => 'left_shift',
+              'Fixnum' => 'left_shift',
+              'IO'     => 'output',
+              :default => 'append' },
+    '>>'  => 'right_shift',
+    '<'   => 'lt',
+    '<='  => 'lte',
+    '>'   => 'gt',
+    '>='  => 'gte',
+    '='   => 'assignment',
+    '=='  => 'equal_value',
+    '===' => 'case_compare',
+    '<=>' => 'comparison',
+    '[]'  => 'element_reference',
+    '[]=' => 'element_set',
+    '**'  => 'exponent',
+    '!'   => 'not',
+    '~'   => {'Bignum' => 'complement',
+              'Fixnum' => 'complement',
+              'Regexp' => 'match',
+              'String' => 'match' },
+    '!='  => 'not_equal',
+    '!~'  => 'not_match',
+    '=~'  => 'match',
+    '&'   => {'Bignum'     => 'bit_and',
+              'Fixnum'     => 'bit_and',
+              'Array'      => 'intersection',
+              'TrueClass'  => 'and',
+              'FalseClass' => 'and',
+              'NilClass'   => 'and',
+              'Set'        => 'intersection' },
+    '|'   => {'Bignum'     => 'bit_or',
+              'Fixnum'     => 'bit_or',
+              'Array'      => 'union',
+              'TrueClass'  => 'or',
+              'FalseClass' => 'or',
+              'NilClass'   => 'or',
+              'Set'        => 'union' },
+    '^'   => {'Bignum'     => 'bit_xor',
+              'Fixnum'     => 'bit_xor',
+              'TrueClass'  => 'xor',
+              'FalseClass' => 'xor',
+              'NilClass'   => 'xor',
+              'Set'        => 'exclusion'},
+  }
+
+  EXCLUDED = %w[
+    MSpecScript
+    MkSpec
+    DTracer
+    NameMap
+    OptionParser
+    YAML
+  ]
+
+  def initialize(filter=false)
+    @seen = {}
+    @filter = filter
+  end
+
+  def exception?(name)
+    return false unless c = class_or_module(name)
+    c == Errno or c.ancestors.include? Exception
+  end
+
+  def class_or_module(c)
+    const = const_lookup(c)
+    filtered = @filter && EXCLUDED.include?(const.name)
+    return const if Module === const and not filtered
+  rescue NameError
+  end
+
+  def namespace(mod, const)
+    return const.to_s if mod.nil? or %w[Object Class Module].include? mod
+    "#{mod}::#{const}"
+  end
+
+  def map(hash, constants, mod=nil)
+    @seen = {} unless mod
+
+    constants.each do |const|
+      name = namespace mod, const
+      m = class_or_module name
+      next unless m and not @seen[m]
+      @seen[m] = true
+
+      ms = m.methods false
+      hash["#{name}."] = ms.sort unless ms.empty?
+
+      ms = m.public_instance_methods(false) +
+           m.protected_instance_methods(false)
+      hash["#{name}#"] = ms.sort unless ms.empty?
+
+      map hash, m.constants, name
+    end
+
+    hash
+  end
+
+  def dir_name(c, base)
+    return File.join(base, 'exception') if exception? c
+
+    c.split('::').inject(base) do |dir, name|
+      name.gsub!(/Class/, '') unless name == 'Class'
+      File.join dir, name.downcase
+    end
+  end
+
+  def file_name(m, c)
+    if MAP.key?(m)
+      name = MAP[m].is_a?(Hash) ? MAP[m][c.split('::').last] || MAP[m][:default] : MAP[m]
+    else
+      name = m.gsub(/[?!=]/, '')
+    end
+    "#{name}_spec.rb"
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/utils/options.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/utils/options.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/utils/options.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,441 @@
+require 'mspec/version'
+
+class MSpecOption
+  attr_reader :short, :long, :arg, :description, :block
+
+  def initialize(short, long, arg, description, block)
+    @short       = short
+    @long        = long
+    @arg         = arg
+    @description = description
+    @block       = block
+  end
+
+  def arg?
+    @arg != nil
+  end
+
+  def match?(opt)
+    opt == @short or opt == @long
+  end
+end
+
+# MSpecOptions provides a parser for command line options. It also
+# provides a composable set of options from which the runner scripts
+# can select for their particular functionality.
+class MSpecOptions
+  # Raised if incorrect or incomplete formats are passed to #on.
+  class OptionError < Exception; end
+
+  # Raised if an unrecognized option is encountered.
+  class ParseError < Exception; end
+
+  attr_accessor :config, :banner, :width, :options
+
+  def initialize(banner="", width=30, config=nil)
+    @banner   = banner
+    @config   = config
+    @width    = width
+    @options  = []
+    @doc      = []
+    @extra    = []
+    @on_extra = lambda { |x|
+      raise ParseError, "Unrecognized option: #{x}" if x[0] == ?-
+      @extra << x
+    }
+
+    yield self if block_given?
+  end
+
+  # Registers an option. Acceptable formats for arguments are:
+  #
+  #  on "-a", "description"
+  #  on "-a", "--abdc", "description"
+  #  on "-a", "ARG", "description"
+  #  on "--abdc", "ARG", "description"
+  #  on "-a", "--abdc", "ARG", "description"
+  #
+  # If an block is passed, it will be invoked when the option is
+  # matched. Not passing a block is permitted, but nonsensical.
+  def on(*args, &block)
+    raise OptionError, "option and description are required" if args.size < 2
+
+    description = args.pop
+    short, long, argument = nil
+    args.each do |arg|
+      if arg[0] == ?-
+        if arg[1] == ?-
+          long = arg
+        else
+          short = arg
+        end
+      else
+        argument = arg
+      end
+    end
+
+    add short, long, argument, description, block
+  end
+
+  # Adds documentation text for an option and adds an +MSpecOption+
+  # instance to the list of registered options.
+  def add(short, long, arg, description, block)
+    s = short ? short.dup : "  "
+    s << (short ? ", " : "  ") if long
+    doc "   #{s}#{long} #{arg}".ljust(@width-1) + " #{description}"
+    @options << MSpecOption.new(short, long, arg, description, block)
+  end
+
+  # Searches all registered options to find a match for +opt+. Returns
+  # +nil+ if no registered options match.
+  def match?(opt)
+    @options.find { |o| o.match? opt }
+  end
+
+  # Processes an option. Calles the #on_extra block (or default) for
+  # unrecognized options. For registered options, possibly fetches an
+  # argument and invokes the option's block if it is not nil.
+  def process(argv, entry, opt, arg)
+    unless option = match?(opt)
+      @on_extra[entry]
+    else
+      if option.arg?
+        arg = argv.shift if arg.nil?
+        raise ParseError, "No argument provided for #{opt}" unless arg
+      end
+      option.block[arg] if option.block
+    end
+    option
+  end
+
+  # Splits a string at +n+ characters into the +opt+ and the +rest+.
+  # The +arg+ is set to +nil+ if +rest+ is an empty string.
+  def split(str, n)
+    opt  = str[0, n]
+    rest = str[n, str.size]
+    arg  = rest == "" ? nil : rest
+    return opt, arg, rest
+  end
+
+  # Parses an array of command line entries, calling blocks for
+  # registered options.
+  def parse(argv=ARGV)
+    argv = Array(argv).dup
+
+    while entry = argv.shift
+      # collect everything that is not an option
+      if entry[0] != ?- or entry.size < 2
+        @on_extra[entry]
+        next
+      end
+
+      # this is a long option
+      if entry[1] == ?-
+        opt, arg = entry.split "="
+        process argv, entry, opt, arg
+        next
+      end
+
+      # disambiguate short option group from short option with argument
+      opt, arg, rest = split entry, 2
+
+      # process first option
+      option = process argv, entry, opt, arg
+      next unless option and not option.arg?
+
+      # process the rest of the options
+      while rest.size > 0
+        opt, arg, rest = split rest, 1
+        opt = "-" + opt
+        option = process argv, opt, opt, arg
+        break if option.arg?
+      end
+    end
+
+    @extra
+  rescue ParseError => e
+    puts self
+    puts e
+    exit 1
+  end
+
+  # Adds a string of documentation text inline in the text generated
+  # from the options. See #on and #add.
+  def doc(str)
+    @doc << str
+  end
+
+  # Convenience method for providing -v, --version options.
+  def version(version, &block)
+    show = block || lambda { puts "#{File.basename $0} #{version}"; exit }
+    on "-v", "--version", "Show version", &show
+  end
+
+  # Convenience method for providing -h, --help options.
+  def help(&block)
+    help = block || lambda { puts self; exit 1 }
+    on "-h", "--help", "Show this message", &help
+  end
+
+  # Stores a block that will be called with unrecognized options
+  def on_extra(&block)
+    @on_extra = block
+  end
+
+  # Returns a string representation of the options and doc strings.
+  def to_s
+    @banner + "\n\n" + @doc.join("\n") + "\n"
+  end
+
+  # The methods below provide groups of options that
+  # are composed by the particular runners to provide
+  # their functionality
+
+  def configure(&block)
+    on("-B", "--config", "FILE",
+       "Load FILE containing configuration options", &block)
+  end
+
+  def name
+    on("-n", "--name", "RUBY_NAME",
+       "Set the value of RUBY_NAME (used to determine the implementation)") do |n|
+      Object.const_set :RUBY_NAME, n
+    end
+  end
+
+  def targets
+    on("-t", "--target", "TARGET",
+       "Implementation to run the specs, where:") do |t|
+      case t
+      when 'r', 'ruby'
+        config[:target] = 'ruby'
+      when 'r19', 'ruby19'
+        config[:target] = 'ruby1.9'
+      when 'x', 'rubinius'
+        config[:target] = './bin/rbx'
+      when 'X', 'rbx'
+        config[:target] = 'rbx'
+      when 'j', 'jruby'
+        config[:target] = 'jruby'
+      when 'i','ironruby'
+        config[:target] = 'ir'
+      else
+        config[:target] = t
+      end
+    end
+
+    doc ""
+    doc "     r or ruby              invokes ruby in PATH"
+    doc "     r19, ruby19 or ruby1.9 invokes ruby1.9 in PATH"
+    doc "     x or rubinius          invokes ./bin/rbx"
+    doc "     X or rbx               invokes rbx in PATH"
+    doc "     j or jruby             invokes jruby in PATH"
+    doc "     i or ironruby          invokes ir in PATH\n"
+
+    on("-T", "--target-opt", "OPT",
+       "Pass OPT as a flag to the target implementation") do |t|
+      config[:flags] << t
+    end
+    on("-I", "--include", "DIR",
+       "Pass DIR through as the -I option to the target") do |d|
+      config[:includes] << "-I#{d}"
+    end
+    on("-r", "--require", "LIBRARY",
+       "Pass LIBRARY through as the -r option to the target") do |f|
+      config[:requires] << "-r#{f}"
+    end
+  end
+
+  def formatters
+    on("-f", "--format", "FORMAT",
+       "Formatter for reporting, where FORMAT is one of:") do |o|
+      case o
+      when 's', 'spec', 'specdoc'
+        config[:formatter] = SpecdocFormatter
+      when 'h', 'html'
+        config[:formatter] = HtmlFormatter
+      when 'd', 'dot', 'dotted'
+        config[:formatter] = DottedFormatter
+      when 'b', 'describe'
+        config[:formatter] = DescribeFormatter
+      when 'f', 'file'
+        config[:formatter] = FileFormatter
+      when 'u', 'unit', 'unitdiff'
+        config[:formatter] = UnitdiffFormatter
+      when 'm', 'summary'
+        config[:formatter] = SummaryFormatter
+      when 'a', '*', 'spin'
+        config[:formatter] = SpinnerFormatter
+      when 't', 'method'
+        config[:formatter] = MethodFormatter
+      when 'y', 'yaml'
+        config[:formatter] = YamlFormatter
+      else
+        puts "Unknown format: #{o}"
+        puts @parser
+        exit
+      end
+    end
+
+    doc ""
+    doc "       s, spec, specdoc         SpecdocFormatter"
+    doc "       h, html,                 HtmlFormatter"
+    doc "       d, dot, dotted           DottedFormatter"
+    doc "       f, file                  FileFormatter"
+    doc "       u, unit, unitdiff        UnitdiffFormatter"
+    doc "       m, summary               SummaryFormatter"
+    doc "       a, *, spin               SpinnerFormatter"
+    doc "       t, method                MethodFormatter"
+    doc "       y, yaml                  YamlFormatter\n"
+
+    on("-o", "--output", "FILE",
+       "Write formatter output to FILE") do |f|
+      config[:output] = f
+    end
+  end
+
+  def filters
+    on("-e", "--example", "STR",
+       "Run examples with descriptions matching STR") do |o|
+      config[:includes] << o
+    end
+    on("-E", "--exclude", "STR",
+       "Exclude examples with descriptions matching STR") do |o|
+      config[:excludes] << o
+    end
+    on("-p", "--pattern", "PATTERN",
+       "Run examples with descriptions matching PATTERN") do |o|
+      config[:patterns] << Regexp.new(o)
+    end
+    on("-P", "--excl-pattern", "PATTERN",
+       "Exclude examples with descriptions matching PATTERN") do |o|
+      config[:xpatterns] << Regexp.new(o)
+    end
+    on("-g", "--tag", "TAG",
+       "Run examples with descriptions matching ones tagged with TAG") do |o|
+      config[:tags] << o
+    end
+    on("-G", "--excl-tag", "TAG",
+       "Exclude examples with descriptions matching ones tagged with TAG") do |o|
+      config[:xtags] << o
+    end
+    on("-w", "--profile", "FILE",
+       "Run examples for methods listed in the profile FILE") do |f|
+      config[:profiles] << f
+    end
+    on("-W", "--excl-profile", "FILE",
+       "Exclude examples for methods listed in the profile FILE") do |f|
+      config[:xprofiles] << f
+    end
+  end
+
+  def chdir
+    on("-C", "--chdir", "DIR",
+       "Change the working directory to DIR before running specs") do |d|
+      Dir.chdir d
+    end
+  end
+
+  def prefix
+    on("--prefix", "STR", "Prepend STR when resolving spec file names") do |p|
+      config[:prefix] = p
+    end
+  end
+
+  def pretend
+    on("-Z", "--dry-run",
+       "Invoke formatters and other actions, but don't execute the specs") do
+      MSpec.register_mode :pretend
+    end
+  end
+
+  def background
+    on("--background",
+       "Enable guard for specs that may hang in background processes") do
+      MSpec.register_mode :background
+    end
+  end
+
+  def unguarded
+    on("--unguarded", "Turn off all guards") do
+      MSpec.register_mode :unguarded
+    end
+    on("--no-ruby_bug", "Turn off the ruby_bug guard") do
+      MSpec.register_mode :no_ruby_bug
+    end
+  end
+
+  def randomize
+    on("-H", "--random",
+       "Randomize the list of spec files") do
+      MSpec.randomize
+    end
+  end
+
+  def verbose
+    on("-V", "--verbose", "Output the name of each file processed") do
+      obj = Object.new
+      def obj.start
+        @width = MSpec.retrieve(:files).inject(0) { |max, f| f.size > max ? f.size : max }
+      end
+      def obj.load
+        file = MSpec.retrieve :file
+        print "\n#{file.ljust(@width)}"
+      end
+      MSpec.register :start, obj
+      MSpec.register :load, obj
+    end
+
+    on("-m", "--marker", "MARKER",
+       "Output MARKER for each file processed") do |o|
+      obj = Object.new
+      obj.instance_variable_set :@marker, o
+      def obj.load
+        print @marker
+      end
+      MSpec.register :load, obj
+    end
+  end
+
+  def interrupt
+    on("--int-spec", "Control-C interupts the current spec only") do
+      config[:abort] = false
+    end
+  end
+
+  def verify
+    on("--report-on", "GUARD", "Report specs guarded by GUARD") do |g|
+      MSpec.register_mode :report_on
+      SpecGuard.guards << g.to_sym
+    end
+    on("-O", "--report", "Report guarded specs") do
+      MSpec.register_mode :report
+    end
+    on("-Y", "--verify",
+       "Verify that guarded specs pass and fail as expected") do
+      MSpec.register_mode :verify
+    end
+  end
+
+  def action_filters
+    on("-K", "--action-tag", "TAG",
+       "Spec descriptions marked with TAG will trigger the specified action") do |o|
+      config[:atags] << o
+    end
+    on("-S", "--action-string", "STR",
+       "Spec descriptions matching STR will trigger the specified action") do |o|
+      config[:astrings] << o
+    end
+  end
+
+  def actions
+    on("--spec-debug",
+       "Invoke the debugger when a spec description matches (see -K, -S)") do
+      config[:debugger] = true
+    end
+    on("--spec-gdb",
+       "Invoke Gdb when a spec description matches (see -K, -S)") do
+      config[:gdb] = true
+    end
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/utils/ruby_name.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/utils/ruby_name.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/utils/ruby_name.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,8 @@
+unless Object.const_defined?(:RUBY_NAME) and RUBY_NAME
+  if Object.const_defined?(:RUBY_ENGINE) and RUBY_ENGINE
+    RUBY_NAME = RUBY_ENGINE
+  else
+    require 'rbconfig'
+    RUBY_NAME = Config::CONFIG["RUBY_INSTALL_NAME"] || Config::CONFIG["ruby_install_name"]
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/utils/script.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/utils/script.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/utils/script.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,219 @@
+require 'mspec/guards/guard'
+require 'mspec/runner/formatters/dotted'
+
+# MSpecScript provides a skeleton for all the MSpec runner scripts.
+
+class MSpecScript
+  # Returns the config object. Maintained at the class
+  # level to easily enable simple config files. See the
+  # class method +set+.
+  def self.config
+    @config ||= {
+      :path => ['.', 'spec'],
+      :config_ext => '.mspec'
+    }
+  end
+
+  # Associates +value+ with +key+ in the config object. Enables
+  # simple config files of the form:
+  #
+  #   class MSpecScript
+  #     set :target, "ruby"
+  #     set :files, ["one_spec.rb", "two_spec.rb"]
+  #   end
+  def self.set(key, value)
+    config[key] = value
+  end
+
+  # Gets the value of +key+ from the config object. Simplifies
+  # getting values in a config file:
+  #
+  #   class MSpecScript
+  #     set :a, 1
+  #     set :b, 2
+  #     set :c, get(:a) + get(:b)
+  #   end
+  def self.get(key)
+    config[key]
+  end
+
+  def initialize
+    config[:formatter] = nil
+    config[:includes]  = []
+    config[:excludes]  = []
+    config[:patterns]  = []
+    config[:xpatterns] = []
+    config[:tags]      = []
+    config[:xtags]     = []
+    config[:profiles]  = []
+    config[:xprofiles] = []
+    config[:atags]     = []
+    config[:astrings]  = []
+    config[:ltags]     = []
+    config[:abort]     = true
+  end
+
+  # Returns the config object maintained by the instance's class.
+  # See the class methods +set+ and +config+.
+  def config
+    MSpecScript.config
+  end
+
+  # Returns +true+ if the file was located in +config[:path]+,
+  # possibly appending +config[:config_ext]. Returns +false+
+  # otherwise.
+  def load(target)
+    names = [target]
+    unless target[-6..-1] == config[:config_ext]
+      names << target + config[:config_ext]
+    end
+
+    names.each do |name|
+      return Kernel.load(name) if File.exist?(File.expand_path(name))
+
+      config[:path].each do |dir|
+        file = File.join dir, name
+        return Kernel.load(file) if File.exist? file
+      end
+    end
+
+    false
+  end
+
+  # Attempts to load a default config file. First tries to load
+  # 'default.mspec'. If that fails, attempts to load a config
+  # file name constructed from the value of RUBY_ENGINE and the
+  # first two numbers in RUBY_VERSION. For example, on MRI 1.8.6,
+  # the file name would be 'ruby.1.8.mspec'.
+  def load_default
+    return if load 'default.mspec'
+
+    if Object.const_defined?(:RUBY_ENGINE)
+      engine = RUBY_ENGINE
+    else
+      engine = 'ruby'
+    end
+    load "#{engine}.#{SpecGuard.ruby_version}.mspec"
+  end
+
+  # Callback for enabling custom options. This version is a no-op.
+  # Provide an implementation specific version in a config file.
+  # Called by #options after the MSpec-provided options are added.
+  def custom_options(options)
+    options.doc "   No custom options registered"
+  end
+
+  # Registers all filters and actions.
+  def register
+    if config[:formatter].nil?
+      config[:formatter] = @files.size < 50 ? DottedFormatter : FileFormatter
+    end
+
+    if config[:formatter]
+      formatter = config[:formatter].new(config[:output])
+      formatter.register
+      MSpec.store :formatter, formatter
+    end
+
+    MatchFilter.new(:include, *config[:includes]).register    unless config[:includes].empty?
+    MatchFilter.new(:exclude, *config[:excludes]).register    unless config[:excludes].empty?
+    RegexpFilter.new(:include, *config[:patterns]).register   unless config[:patterns].empty?
+    RegexpFilter.new(:exclude, *config[:xpatterns]).register  unless config[:xpatterns].empty?
+    TagFilter.new(:include, *config[:tags]).register          unless config[:tags].empty?
+    TagFilter.new(:exclude, *config[:xtags]).register         unless config[:xtags].empty?
+    ProfileFilter.new(:include, *config[:profiles]).register  unless config[:profiles].empty?
+    ProfileFilter.new(:exclude, *config[:xprofiles]).register unless config[:xprofiles].empty?
+
+    DebugAction.new(config[:atags], config[:astrings]).register if config[:debugger]
+    GdbAction.new(config[:atags], config[:astrings]).register   if config[:gdb]
+
+    custom_register
+  end
+
+  # Callback for enabling custom actions, etc. This version is a
+  # no-op. Provide an implementation specific version in a config
+  # file. Called by #register.
+  def custom_register
+  end
+
+  # Sets up signal handlers. Only a handler for SIGINT is
+  # registered currently.
+  def signals
+    if config[:abort]
+      Signal.trap "INT" do
+        puts "\nProcess aborted!"
+        exit! 1
+      end
+    end
+  end
+
+  # Attempts to resolve +partial+ as a file or directory name in the
+  # following order:
+  #
+  #   1. +partial+
+  #   2. +partial+ + "_spec.rb"
+  #   3. <tt>File.join(config[:prefix], partial)</tt>
+  #   4. <tt>File.join(config[:prefix], partial + "_spec.rb")</tt>
+  #
+  # If it is a file name, returns the name as an entry in an array.
+  # If it is a directory, returns all *_spec.rb files in the
+  # directory and subdirectories.
+  #
+  # If unable to resolve +partial+, returns <tt>Dir[partial]</tt>.
+  def entries(partial)
+    file = partial + "_spec.rb"
+    patterns = [partial]
+    patterns << file
+    if config[:prefix]
+      patterns << File.join(config[:prefix], partial)
+      patterns << File.join(config[:prefix], file)
+    end
+
+    patterns.each do |pattern|
+      expanded = File.expand_path(pattern)
+      return [pattern] if File.file?(expanded)
+
+      specs = File.join(pattern, "/**/*_spec.rb")
+      specs = File.expand_path(specs) rescue specs
+      return Dir[specs].sort if File.directory?(expanded)
+    end
+
+    Dir[partial]
+  end
+
+  # Resolves each entry in +list+ to a set of files.
+  #
+  # If the entry has a leading '^' character, the list of files
+  # is subtracted from the list of files accumulated to that point.
+  #
+  # If the entry has a leading ':' character, the corresponding
+  # key is looked up in the config object and the entries in the
+  # value retrieved are processed through #entries.
+  def files(list)
+    list.inject([]) do |files, item|
+      case item[0]
+      when ?^
+        files -= entries(item[1..-1])
+      when ?:
+        key = item[1..-1].to_sym
+        files += files(Array(config[key]))
+      else
+        files += entries(item)
+      end
+      files
+    end
+  end
+
+  # Instantiates an instance and calls the series of methods to
+  # invoke the script.
+  def self.main
+    $VERBOSE = nil unless ENV['OUTPUT_WARNINGS']
+    script = new
+    script.load_default
+    script.load '~/.mspecrc'
+    script.options
+    script.signals
+    script.register
+    script.run
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/utils/version.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/utils/version.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/utils/version.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,53 @@
+class SpecVersion
+  # If beginning implementations have a problem with this include, we can
+  # manually implement the relational operators that are needed.
+  include Comparable
+
+  # SpecVersion handles comparison correctly for the context by filling in
+  # missing version parts according to the value of +ceil+. If +ceil+ is
+  # +false+, 0 digits fill in missing version parts. If +ceil+ is +true+, 9
+  # digits fill in missing parts. (See e.g. VersionGuard and BugGuard.)
+  def initialize(version, ceil = false)
+    @version = version
+    @ceil    = ceil
+    @integer = nil
+  end
+
+  def to_s
+    @version
+  end
+
+  def to_str
+    to_s
+  end
+
+  # Converts a string representation of a version major.minor.tiny.patchlevel
+  # to an integer representation so that comparisons can be made. For example,
+  # "1.8.6.77" < "1.8.6.123" would be false if compared as strings.
+  def to_i
+    unless @integer
+      major, minor, tiny, patch = @version.split "."
+      if @ceil
+        tiny = 99 unless tiny
+        patch = 9999 unless patch
+      end
+      parts = [major, minor, tiny, patch].map { |x| x.to_i }
+      @integer = ("1%02d%02d%02d%04d" % parts).to_i
+    end
+    @integer
+  end
+
+  def to_int
+    to_i
+  end
+
+  def <=>(other)
+    if other.respond_to? :to_int
+      other = Integer other
+    else
+      other = SpecVersion.new(String(other)).to_i
+    end
+
+    self.to_i <=> other
+  end
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec/version.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec/version.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec/version.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,5 @@
+require 'mspec/utils/version'
+
+module MSpec
+  VERSION = SpecVersion.new "1.5.10"
+end

Added: MacRuby/branches/experimental/mspec/lib/mspec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/lib/mspec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/lib/mspec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,11 @@
+require 'mspec/matchers'
+require 'mspec/expectations'
+require 'mspec/mocks'
+require 'mspec/runner'
+require 'mspec/guards'
+require 'mspec/helpers'
+
+# If the implementation on which the specs are run cannot
+# load pp from the standard library, add a pp.rb file that
+# defines the #pretty_inspect method on Object or Kernel.
+require 'pp'
\ No newline at end of file

Added: MacRuby/branches/experimental/mspec/mspec.gemspec
===================================================================
--- MacRuby/branches/experimental/mspec/mspec.gemspec	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/mspec.gemspec	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,33 @@
+Gem::Specification.new do |s|
+  s.name                      = %q{mspec}
+  s.version                   = "1.5.10"
+
+  s.specification_version     = 2 if s.respond_to? :specification_version=
+
+  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
+  s.authors                   = ["Brian Ford"]
+  s.date                      = %q{2009-3-1}
+  s.email                     = %q{bford at engineyard.com}
+  s.has_rdoc                  = true
+  s.extra_rdoc_files          = %w[ README LICENSE ]
+  s.executables               = ["mkspec", "mspec", "mspec-ci", "mspec-run", "mspec-tag"]
+  s.files                     = FileList[ '{bin,lib,spec}/**/*.{yaml,txt,rb}', 'Rakefile', *s.extra_rdoc_files ]
+  s.homepage                  = %q{http://rubyspec.org}
+  s.rubyforge_project         = 'http://rubyforge.org/projects/mspec'
+  s.require_paths             = ["lib"]
+  s.rubygems_version          = %q{1.1.1}
+  s.summary                   = <<EOS
+MSpec is a specialized framework that is syntax-compatible
+with RSpec for basic things like describe, it blocks and
+before, after actions.
+
+MSpec contains additional features that assist in writing
+the RubySpecs used by multiple Ruby implementations. Also,
+MSpec attempts to use the simplest Ruby language features
+so that beginning Ruby implementations can run it.
+EOS
+
+  s.rdoc_options << '--title' << 'MSpec Gem' <<
+                   '--main' << 'README' <<
+                   '--line-numbers'
+end

Added: MacRuby/branches/experimental/mspec/spec/commands/mkspec_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/commands/mkspec_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/commands/mkspec_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,332 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/commands/mkspec'
+
+
+describe "The -c, --constant CONSTANT option" do
+  before :each do
+    @options = MSpecOptions.new
+    MSpecOptions.stub!(:new).and_return(@options)
+    @script = MkSpec.new
+    @config = @script.config
+  end
+
+  it "is enabled by #options" do
+    @options.stub!(:on)
+    @options.should_receive(:on).with("-c", "--constant", "CONSTANT",
+      an_instance_of(String))
+    @script.options
+  end
+
+  it "adds CONSTANT to the list of constants" do
+    ["-c", "--constant"].each do |opt|
+      @config[:constants] = []
+      @script.options [opt, "Object"]
+      @config[:constants].should include("Object")
+    end
+  end
+end
+
+describe "The -b, --base DIR option" do
+  before :each do
+    @options = MSpecOptions.new
+    MSpecOptions.stub!(:new).and_return(@options)
+    @script = MkSpec.new
+    @config = @script.config
+  end
+
+  it "is enabled by #options" do
+    @options.stub!(:on)
+    @options.should_receive(:on).with("-b", "--base", "DIR",
+      an_instance_of(String))
+    @script.options
+  end
+
+  it "sets the base directory relative to which the spec directories are created" do
+    ["-b", "--base"].each do |opt|
+      @config[:base] = nil
+      @script.options [opt, "superspec"]
+      @config[:base].should == File.expand_path("superspec")
+    end
+  end
+end
+
+describe "The -r, --require LIBRARY option" do
+  before :each do
+    @options = MSpecOptions.new
+    MSpecOptions.stub!(:new).and_return(@options)
+    @script = MkSpec.new
+    @config = @script.config
+  end
+
+  it "is enabled by #options" do
+    @options.stub!(:on)
+    @options.should_receive(:on).with("-r", "--require", "LIBRARY",
+      an_instance_of(String))
+    @script.options
+  end
+
+  it "adds CONSTANT to the list of constants" do
+    ["-r", "--require"].each do |opt|
+      @config[:requires] = []
+      @script.options [opt, "libspec"]
+      @config[:requires].should include("libspec")
+    end
+  end
+end
+
+describe MkSpec, "#options" do
+  before :each do
+    @options = MSpecOptions.new
+    MSpecOptions.stub!(:new).and_return(@options)
+    @script = MkSpec.new
+  end
+
+  it "parses the command line options" do
+    @options.should_receive(:parse).with(["--this", "and", "--that"])
+    @script.options ["--this", "and", "--that"]
+  end
+
+  it "parses ARGV unless passed other options" do
+    @options.should_receive(:parse).with(ARGV)
+    @script.options
+  end
+
+  it "prints help and exits if passed an unrecognized option" do
+    @options.should_receive(:raise).with(MSpecOptions::ParseError, an_instance_of(String))
+    @options.stub!(:puts)
+    @options.stub!(:exit)
+    @script.options "--iunknown"
+  end
+end
+
+describe MkSpec, "#create_directory" do
+  before :each do
+    @script = MkSpec.new
+    @script.config[:base] = "spec"
+  end
+
+  it "prints a warning if a file with the directory name exists" do
+    File.should_receive(:exist?).and_return(true)
+    File.should_receive(:directory?).and_return(false)
+    FileUtils.should_not_receive(:mkdir_p)
+    @script.should_receive(:puts).with("spec/class already exists and is not a directory.")
+    @script.create_directory("Class").should == nil
+  end
+
+  it "does nothing if the directory already exists" do
+    File.should_receive(:exist?).and_return(true)
+    File.should_receive(:directory?).and_return(true)
+    FileUtils.should_not_receive(:mkdir_p)
+    @script.create_directory("Class").should == "spec/class"
+  end
+
+  it "creates the directory if it does not exist" do
+    File.should_receive(:exist?).and_return(false)
+    FileUtils.should_receive(:mkdir_p).with("spec/class")
+    @script.create_directory("Class").should == "spec/class"
+  end
+
+  it "creates the directory for a namespaced module if it does not exist" do
+    File.should_receive(:exist?).and_return(false)
+    FileUtils.should_receive(:mkdir_p).with("spec/struct/tms")
+    @script.create_directory("Struct::Tms").should == "spec/struct/tms"
+  end
+end
+
+describe MkSpec, "#write_requires" do
+  before :each do
+    @script = MkSpec.new
+    @script.config[:base] = "spec"
+
+    @file = mock("file")
+    File.stub!(:open).and_yield(@file)
+  end
+
+  it "writes the spec_helper require line" do
+    @file.should_receive(:puts).with("require File.dirname(__FILE__) + '/../../../spec_helper'")
+    @script.write_requires("spec/core/tcejbo", "spec/core/tcejbo/inspect_spec.rb")
+  end
+
+  it "writes require lines for each library specified on the command line" do
+    @file.stub!(:puts)
+    @file.should_receive(:puts).with("require File.dirname(__FILE__) + '/../../../spec_helper'")
+    @file.should_receive(:puts).with("require 'complex'")
+    @script.config[:requires] << 'complex'
+    @script.write_requires("spec/core/tcejbo", "spec/core/tcejbo/inspect_spec.rb")
+  end
+end
+
+describe MkSpec, "#write_spec" do
+  before :each do
+    @file = mock("file")
+    @file.stub!(:puts)
+    File.stub!(:open).and_yield(@file)
+
+    @script = MkSpec.new
+    @script.stub!(:puts)
+
+    @response = mock("system command response")
+    @response.stub!(:=~).and_return(false)
+    @script.stub!(:`).and_return(@response)
+  end
+
+  it "checks if specs exist for the method if the spec file exists" do
+    @script.should_receive(:`).with(
+        /mspec\/bin\/mspec-run --dry-run -fs -e 'Object#inspect' spec\/core\/tcejbo\/inspect_spec.rb/)
+    @script.write_spec("spec/core/tcejbo/inspect_spec.rb", "Object#inspect", true)
+  end
+
+  it "escapes the Regexp when checking for method name in the spec file output" do
+    Regexp.should_receive(:escape).with("Array#[]=")
+    @script.write_spec("spec/core/yarra/element_set_spec.rb", "Array#[]=", true)
+  end
+
+  it "returns nil if the spec file exists and contains a spec for the method" do
+    @response.stub!(:=~).and_return(true)
+    @script.write_spec("spec/core/tcejbo/inspect_spec.rb", "Object#inspect", true).should == nil
+  end
+
+  it "does not print the spec file name if it exists and contains a spec for the method" do
+    @response.stub!(:=~).and_return(true)
+    @script.should_not_receive(:puts)
+    @script.write_spec("spec/core/tcejbo/inspect_spec.rb", "Object#inspect", true)
+  end
+
+  it "prints the spec file name if a template spec is written" do
+    @script.should_receive(:puts).with("spec/core/tcejbo/inspect_spec.rb")
+    @script.write_spec("spec/core/tcejbo/inspect_spec.rb", "Object#inspect", true)
+  end
+
+  it "writes a template spec to the file if the spec file does not exist" do
+    @file.should_receive(:puts)
+    @script.should_receive(:puts).with("spec/core/tcejbo/inspect_spec.rb")
+    @script.write_spec("spec/core/tcejbo/inspect_spec.rb", "Object#inspect", false)
+  end
+
+  it "writes a template spec to the file if it exists but contains no spec for the method" do
+    @response.should_receive(:=~).and_return(false)
+    @file.should_receive(:puts)
+    @script.should_receive(:puts).with("spec/core/tcejbo/inspect_spec.rb")
+    @script.write_spec("spec/core/tcejbo/inspect_spec.rb", "Object#inspect", true)
+  end
+
+  it "writes a template spec" do
+    @file.should_receive(:puts).with(<<EOS)
+
+describe "Object#inspect" do
+  it "needs to be reviewed for spec completeness"
+end
+EOS
+    @script.write_spec("spec/core/tcejbo/inspect_spec.rb", "Object#inspect", true)
+  end
+end
+
+describe MkSpec, "#create_file" do
+  before :each do
+    @script = MkSpec.new
+    @script.stub!(:write_requires)
+    @script.stub!(:write_spec)
+
+    File.stub!(:exist?).and_return(false)
+  end
+
+  it "generates a file name based on the directory, class/module, and method" do
+    File.should_receive(:join).with("spec/tcejbo", "inspect_spec.rb"
+        ).and_return("spec/tcejbo/inspect_spec.rb")
+    @script.create_file("spec/tcejbo", "Object", "inspect", "Object#inspect")
+  end
+
+  it "does not call #write_requires if the spec file already exists" do
+    File.should_receive(:exist?).and_return(true)
+    @script.should_not_receive(:write_requires)
+    @script.create_file("spec/tcejbo", "Object", "inspect", "Object#inspect")
+  end
+
+  it "calls #write_requires if the spec file does not exist" do
+    File.should_receive(:exist?).and_return(false)
+    @script.should_receive(:write_requires).with(
+        "spec/tcejbo", "spec/tcejbo/inspect_spec.rb")
+    @script.create_file("spec/tcejbo", "Object", "inspect", "Object#inspect")
+  end
+
+  it "calls #write_spec with the file, method name" do
+    @script.should_receive(:write_spec).with(
+        "spec/tcejbo/inspect_spec.rb", "Object#inspect", false)
+    @script.create_file("spec/tcejbo", "Object", "inspect", "Object#inspect")
+  end
+end
+
+describe MkSpec, "#run" do
+  before :each do
+    @options = MSpecOptions.new
+    MSpecOptions.stub!(:new).and_return(@options)
+
+    @map = NameMap.new
+    NameMap.stub!(:new).and_return(@map)
+
+    @script = MkSpec.new
+    @script.stub!(:create_directory).and_return("spec/mkspec")
+    @script.stub!(:create_file)
+    @script.config[:constants] = [MkSpec]
+  end
+
+  it "loads files in the requires list" do
+    @script.stub!(:require)
+    @script.should_receive(:require).with("alib")
+    @script.should_receive(:require).with("blib")
+    @script.config[:requires] = ["alib", "blib"]
+    @script.run
+  end
+
+  it "creates a map of constants to methods" do
+    @map.should_receive(:map).with({}, @script.config[:constants]).and_return({})
+    @script.run
+  end
+
+  it "creates a map of Object.constants if not constants are specified" do
+    @script.config[:constants] = []
+    Object.stub!(:constants).and_return(["Object"])
+    @map.should_receive(:filter).with(["Object"]).and_return(["Object"])
+    @map.should_receive(:map).with({}, ["Object"]).and_return({})
+    @script.run
+  end
+
+  it "calls #create_directory for each class/module in the map" do
+    @script.should_receive(:create_directory).with("MkSpec").twice
+    @script.run
+  end
+
+  it "calls #create_file for each method on each class/module in the map" do
+    @map.should_receive(:map).with({}, @script.config[:constants]).and_return({"MkSpec#" => "run"})
+    @script.should_receive(:create_file).with("spec/mkspec", "MkSpec", "run", "MkSpec#run")
+    @script.run
+  end
+end
+
+describe MkSpec, ".main" do
+  before :each do
+    @script = mock("MkSpec", :null_object => true)
+    MkSpec.stub!(:new).and_return(@script)
+  end
+
+  it "sets MSPEC_RUNNER = '1' in the environment" do
+    ENV["MSPEC_RUNNER"] = "0"
+    MkSpec.main
+    ENV["MSPEC_RUNNER"].should == "1"
+  end
+
+  it "creates an instance of MSpecScript" do
+    MkSpec.should_receive(:new).and_return(@script)
+    MkSpec.main
+  end
+
+  it "calls the #options method on the script" do
+    @script.should_receive(:options)
+    MkSpec.main
+  end
+
+  it "calls the #run method on the script" do
+    @script.should_receive(:run)
+    MkSpec.main
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/commands/mspec_ci_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/commands/mspec_ci_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/commands/mspec_ci_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,142 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/runner/mspec'
+require 'mspec/runner/filters/tag'
+require 'mspec/commands/mspec-ci'
+
+describe MSpecCI, "#options" do
+  before :each do
+    @options, @config = new_option
+    MSpecOptions.stub!(:new).and_return(@options)
+
+    @script = MSpecCI.new
+    @script.stub!(:config).and_return(@config)
+    @script.stub!(:files).and_return([])
+  end
+
+  it "enables the chdir option" do
+    @options.should_receive(:chdir)
+    @script.options
+  end
+
+  it "enables the prefix option" do
+    @options.should_receive(:prefix)
+    @script.options
+  end
+
+  it "enables the config option" do
+    @options.should_receive(:configure)
+    @script.options
+  end
+
+  it "provides a custom action (block) to the config option" do
+    @script.should_receive(:load).with("cfg.mspec")
+    @script.options ["-B", "cfg.mspec"]
+  end
+
+  it "enables the name option" do
+    @options.should_receive(:name)
+    @script.options
+  end
+
+  it "enables the dry run option" do
+    @options.should_receive(:pretend)
+    @script.options
+  end
+
+  it "enables the background option" do
+    @options.should_receive(:background)
+    @script.options
+  end
+
+  it "enables the unguarded option" do
+    @options.should_receive(:unguarded)
+    @script.options
+  end
+
+  it "enables the interrupt single specs option" do
+    @options.should_receive(:interrupt)
+    @script.options
+  end
+
+  it "enables the formatter options" do
+    @options.should_receive(:formatters)
+    @script.options
+  end
+
+  it "enables the verbose option" do
+    @options.should_receive(:verbose)
+    @script.options
+  end
+
+  it "enables the action options" do
+    @options.should_receive(:actions)
+    @script.options
+  end
+
+  it "enables the action filter options" do
+    @options.should_receive(:action_filters)
+    @script.options
+  end
+
+  it "enables the version option" do
+    @options.should_receive(:version)
+    @script.options
+  end
+
+  it "enables the help option" do
+    @options.should_receive(:help)
+    @script.options
+  end
+
+  it "calls #custom_options" do
+    @script.should_receive(:custom_options).with(@options)
+    @script.options
+  end
+end
+
+describe MSpecCI, "#run" do
+  before :each do
+    MSpec.stub!(:process)
+
+    @filter = mock("TagFilter")
+    TagFilter.stub!(:new).and_return(@filter)
+    @filter.stub!(:register)
+
+    @config = { :ci_files => ["one", "two"] }
+    @script = MSpecCI.new
+    @script.stub!(:exit)
+    @script.stub!(:config).and_return(@config)
+    @script.stub!(:files).and_return(["one", "two"])
+    @script.options
+  end
+
+  it "registers the tags patterns" do
+    @config[:tags_patterns] = [/spec/, "tags"]
+    MSpec.should_receive(:register_tags_patterns).with([/spec/, "tags"])
+    @script.run
+  end
+
+  it "registers the files to process" do
+    MSpec.should_receive(:register_files).with(["one", "two"])
+    @script.run
+  end
+
+  it "registers a tag filter for 'fails', 'unstable', 'incomplete', 'critical', 'unsupported'" do
+    filter = mock("fails filter")
+    TagFilter.should_receive(:new).with(:exclude,
+        "fails", "critical", "unstable", "incomplete", "unsupported").and_return(filter)
+    filter.should_receive(:register)
+    @script.run
+  end
+
+  it "processes the files" do
+    MSpec.should_receive(:process)
+    @script.run
+  end
+
+  it "exits with the exit code registered with MSpec" do
+    MSpec.stub!(:exit_code).and_return(7)
+    @script.should_receive(:exit).with(7)
+    @script.run
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/commands/mspec_run_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/commands/mspec_run_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/commands/mspec_run_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,178 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/runner/mspec'
+require 'mspec/commands/mspec-run'
+
+describe MSpecRun, ".new" do
+  before :each do
+    @script = MSpecRun.new
+  end
+
+  it "sets config[:files] to an empty list" do
+    @script.config[:files].should == []
+  end
+end
+
+describe MSpecRun, "#options" do
+  before :each do
+    @stdout, $stdout = $stdout, IOStub.new
+
+    @argv = ["a", "b"]
+    @options, @config = new_option
+    MSpecOptions.stub!(:new).and_return(@options)
+
+    @script = MSpecRun.new
+    @script.stub!(:config).and_return(@config)
+  end
+
+  after :each do
+    $stdout = @stdout
+  end
+
+  it "enables the filter options" do
+    @options.should_receive(:filters)
+    @script.options @argv
+  end
+
+  it "enables the chdir option" do
+    @options.should_receive(:chdir)
+    @script.options @argv
+  end
+
+  it "enables the prefix option" do
+    @options.should_receive(:prefix)
+    @script.options @argv
+  end
+
+  it "enables the configure option" do
+    @options.should_receive(:configure)
+    @script.options @argv
+  end
+
+  it "provides a custom action (block) to the config option" do
+    @script.should_receive(:load).with("cfg.mspec")
+    @script.options ["-B", "cfg.mspec", "a"]
+  end
+
+  it "enables the name option" do
+    @options.should_receive(:name)
+    @script.options @argv
+  end
+
+  it "enables the randomize option to runs specs in random order" do
+    @options.should_receive(:randomize)
+    @script.options @argv
+  end
+
+  it "enables the dry run option" do
+    @options.should_receive(:pretend)
+    @script.options @argv
+  end
+
+  it "enables the background option" do
+    @options.should_receive(:background)
+    @script.options @argv
+  end
+
+  it "enables the unguarded option" do
+    @options.should_receive(:unguarded)
+    @script.options @argv
+  end
+
+  it "enables the interrupt single specs option" do
+    @options.should_receive(:interrupt)
+    @script.options @argv
+  end
+
+  it "enables the formatter options" do
+    @options.should_receive(:formatters)
+    @script.options @argv
+  end
+
+  it "enables the verbose option" do
+    @options.should_receive(:verbose)
+    @script.options @argv
+  end
+
+  it "enables the verify options" do
+    @options.should_receive(:verify)
+    @script.options @argv
+  end
+
+  it "enables the action options" do
+    @options.should_receive(:actions)
+    @script.options @argv
+  end
+
+  it "enables the action filter options" do
+    @options.should_receive(:action_filters)
+    @script.options @argv
+  end
+
+  it "enables the version option" do
+    @options.should_receive(:version)
+    @script.options @argv
+  end
+
+  it "enables the help option" do
+    @options.should_receive(:help)
+    @script.options @argv
+  end
+
+  it "exits if there are no files to process" do
+    @options.should_receive(:parse).and_return([])
+    @script.should_receive(:exit)
+    @script.options
+    $stdout.should =~ /No files specified/
+  end
+
+  it "calls #custom_options" do
+    @script.should_receive(:custom_options).with(@options)
+    @script.options @argv
+  end
+end
+
+describe MSpecRun, "#run" do
+  before :each do
+    @script = MSpecRun.new
+    @script.stub!(:exit)
+    @spec_dir = File.expand_path(File.dirname(__FILE__)+"/fixtures")
+    @file_patterns = [
+      @spec_dir+"/level2",
+      @spec_dir+"/one_spec.rb",
+      @spec_dir+"/two_spec.rb"]
+    @files = [
+      @spec_dir+"/level2/three_spec.rb",
+      @spec_dir+"/one_spec.rb",
+      @spec_dir+"/two_spec.rb"]
+    @script.options @file_patterns
+  end
+
+  it "registers the tags patterns" do
+    @script.config[:tags_patterns] = [/spec/, "tags"]
+    MSpec.should_receive(:register_tags_patterns).with([/spec/, "tags"])
+    @script.run
+  end
+
+  it "registers the files to process" do
+    MSpec.should_receive(:register_files).with(@files)
+    @script.run
+  end
+
+  it "uses config[:files] if no files are given on the command line" do
+    @script.config[:files] = @file_patterns
+    MSpec.should_receive(:register_files).with(@files)
+    @script.options []
+    @script.run
+  end
+
+  it "processes the files" do
+    MSpec.should_receive(:process)
+    @script.run
+  end
+
+  it "exits with the exit code registered with MSpec" do
+    MSpec.stub!(:exit_code).and_return(7)
+    @script.should_receive(:exit).with(7)
+    @script.run
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/commands/mspec_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/commands/mspec_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/commands/mspec_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,425 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'yaml'
+require 'mspec/commands/mspec'
+
+describe MSpecMain, "#options" do
+  before :each do
+    @options, @config = new_option
+    MSpecOptions.stub!(:new).and_return(@options)
+
+    @script = MSpecMain.new
+    @script.stub!(:config).and_return(@config)
+    @script.stub!(:load)
+  end
+
+  it "enables the configure option" do
+    @options.should_receive(:configure)
+    @script.options
+  end
+
+  it "provides a custom action (block) to the config option" do
+    @script.options ["-B", "config"]
+    @config[:options].should include("-B", "config")
+  end
+
+  it "loads the file specified by the config option" do
+    @script.should_receive(:load).with("config")
+    @script.options ["-B", "config"]
+  end
+
+  it "enables the target options" do
+    @options.should_receive(:targets)
+    @script.options
+  end
+
+  it "sets config[:options] to all argv entries that are not registered options" do
+    @options.on "-X", "--exclude", "ARG", "description"
+    @script.options [".", "-G", "fail", "-X", "ARG", "--list", "unstable", "some/file.rb"]
+    @config[:options].should == [".", "-G", "fail", "--list", "unstable", "some/file.rb"]
+  end
+
+  it "calls #custom_options" do
+    @script.should_receive(:custom_options).with(@options)
+    @script.options
+  end
+end
+
+describe MSpecMain, "#parallel" do
+  before :all do
+    @verbose, $VERBOSE = $VERBOSE, nil
+  end
+
+  after :all do
+    $VERBOSE = @verbose
+  end
+
+  before :each do
+    @script = MSpecMain.new
+    @ruby_platform = Object.const_get :RUBY_PLATFORM
+  end
+
+  after :each do
+    Object.const_set :RUBY_PLATFORM, @ruby_platform
+  end
+
+  it "returns false if JRUBY_VERSION is defined" do
+    Object.should_receive(:const_defined?).with(:JRUBY_VERSION).and_return(true)
+    @script.parallel.should == false
+  end
+
+  it "returns false if RUBY_PLATFORM matches mswin" do
+    Object.const_set :RUBY_PLATFORM, "i386-mswin32"
+    @script.parallel.should == false
+  end
+
+  it "returns false if RUBY_PLATFORM matches mingw" do
+    Object.const_set :RUBY_PLATFORM, "i386-mingw32"
+    @script.parallel.should == false
+  end
+
+  it "returns true unless JRUBY_VERSION is set or RUBY_PLATFORM matches mswin or mingw" do
+    Object.should_receive(:const_defined?).with(:JRUBY_VERSION).and_return(false)
+    Object.const_set :RUBY_PLATFORM, "i686-linux"
+    @script.parallel.should == true
+  end
+end
+
+describe MSpecMain, "#fork" do
+  before :each do
+    @script = MSpecMain.new
+    ScratchPad.clear
+  end
+
+  it "calls Kernel.fork if #parallel returns true" do
+    @script.should_receive(:parallel).and_return(true)
+    Kernel.should_receive(:fork)
+    @script.fork
+  end
+
+  it "calls the block if #parallel returns false" do
+    @script.should_receive(:parallel).and_return(false)
+    Kernel.should_not_receive(:fork)
+    @script.fork { ScratchPad.record :called }
+    ScratchPad.recorded.should == :called
+  end
+end
+
+describe MSpecMain, "#report" do
+  before :each do
+    @stdout, $stdout = $stdout, IOStub.new
+
+    @timer = mock("timer", :null_object => true)
+    @timer.stub!(:format).and_return("Finished in 42 seconds")
+    @file = mock("file", :null_object => true)
+
+    File.stub!(:delete)
+    YAML.stub!(:load)
+
+    @hash = { "files"=>1, "examples"=>1, "expectations"=>2, "failures"=>0, "errors"=>0 }
+    File.stub!(:open).and_yield(@file).and_return(@hash)
+
+    @script = MSpecMain.new
+  end
+
+  after :each do
+    $stdout = @stdout
+  end
+
+  it "calls YAML.load for each element in the passed array" do
+    YAML.should_receive(:load).with(@file).twice
+    @script.report(["a", "b"], @timer)
+  end
+
+  it "calls File.delete for each element in the passed array" do
+    File.should_receive(:delete).with("a")
+    File.should_receive(:delete).with("b")
+    @script.report(["a", "b"], @timer)
+  end
+
+  it "outputs a summary without errors" do
+    @script.report(["a", "b"], @timer)
+    $stdout.should ==
+%[
+
+Finished in 42 seconds
+
+2 files, 2 examples, 4 expectations, 0 failures, 0 errors
+]
+  end
+
+  it "outputs a summary with errors" do
+    @hash["exceptions"] = [
+      "Some#method works real good FAILED\nExpected real good\n to equal fail\n\nfoo.rb:1\nfoo.rb:2",
+      "Some#method never fails ERROR\nExpected 5\n to equal 3\n\nfoo.rb:1\nfoo.rb:2"
+    ]
+    @script.report(["a"], @timer)
+    $stdout.should ==
+%[
+
+1)
+Some#method works real good FAILED
+Expected real good
+ to equal fail
+
+foo.rb:1
+foo.rb:2
+
+2)
+Some#method never fails ERROR
+Expected 5
+ to equal 3
+
+foo.rb:1
+foo.rb:2
+
+Finished in 42 seconds
+
+1 file, 1 example, 2 expectations, 0 failures, 0 errors
+]
+  end
+end
+
+describe MSpecMain, "#multi_exec" do
+  before :each do
+    @options, @config = new_option
+    MSpecOptions.stub!(:new).and_return(@options)
+
+    @config[:target] = "target"
+    @config[:ci_files] = ["a", "b"]
+
+    @script = MSpecMain.new
+    @script.stub!(:config).and_return(@config)
+    @script.stub!(:fork)
+    @script.stub!(:report)
+    Process.stub!(:waitall)
+  end
+
+  it "calls #fork for each entry in config[:ci_files]" do
+    @script.should_receive(:fork).twice
+    @script.multi_exec []
+  end
+
+  it "calls Process.waitall" do
+    Process.should_receive(:waitall)
+    @script.multi_exec []
+  end
+
+  it "calls #report" do
+    @script.should_receive(:report)
+    @script.multi_exec []
+  end
+end
+
+describe MSpecMain, "#run" do
+  before :each do
+    @options, @config = new_option
+    MSpecOptions.stub!(:new).and_return(@options)
+    @script = MSpecMain.new
+    @script.stub!(:config).and_return(@config)
+    @script.stub!(:exec)
+  end
+
+  it "sets MSPEC_RUNNER = '1' in the environment" do
+    ENV["MSPEC_RUNNER"] = "0"
+    @script.run
+    ENV["MSPEC_RUNNER"].should == "1"
+  end
+
+  it "sets RUBY_EXE = config[:target] in the environment" do
+    ENV["RUBY_EXE"] = nil
+    @script.run
+    ENV["RUBY_EXE"].should == @config[:target]
+  end
+
+  it "sets RUBY_FLAGS = config[:flags] in the environment" do
+    ENV["RUBY_FLAGS"] = nil
+    @config[:flags] = ["-w", "-Q"]
+    @script.run
+    ENV["RUBY_FLAGS"].should == "-w -Q"
+  end
+
+  it "uses exec to invoke the runner script" do
+    @script.should_receive(:exec).with("ruby", "-v", %r"mspec/bin/mspec-run$")
+    @script.options []
+    @script.run
+  end
+
+  it "calls #multi_exec if the command is 'ci' and the multi option is passed" do
+    @script.should_receive(:multi_exec).and_return do |arg|
+      arg.length.should == 3
+      arg[0].should == "-v"
+      arg[1].should =~ %r"mspec/bin/mspec-ci$"
+      arg[2].should == "-fy"
+    end
+    @script.options ["ci", "-j"]
+    @script.run
+  end
+end
+
+describe "The -D, --gdb option" do
+  before :each do
+    @options, @config = new_option
+    MSpecOptions.stub!(:new).and_return(@options)
+    @script = MSpecMain.new
+    @script.stub!(:config).and_return(@config)
+  end
+
+  it "is enabled by #options" do
+    @options.stub!(:on)
+    @options.should_receive(:on).with("-D", "--gdb", an_instance_of(String))
+    @script.options
+  end
+
+  it "sets flags to --gdb" do
+    ["-D", "--gdb"].each do |opt|
+      @config[:flags] = []
+      @script.options [opt]
+      @config[:flags].should include("--gdb")
+    end
+  end
+end
+
+describe "The -A, --valgrind option" do
+  before :each do
+    @options, @config = new_option
+    MSpecOptions.stub!(:new).and_return(@options)
+    @script = MSpecMain.new
+    @script.stub!(:config).and_return(@config)
+  end
+
+  it "is enabled by #options" do
+    @options.stub!(:on)
+    @options.should_receive(:on).with("-A", "--valgrind", an_instance_of(String))
+    @script.options
+  end
+
+  it "sets flags to --valgrind" do
+    ["-A", "--valgrind"].each do |opt|
+      @config[:flags] = []
+      @script.options [opt]
+      @config[:flags].should include("--valgrind")
+    end
+  end
+end
+
+describe "The --warnings option" do
+  before :each do
+    @options, @config = new_option
+    MSpecOptions.stub!(:new).and_return(@options)
+    @script = MSpecMain.new
+    @script.stub!(:config).and_return(@config)
+  end
+
+  it "is enabled by #options" do
+    @options.stub!(:on)
+    @options.should_receive(:on).with("--warnings", an_instance_of(String))
+    @script.options
+  end
+
+  it "sets flags to -w" do
+    @config[:flags] = []
+    @script.options ["--warnings"]
+    @config[:flags].should include("-w")
+  end
+
+  it "set OUTPUT_WARNINGS = '1' in the environment" do
+    ENV['OUTPUT_WARNINGS'] = '0'
+    @script.options ["--warnings"]
+    ENV['OUTPUT_WARNINGS'].should == '1'
+  end
+end
+
+describe "The -j, --multi option" do
+  before :each do
+    @options, @config = new_option
+    MSpecOptions.stub!(:new).and_return(@options)
+    @script = MSpecMain.new
+    @script.stub!(:config).and_return(@config)
+  end
+
+  it "is enabled by #options" do
+    @options.stub!(:on)
+    @options.should_receive(:on).with("-j", "--multi", an_instance_of(String))
+    @script.options
+  end
+
+  it "sets the multiple process option" do
+    ["-j", "--multi"].each do |opt|
+      @config[:multi] = nil
+      @script.options [opt]
+      @config[:multi].should == true
+    end
+  end
+
+  it "sets the formatter to YamlFormatter" do
+    ["-j", "--multi"].each do |opt|
+      @config[:options] = []
+      @script.options [opt]
+      @config[:options].should include("-fy")
+    end
+  end
+end
+
+describe "The -h, --help option" do
+  before :each do
+    @options, @config = new_option
+    MSpecOptions.stub!(:new).and_return(@options)
+    @script = MSpecMain.new
+    @script.stub!(:config).and_return(@config)
+  end
+
+  it "is enabled by #options" do
+    @options.stub!(:on)
+    @options.should_receive(:on).with("-h", "--help", an_instance_of(String))
+    @script.options
+  end
+
+  it "passes the option to the subscript" do
+    ["-h", "--help"].each do |opt|
+      @config[:options] = []
+      @script.options ["ci", opt]
+      @config[:options].sort.should == ["-h"]
+    end
+  end
+
+  it "prints help and exits" do
+    @script.should_receive(:puts).twice
+    @script.should_receive(:exit).twice
+    ["-h", "--help"].each do |opt|
+      @script.options [opt]
+    end
+  end
+end
+
+describe "The -v, --version option" do
+  before :each do
+    @options, @config = new_option
+    MSpecOptions.stub!(:new).and_return(@options)
+    @script = MSpecMain.new
+    @script.stub!(:config).and_return(@config)
+  end
+
+  it "is enabled by #options" do
+    @options.stub!(:on)
+    @options.should_receive(:on).with("-v", "--version", an_instance_of(String))
+    @script.options
+  end
+
+  it "passes the option to the subscripts" do
+    ["-v", "--version"].each do |opt|
+      @config[:options] = []
+      @script.options ["ci", opt]
+      @config[:options].sort.should == ["-v"]
+    end
+  end
+
+  it "prints the version and exits if no subscript is invoked" do
+    @config[:command] = nil
+    File.stub!(:basename).and_return("mspec")
+    @script.should_receive(:puts).twice.with("mspec #{MSpec::VERSION}")
+    @script.should_receive(:exit).twice
+    ["-v", "--version"].each do |opt|
+      @script.options [opt]
+    end
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/commands/mspec_tag_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/commands/mspec_tag_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/commands/mspec_tag_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,410 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/runner/mspec'
+require 'mspec/commands/mspec-tag'
+require 'mspec/runner/actions/tag'
+require 'mspec/runner/actions/taglist'
+require 'mspec/runner/actions/tagpurge'
+
+describe MSpecTag, ".new" do
+  before :each do
+    @script = MSpecTag.new
+  end
+
+  it "sets config[:ltags] to an empty list" do
+    @script.config[:ltags].should == []
+  end
+
+  it "sets config[:tagger] to :add" do
+    @script.config[:tagger] = :add
+  end
+
+  it "sets config[:tag] to 'fails:'" do
+    @script.config[:tag] = 'fails:'
+  end
+
+  it "sets config[:outcome] to :fail" do
+    @script.config[:outcome] = :fail
+  end
+end
+
+describe MSpecTag, "#options" do
+  before :each do
+    @stdout, $stdout = $stdout, IOStub.new
+
+    @argv = ["a", "b"]
+    @options, @config = new_option
+    MSpecOptions.stub!(:new).and_return(@options)
+
+    @script = MSpecTag.new
+    @script.stub!(:config).and_return(@config)
+  end
+
+  after :each do
+    $stdout = @stdout
+  end
+
+  it "enables the filter options" do
+    @options.should_receive(:filters)
+    @script.options @argv
+  end
+
+  it "enables the configure option" do
+    @options.should_receive(:configure)
+    @script.options @argv
+  end
+
+  it "provides a custom action (block) to the config option" do
+    @script.should_receive(:load).with("cfg.mspec")
+    @script.options ["-B", "cfg.mspec", "a"]
+  end
+
+  it "enables the name option" do
+    @options.should_receive(:name)
+    @script.options @argv
+  end
+
+  it "enables the dry run option" do
+    @options.should_receive(:pretend)
+    @script.options @argv
+  end
+
+  it "enables the unguarded option" do
+    @options.should_receive(:unguarded)
+    @script.options @argv
+  end
+
+  it "enables the interrupt single specs option" do
+    @options.should_receive(:interrupt)
+    @script.options @argv
+  end
+
+  it "enables the formatter options" do
+    @options.should_receive(:formatters)
+    @script.options @argv
+  end
+
+  it "enables the verbose option" do
+    @options.should_receive(:verbose)
+    @script.options @argv
+  end
+
+  it "enables the version option" do
+    @options.should_receive(:version)
+    @script.options @argv
+  end
+
+  it "enables the help option" do
+    @options.should_receive(:help)
+    @script.options @argv
+  end
+
+  it "calls #custom_options" do
+    @script.should_receive(:custom_options).with(@options)
+    @script.options @argv
+  end
+
+  it "exits if there are no files to process" do
+    @options.should_receive(:parse).and_return([])
+    @script.should_receive(:exit)
+    @script.options
+    $stdout.should =~ /No files specified/
+  end
+end
+
+describe MSpecTag, "options" do
+  before :each do
+    @options, @config = new_option
+    MSpecOptions.stub!(:new).and_return(@options)
+    @script = MSpecTag.new
+    @script.stub!(:config).and_return(@config)
+  end
+
+  describe "-N, --add TAG" do
+    it "is enabled with #options" do
+      @options.stub!(:on)
+      @options.should_receive(:on).with("-N", "--add", "TAG", an_instance_of(String))
+      @script.options ["file.rb"]
+    end
+
+    it "sets the mode to :add and sets the tag to TAG" do
+      ["-N", "--add"].each do |opt|
+        @config[:tagger] = nil
+        @config[:tag] = nil
+        @script.options [opt, "taggit", "file.rb"]
+        @config[:tagger].should == :add
+        @config[:tag].should == "taggit:"
+      end
+    end
+  end
+
+  describe "-R, --del TAG" do
+    it "is enabled with #options" do
+      @options.stub!(:on)
+      @options.should_receive(:on).with("-R", "--del", "TAG",
+          an_instance_of(String))
+      @script.options ["file.rb"]
+    end
+
+    it "it sets the mode to :del, the tag to TAG, and the outcome to :pass" do
+      ["-R", "--del"].each do |opt|
+        @config[:tagger] = nil
+        @config[:tag] = nil
+        @config[:outcome] = nil
+        @script.options [opt, "taggit", "file.rb"]
+        @config[:tagger].should == :del
+        @config[:tag].should == "taggit:"
+        @config[:outcome].should == :pass
+      end
+    end
+  end
+
+  describe "-Q, --pass" do
+    it "is enabled with #options" do
+      @options.stub!(:on)
+      @options.should_receive(:on).with("-Q", "--pass", an_instance_of(String))
+      @script.options ["file.rb"]
+    end
+
+    it "sets the outcome to :pass" do
+      ["-Q", "--pass"].each do |opt|
+        @config[:outcome] = nil
+        @script.options [opt, "file.rb"]
+        @config[:outcome].should == :pass
+      end
+    end
+  end
+
+  describe "-F, --fail" do
+    it "is enabled with #options" do
+      @options.stub!(:on)
+      @options.should_receive(:on).with("-F", "--fail", an_instance_of(String))
+      @script.options ["file.rb"]
+    end
+
+    it "sets the outcome to :fail" do
+      ["-F", "--fail"].each do |opt|
+        @config[:outcome] = nil
+        @script.options [opt, "file.rb"]
+        @config[:outcome].should == :fail
+      end
+    end
+  end
+
+  describe "-L, --all" do
+    it "is enabled with #options" do
+      @options.stub!(:on)
+      @options.should_receive(:on).with("-L", "--all", an_instance_of(String))
+      @script.options ["file.rb"]
+    end
+
+    it "sets the outcome to :all" do
+      ["-L", "--all"].each do |opt|
+        @config[:outcome] = nil
+        @script.options [opt, "file.rb"]
+        @config[:outcome].should == :all
+      end
+    end
+  end
+
+  describe "--list TAG" do
+    it "is enabled with #options" do
+      @options.stub!(:on)
+      @options.should_receive(:on).with("--list", "TAG", an_instance_of(String))
+      @script.options ["file.rb"]
+    end
+
+    it "sets the mode to :list" do
+      @config[:tagger] = nil
+      @script.options ["--list", "TAG", "file.rb"]
+      @config[:tagger].should == :list
+    end
+
+    it "sets ltags to include TAG" do
+      @config[:tag] = nil
+      @script.options ["--list", "TAG", "file.rb"]
+      @config[:ltags].should == ["TAG"]
+    end
+  end
+
+  describe "--list-all" do
+    it "is enabled with #options" do
+      @options.stub!(:on)
+      @options.should_receive(:on).with("--list-all", an_instance_of(String))
+      @script.options ["file.rb"]
+    end
+
+    it "sets the mode to :list_all" do
+      @config[:tagger] = nil
+      @script.options ["--list-all", "file.rb"]
+      @config[:tagger].should == :list_all
+    end
+  end
+
+  describe "--purge" do
+    it "is enabled with #options" do
+      @options.stub!(:on)
+      @options.should_receive(:on).with("--purge", an_instance_of(String))
+      @script.options ["file.rb"]
+    end
+
+    it "sets the mode to :purge" do
+      @config[:tagger] = nil
+      @script.options ["--purge", "file.rb"]
+      @config[:tagger].should == :purge
+    end
+  end
+end
+
+describe MSpecTag, "#run" do
+  before :each do
+    MSpec.stub!(:process)
+
+    options = mock("MSpecOptions", :null_object => true)
+    options.stub!(:parse).and_return(["one", "two"])
+    MSpecOptions.stub!(:new).and_return(options)
+
+    @config = { }
+    @script = MSpecTag.new
+    @script.stub!(:exit)
+    @script.stub!(:config).and_return(@config)
+    @script.stub!(:files).and_return(["one", "two"])
+    @script.options
+  end
+
+  it "registers the tags patterns" do
+    @config[:tags_patterns] = [/spec/, "tags"]
+    MSpec.should_receive(:register_tags_patterns).with([/spec/, "tags"])
+    @script.run
+  end
+
+  it "registers the files to process" do
+    MSpec.should_receive(:register_files).with(["one", "two"])
+    @script.run
+  end
+
+  it "processes the files" do
+    MSpec.should_receive(:process)
+    @script.run
+  end
+
+  it "exits with the exit code registered with MSpec" do
+    MSpec.stub!(:exit_code).and_return(7)
+    @script.should_receive(:exit).with(7)
+    @script.run
+  end
+end
+
+describe MSpecTag, "#register" do
+  before :each do
+    @script = MSpecTag.new
+    @config = @script.config
+    @config[:tag] = "fake:"
+    @config[:atags] = []
+    @config[:astrings] = []
+    @config[:ltags] = ["fails", "unstable"]
+
+    @script.stub!(:files).and_return([])
+    @script.options "fake"
+
+    @t = mock("TagAction")
+    @t.stub!(:register)
+
+    @tl = mock("TagListAction")
+    @tl.stub!(:register)
+  end
+
+  it "creates a TagAction if config[:tagger] is :add" do
+    TagAction.should_receive(:new).with(:add, :fail, "fake", nil, [], []).and_return(@t)
+    @script.register
+  end
+
+  it "creates a TagAction if config[:tagger] is :del" do
+    @config[:tagger] = :del
+    @config[:outcome] = :pass
+    TagAction.should_receive(:new).with(:del, :pass, "fake", nil, [], []).and_return(@t)
+    @script.register
+  end
+
+  it "calls #register on the TagAction instance" do
+    TagAction.should_receive(:new).and_return(@t)
+    @t.should_receive(:register)
+    @script.register
+  end
+
+  it "raises an ArgumentError if no recognized action is given" do
+    @config[:tagger] = :totally_whack
+    lambda { @script.register }.should raise_error(ArgumentError)
+  end
+
+  describe "when config[:tagger] is :list" do
+    before :each do
+      @config[:tagger] = :list
+    end
+
+    it "creates a TagListAction" do
+      TagListAction.should_receive(:new).with(@config[:ltags]).and_return(@tl)
+      @tl.should_receive(:register)
+      @script.register
+    end
+
+    it "registers MSpec pretend mode" do
+      MSpec.should_receive(:register_mode).with(:pretend)
+      @script.register
+    end
+
+    it "sets config[:formatter] to false" do
+      @script.register
+      @config[:formatter].should be_false
+    end
+  end
+
+  describe "when config[:tagger] is :list_all" do
+    before :each do
+      @config[:tagger] = :list_all
+    end
+
+    it "creates a TagListAction" do
+      TagListAction.should_receive(:new).with(nil).and_return(@tl)
+      @tl.should_receive(:register)
+      @script.register
+    end
+
+    it "registers MSpec pretend mode" do
+      MSpec.should_receive(:register_mode).with(:pretend)
+      @script.register
+    end
+
+    it "sets config[:formatter] to false" do
+      @script.register
+      @config[:formatter].should be_false
+    end
+  end
+
+  describe "when config[:tagger] is :purge" do
+    before :each do
+      MSpec.stub!(:register_mode)
+      @config[:tagger] = :purge
+    end
+
+    it "creates a TagPurgeAction" do
+      TagPurgeAction.should_receive(:new).and_return(@tl)
+      @tl.should_receive(:register)
+      @script.register
+    end
+
+    it "registers MSpec in pretend mode" do
+      MSpec.should_receive(:register_mode).with(:pretend)
+      @script.register
+    end
+
+    it "registers MSpec in unguarded mode" do
+      MSpec.should_receive(:register_mode).with(:unguarded)
+      @script.register
+    end
+
+    it "sets config[:formatter] to false" do
+      @script.register
+      @config[:formatter].should be_false
+    end
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/expectations/expectations_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/expectations/expectations_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/expectations/expectations_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,29 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/expectations/expectations'
+
+describe ExpectationNotMetError do
+  it "is a subclass of StandardError" do
+    ExpectationNotMetError.ancestors.should include(StandardError)
+  end
+end
+
+describe ExpectationNotFoundError do
+  it "is a subclass of StandardError" do
+    ExpectationNotFoundError.ancestors.should include(StandardError)
+  end
+end
+
+describe ExpectationNotFoundError, "#message" do
+  it "returns 'No behavior expectation was found in the example'" do
+    m = ExpectationNotFoundError.new.message
+    m.should == "No behavior expectation was found in the example"
+  end
+end
+
+describe Expectation, "#fail_with" do
+  it "raises an ExpectationNotMetError" do
+    lambda {
+      Expectation.fail_with "expected this", "to equal that"
+    }.should raise_error(ExpectationNotMetError, "expected this to equal that")
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/expectations/should_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/expectations/should_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/expectations/should_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,129 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers/base'
+require 'mspec/runner/mspec'
+
+class Object
+  alias_method :rspec_should,     :should
+  alias_method :rspec_should_not, :should_not
+end
+require 'mspec/expectations/should'
+class Object
+  alias_method :mspec_should,     :should
+  alias_method :mspec_should_not, :should_not
+  alias_method :should,           :rspec_should
+  alias_method :should_not,       :rspec_should_not
+end
+
+# Adapted from RSpec 1.0.8
+describe Object, "#should" do
+  before :each do
+    class Object; alias_method :should, :mspec_should; end
+
+    @state = mock("example state", :null_object => true)
+    context = mock("context state", :null_object => true)
+    context.stub!(:state).and_return(@state)
+    MSpec.stub!(:current).and_return(context)
+    MSpec.stub!(:actions)
+
+    @target = "target"
+    @matcher = mock("matcher", :null_object => true)
+  end
+
+  after :each do
+    class Object; alias_method :should, :rspec_should; end
+  end
+
+  it "accepts and interacts with a matcher" do
+    @matcher.should_receive(:matches?).with(@target).and_return(true)
+    @target.should @matcher
+  end
+
+  it "calls #failure_message when matcher #matches? returns false" do
+    @matcher.should_receive(:matches?).with(@target).and_return(false)
+    @matcher.should_receive(:failure_message).and_return(["expected", "actual"])
+    @target.should @matcher rescue nil
+  end
+
+  it "raises an ExpectationNotMetError when matcher #matches? returns false" do
+    @matcher.should_receive(:matches?).with(@target).and_return(false)
+    @matcher.should_receive(:failure_message).and_return(["expected", "actual"])
+    lambda {
+      @target.should @matcher
+    }.should raise_error(ExpectationNotMetError, "expected actual")
+  end
+
+  it "returns a PostiveOperatorMatcher instance when not passed a matcher" do
+    matcher = should
+    class Object; alias_method :should, :rspec_should; end
+    matcher.should be_instance_of(PositiveOperatorMatcher)
+  end
+
+  it "invokes the MSpec :expectation actions" do
+    MSpec.should_receive(:actions).with(:expectation, @state)
+    @target.should @matcher
+  end
+
+  it "registers that an expectation has been encountered" do
+    MSpec.should_receive(:expectation)
+    @target.should @matcher
+  end
+end
+
+describe Object, "#should_not" do
+  before :each do
+    class Object; alias_method :should,     :mspec_should; end
+    class Object; alias_method :should_not, :mspec_should_not; end
+
+    @state = mock("example state", :null_object => true)
+    context = mock("context state", :null_object => true)
+    context.stub!(:state).and_return(@state)
+    MSpec.stub!(:current).and_return(context)
+    MSpec.stub!(:actions)
+
+    @target = "target"
+    @matcher = mock("matcher", :null_object => true)
+  end
+
+  after :each do
+    class Object; alias_method :should,     :rspec_should; end
+    class Object; alias_method :should_not, :rspec_should_not; end
+  end
+
+  it "accepts and interacts with a matcher" do
+    @matcher.should_receive(:matches?).with(@target).and_return(false)
+    @target.should_not @matcher
+  end
+
+  it "calls #negative_failure_message when matcher.matches? returns true" do
+    @matcher.should_receive(:matches?).with(@target).and_return(true)
+    @matcher.should_receive(:negative_failure_message).and_return(["expected", "actual"])
+    @target.should_not @matcher rescue nil
+  end
+
+  it "raises an ExpectationNotMetError when matcher.matches? returns true" do
+    @matcher.should_receive(:matches?).with(@target).and_return(true)
+    @matcher.should_receive(:negative_failure_message).and_return(["expected", "actual"])
+    lambda {
+      @target.should_not @matcher
+    }.should raise_error(ExpectationNotMetError, "expected actual")
+  end
+
+  it "returns a NegativeOperatorMatcher instance when not passed a matcher" do
+    matcher = should_not nil
+    class Object; alias_method :should, :rspec_should; end
+    matcher.should be_instance_of(NegativeOperatorMatcher)
+  end
+
+  it "invokes the MSpec :expectation actions" do
+    MSpec.should_receive(:actions).with(:expectation, @state)
+    @matcher.should_receive(:negative_failure_message).and_return(["expected", "actual"])
+    @target.should_not @matcher rescue nil
+  end
+
+  it "registers that an expectation has been encountered" do
+    MSpec.should_receive(:expectation)
+    @matcher.should_receive(:negative_failure_message).and_return(["expected", "actual"])
+    @target.should_not @matcher rescue nil
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/guards/background_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/guards/background_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/guards/background_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,36 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/guards/background'
+
+describe Object, "#process_is_foreground" do
+  before :each do
+    MSpec.clear_modes
+    ScratchPad.clear
+
+    @guard = BackgroundGuard.new
+    BackgroundGuard.stub!(:new).and_return(@guard)
+  end
+
+  it "yields if MSpec.mode?(:background) is false" do
+    MSpec.mode?(:background).should be_false
+    process_is_foreground { ScratchPad.record :yield }
+    ScratchPad.recorded.should == :yield
+  end
+
+  it "does not yield if MSpec.mode?(:background) is true" do
+    MSpec.register_mode :background
+    process_is_foreground { ScratchPad.record :yield }
+    ScratchPad.recorded.should_not == :yield
+  end
+
+  it "sets the name of the guard to :process_is_foreground" do
+    process_is_foreground { ScratchPad.record :yield }
+    @guard.name.should == :process_is_foreground
+  end
+
+  it "calls #unregister even when an exception is raised in the guard block" do
+    @guard.should_receive(:unregister)
+    lambda do
+      process_is_foreground { raise Exception }
+    end.should raise_error(Exception)
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/guards/bug_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/guards/bug_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/guards/bug_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,140 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/utils/ruby_name'
+require 'mspec/guards/bug'
+
+describe BugGuard, "#match? when #implementation? is 'ruby'" do
+  before :all do
+    @verbose = $VERBOSE
+    $VERBOSE = nil
+  end
+
+  after :all do
+    $VERBOSE = @verbose
+  end
+
+  before :each do
+    @ruby_version = Object.const_get :RUBY_VERSION
+    @ruby_patch = Object.const_get :RUBY_PATCHLEVEL
+    @ruby_name = Object.const_get :RUBY_NAME
+
+    Object.const_set :RUBY_VERSION, '1.8.6'
+    Object.const_set :RUBY_PATCHLEVEL, 114
+    Object.const_set :RUBY_NAME, 'ruby'
+  end
+
+  after :each do
+    Object.const_set :RUBY_VERSION, @ruby_version
+    Object.const_set :RUBY_PATCHLEVEL, @ruby_patch
+    Object.const_set :RUBY_NAME, @ruby_name
+  end
+
+  it "returns false when version argument is less than RUBY_VERSION and RUBY_PATCHLEVEL" do
+    BugGuard.new("#1", "1.8.5").match?.should == false
+    BugGuard.new("#1", "1.8.6.113").match?.should == false
+  end
+
+  it "returns true when version argument is equal to RUBY_VERSION and RUBY_PATCHLEVEL" do
+    BugGuard.new("#1", "1.8.6.114").match?.should == true
+  end
+
+  it "returns true when version argument is greater than RUBY_VERSION and RUBY_PATCHLEVEL" do
+    BugGuard.new("#1", "1.8.7").match?.should == true
+    BugGuard.new("#1", "1.8.6.115").match?.should == true
+  end
+
+  it "returns true when version argument implicitly includes RUBY_VERSION and RUBY_PATCHLEVEL" do
+    BugGuard.new("#1", "1.8").match?.should == true
+    BugGuard.new("#1", "1.8.6").match?.should == true
+  end
+
+  it "returns false when MSpec.mode?(:no_ruby_bug) is true" do
+    MSpec.should_receive(:mode?).with(:no_ruby_bug).twice.and_return(:true)
+    BugGuard.new("#1", "1.8.5").match?.should == false
+    BugGuard.new("#1", "1.8").match?.should == false
+  end
+end
+
+describe BugGuard, "#match? when #implementation? is not 'ruby'" do
+  before :all do
+    @verbose = $VERBOSE
+    $VERBOSE = nil
+  end
+
+  after :all do
+    $VERBOSE = @verbose
+  end
+
+  before :each do
+    @ruby_version = Object.const_get :RUBY_VERSION
+    @ruby_patch = Object.const_get :RUBY_PATCHLEVEL
+    @ruby_name = Object.const_get :RUBY_NAME
+
+    Object.const_set :RUBY_VERSION, '1.8.6'
+    Object.const_set :RUBY_PATCHLEVEL, 114
+    Object.const_set :RUBY_NAME, 'jruby'
+  end
+
+  after :each do
+    Object.const_set :RUBY_VERSION, @ruby_version
+    Object.const_set :RUBY_PATCHLEVEL, @ruby_patch
+    Object.const_set :RUBY_NAME, @ruby_name
+  end
+
+  it "returns false when version argument is less than RUBY_VERSION and RUBY_PATCHLEVEL" do
+    BugGuard.new("#1", "1.8").match?.should == false
+    BugGuard.new("#1", "1.8.6").match?.should == false
+  end
+
+  it "returns false when version argument is equal to RUBY_VERSION and RUBY_PATCHLEVEL" do
+    BugGuard.new("#1", "1.8.6.114").match?.should == false
+  end
+
+  it "returns false when version argument is greater than RUBY_VERSION and RUBY_PATCHLEVEL" do
+    BugGuard.new("#1", "1.8.7").match?.should == false
+    BugGuard.new("#1", "1.8.6.115").match?.should == false
+  end
+
+  it "returns false when MSpec.mode?(:no_ruby_bug) is true" do
+    MSpec.should_receive(:mode?).with(:no_ruby_bug).any_number_of_times.and_return(:true)
+    BugGuard.new("#1", "1.8.6").match?.should == false
+    BugGuard.new("#1", "1.8.6.114").match?.should == false
+    BugGuard.new("#1", "1.8.6.115").match?.should == false
+  end
+end
+
+describe Object, "#ruby_bug" do
+  before :each do
+    @guard = BugGuard.new "#1234", "x.x.x.x"
+    BugGuard.stub!(:new).and_return(@guard)
+    ScratchPad.clear
+  end
+
+  it "yields when #match? returns false" do
+    @guard.stub!(:match?).and_return(false)
+    ruby_bug("#1234", "1.8.6") { ScratchPad.record :yield }
+    ScratchPad.recorded.should == :yield
+  end
+
+  it "does not yield when #match? returns true" do
+    @guard.stub!(:match?).and_return(true)
+    ruby_bug("#1234", "1.8.6") { ScratchPad.record :yield }
+    ScratchPad.recorded.should_not == :yield
+  end
+
+  it "requires a bug tracker number and a version number" do
+    lambda { ruby_bug { }          }.should raise_error(ArgumentError)
+    lambda { ruby_bug("#1234") { } }.should raise_error(ArgumentError)
+  end
+
+  it "sets the name of the guard to :ruby_bug" do
+    ruby_bug("#1234", "1.8.6") { }
+    @guard.name.should == :ruby_bug
+  end
+
+  it "calls #unregister even when an exception is raised in the guard block" do
+    @guard.should_receive(:unregister)
+    lambda do
+      ruby_bug("", "") { raise Exception }
+    end.should raise_error(Exception)
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/guards/compliance_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/guards/compliance_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/guards/compliance_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,140 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/guards/compliance'
+
+describe Object, "#compliant_on" do
+  before :all do
+    @verbose = $VERBOSE
+    $VERBOSE = nil
+    @ruby_name = Object.const_get :RUBY_NAME if Object.const_defined? :RUBY_NAME
+  end
+
+  after :all do
+    $VERBOSE = @verbose
+    if @ruby_name
+      Object.const_set :RUBY_NAME, @ruby_name
+    else
+      Object.send :remove_const, :RUBY_NAME
+    end
+  end
+
+  before :each do
+    ScratchPad.clear
+  end
+
+  it "raises an Exception when passed :ruby" do
+    Object.const_set :RUBY_NAME, "jruby"
+    lambda {
+      compliant_on(:ruby) { ScratchPad.record :yield }
+    }.should raise_error(Exception)
+    ScratchPad.recorded.should_not == :yield
+  end
+
+  it "does not yield when #standard? and #implementation? return false" do
+    Object.const_set :RUBY_NAME, "jruby"
+    compliant_on(:rubinius, :ironruby) { ScratchPad.record :yield }
+    ScratchPad.recorded.should_not == :yield
+  end
+
+  it "yields when #standard? returns true" do
+    Object.const_set :RUBY_NAME, "ruby"
+    compliant_on(:rubinius) { ScratchPad.record :yield }
+    ScratchPad.recorded.should == :yield
+  end
+
+  it "yields when #implementation? returns true" do
+    Object.const_set :RUBY_NAME, "jruby"
+    compliant_on(:jruby) { ScratchPad.record :yield }
+    ScratchPad.recorded.should == :yield
+  end
+
+  it "does not yield when #implementation? returns false" do
+    Object.const_set :RUBY_NAME, "jruby"
+    compliant_on(:rubinius) { ScratchPad.record :yield }
+    ScratchPad.recorded.should_not == :yield
+  end
+end
+
+describe Object, "#compliant_on" do
+  before :each do
+    @guard = CompliantOnGuard.new :any
+    CompliantOnGuard.stub!(:new).and_return(@guard)
+  end
+
+  it "sets the name of the guard to :compliant_on" do
+    compliant_on(:rubinius) { }
+    @guard.name.should == :compliant_on
+  end
+
+  it "calls #unregister even when an exception is raised in the guard block" do
+    @guard.should_receive(:unregister)
+    lambda do
+      compliant_on(:rubinius) { raise Exception }
+    end.should raise_error(Exception)
+  end
+end
+
+describe Object, "#not_compliant_on" do
+  before :all do
+    @verbose = $VERBOSE
+    $VERBOSE = nil
+    @ruby_name = Object.const_get :RUBY_NAME if Object.const_defined? :RUBY_NAME
+  end
+
+  after :all do
+    $VERBOSE = @verbose
+    if @ruby_name
+      Object.const_set :RUBY_NAME, @ruby_name
+    else
+      Object.send :remove_const, :RUBY_NAME
+    end
+  end
+
+  before :each do
+    ScratchPad.clear
+  end
+
+  it "raises an Exception when passed :ruby" do
+    Object.const_set :RUBY_NAME, "jruby"
+    lambda {
+      not_compliant_on(:ruby) { ScratchPad.record :yield }
+    }.should raise_error(Exception)
+    ScratchPad.recorded.should_not == :yield
+  end
+
+  it "yields when #standard? returns true" do
+    Object.const_set :RUBY_NAME, "ruby"
+    not_compliant_on(:rubinius) { ScratchPad.record :yield }
+    ScratchPad.recorded.should == :yield
+  end
+
+  it "does not yield when #implementation? returns true" do
+    Object.const_set :RUBY_NAME, "jruby "
+    not_compliant_on(:jruby) { ScratchPad.record :yield }
+    ScratchPad.recorded.should_not == :yield
+  end
+
+  it "yields when #implementation? returns false" do
+    Object.const_set :RUBY_NAME, "jruby "
+    not_compliant_on(:rubinius) { ScratchPad.record :yield }
+    ScratchPad.recorded.should == :yield
+  end
+end
+
+describe Object, "#not_compliant_on" do
+  before :each do
+    @guard = NotCompliantOnGuard.new :any
+    NotCompliantOnGuard.stub!(:new).and_return(@guard)
+  end
+
+  it "sets the name of the guard to :not_compliant_on" do
+    not_compliant_on(:rubinius) { }
+    @guard.name.should == :not_compliant_on
+  end
+
+  it "calls #unregister even when an exception is raised in the guard block" do
+    @guard.should_receive(:unregister)
+    lambda do
+      not_compliant_on(:rubinius) { raise Exception }
+    end.should raise_error(Exception)
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/guards/conflict_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/guards/conflict_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/guards/conflict_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,39 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/guards/conflict'
+
+describe Object, "#conflicts_with" do
+  before :each do
+    ScratchPad.clear
+  end
+
+  it "does not yield if Object.constants includes any of the arguments" do
+    Object.stub!(:constants).and_return(["SomeClass", "OtherClass"])
+    conflicts_with(:SomeClass, :AClass, :BClass) { ScratchPad.record :yield }
+    ScratchPad.recorded.should_not == :yield
+  end
+
+  it "yields if Object.constants does not include any of the arguments" do
+    Object.stub!(:constants).and_return(["SomeClass", "OtherClass"])
+    conflicts_with(:AClass, :BClass) { ScratchPad.record :yield }
+    ScratchPad.recorded.should == :yield
+  end
+end
+
+describe Object, "#conflicts_with" do
+  before :each do
+    @guard = ConflictsGuard.new
+    ConflictsGuard.stub!(:new).and_return(@guard)
+  end
+
+  it "sets the name of the guard to :conflicts_with" do
+    conflicts_with(:AClass, :BClass) { }
+    @guard.name.should == :conflicts_with
+  end
+
+  it "calls #unregister even when an exception is raised in the guard block" do
+    @guard.should_receive(:unregister)
+    lambda do
+      conflicts_with(:AClass, :BClass) { raise Exception }
+    end.should raise_error(Exception)
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/guards/endian_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/guards/endian_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/guards/endian_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,68 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/guards/endian'
+
+describe Object, "#big_endian" do
+  before :each do
+    @guard = BigEndianGuard.new
+    BigEndianGuard.stub!(:new).and_return(@guard)
+    ScratchPad.clear
+  end
+
+  it "yields on big-endian platforms" do
+    @guard.stub!(:pattern).and_return([1])
+    big_endian { ScratchPad.record :yield }
+    ScratchPad.recorded.should == :yield
+  end
+
+  it "does not yield on little-endian platforms" do
+    @guard.stub!(:pattern).and_return([0])
+    big_endian { ScratchPad.record :yield }
+    ScratchPad.recorded.should_not == :yield
+  end
+
+  it "sets the name of the guard to :big_endian" do
+    big_endian { }
+    @guard.name.should == :big_endian
+  end
+
+  it "calls #unregister even when an exception is raised in the guard block" do
+    @guard.stub!(:pattern).and_return([1])
+    @guard.should_receive(:unregister)
+    lambda do
+      big_endian { raise Exception }
+    end.should raise_error(Exception)
+  end
+end
+
+describe Object, "#little_endian" do
+  before :each do
+    @guard = LittleEndianGuard.new
+    LittleEndianGuard.stub!(:new).and_return(@guard)
+    ScratchPad.clear
+  end
+
+  it "yields on little-endian platforms" do
+    @guard.stub!(:pattern).and_return([0])
+    little_endian { ScratchPad.record :yield }
+    ScratchPad.recorded.should == :yield
+  end
+
+  it "does not yield on big-endian platforms" do
+    @guard.stub!(:pattern).and_return([1])
+    little_endian { ScratchPad.record :yield }
+    ScratchPad.recorded.should_not == :yield
+  end
+
+  it "sets the name of the guard to :little_endian" do
+    little_endian { }
+    @guard.name.should == :little_endian
+  end
+
+  it "calls #unregister even when an exception is raised in the guard block" do
+    @guard.stub!(:pattern).and_return([0])
+    @guard.should_receive(:unregister)
+    lambda do
+      little_endian { raise Exception }
+    end.should raise_error(Exception)
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/guards/extensions_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/guards/extensions_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/guards/extensions_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,69 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/guards/extensions'
+
+describe Object, "#extended_on" do
+  before :all do
+    @verbose = $VERBOSE
+    $VERBOSE = nil
+    @ruby_name = Object.const_get :RUBY_NAME if Object.const_defined? :RUBY_NAME
+  end
+
+  after :all do
+    $VERBOSE = @verbose
+    if @ruby_name
+      Object.const_set :RUBY_NAME, @ruby_name
+    else
+      Object.send :remove_const, :RUBY_NAME
+    end
+  end
+
+  before :each do
+    ScratchPad.clear
+  end
+
+  it "raises an Exception when passed :ruby" do
+    Object.const_set :RUBY_NAME, "jruby"
+    lambda {
+      extended_on(:ruby) { ScratchPad.record :yield }
+    }.should raise_error(Exception)
+    ScratchPad.recorded.should_not == :yield
+  end
+
+  it "does not yield when #standard? returns true" do
+    Object.const_set :RUBY_NAME, "ruby"
+    extended_on(:rubinius) { ScratchPad.record :yield }
+    ScratchPad.recorded.should_not == :yield
+  end
+
+  it "does not yield when #implementation? returns false" do
+    Object.const_set :RUBY_NAME, "jruby"
+    extended_on(:rubinius) { ScratchPad.record :yield }
+    ScratchPad.recorded.should_not == :yield
+  end
+
+  it "yields when #implementation? returns true" do
+    Object.const_set :RUBY_NAME, "rbx"
+    extended_on(:rubinius) { ScratchPad.record :yield }
+    ScratchPad.recorded.should == :yield
+  end
+end
+
+describe Object, "#extended_on" do
+  before :each do
+    @guard = ExtensionsGuard.new
+    ExtensionsGuard.stub!(:new).and_return(@guard)
+  end
+
+  it "sets the name of the guard to :extended_on" do
+    extended_on(:rubinius) { }
+    @guard.name.should == :extended_on
+  end
+
+  it "calls #unregister even when an exception is raised in the guard block" do
+    @guard.should_receive(:match?).and_return(true)
+    @guard.should_receive(:unregister)
+    lambda do
+      extended_on(:rubinius) { raise Exception }
+    end.should raise_error(Exception)
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/guards/guard_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/guards/guard_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/guards/guard_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,476 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/utils/ruby_name'
+require 'mspec/guards/guard'
+require 'rbconfig'
+
+describe SpecGuard, ".ruby_version" do
+  before :all do
+    @ruby_version = Object.const_get :RUBY_VERSION
+    @ruby_patchlevel = Object.const_get :RUBY_PATCHLEVEL
+
+    Object.const_set :RUBY_VERSION, "8.2.3"
+    Object.const_set :RUBY_PATCHLEVEL, 71
+  end
+
+  after :all do
+    Object.const_set :RUBY_VERSION, @ruby_version
+    Object.const_set :RUBY_PATCHLEVEL, @ruby_patchlevel
+  end
+
+  it "returns the version and patchlevel for :full" do
+    SpecGuard.ruby_version(:full).should == "8.2.3.71"
+  end
+
+  it "returns 0 for negative RUBY_PATCHLEVEL values" do
+    Object.const_set :RUBY_PATCHLEVEL, -1
+    SpecGuard.ruby_version(:full).should == "8.2.3.0"
+  end
+
+  it "returns major.minor.tiny for :tiny" do
+    SpecGuard.ruby_version(:tiny).should == "8.2.3"
+  end
+
+  it "returns major.minor.tiny for :teeny" do
+    SpecGuard.ruby_version(:tiny).should == "8.2.3"
+  end
+
+  it "returns major.minor for :minor" do
+    SpecGuard.ruby_version(:minor).should == "8.2"
+  end
+
+  it "defaults to :minor" do
+    SpecGuard.ruby_version.should == "8.2"
+  end
+
+  it "returns major for :major" do
+    SpecGuard.ruby_version(:major).should == "8"
+  end
+end
+
+describe SpecGuard, ".windows?" do
+  before :all do
+    @ruby_platform = Object.const_get :RUBY_PLATFORM
+  end
+
+  after :all do
+    Object.const_set :RUBY_PLATFORM, @ruby_platform
+  end
+
+  it "returns true if key is mswin32" do
+    SpecGuard.windows?("mswin32").should be_true
+  end
+
+  it "returns true if key is mingw" do
+    SpecGuard.windows?("mingw").should be_true
+  end
+
+  it "returns false for non-windows" do
+    SpecGuard.windows?("notwindows").should be_false
+  end
+
+  it "uses RUBY_PLATFORM by default" do
+    Object.const_set :RUBY_PLATFORM, "mswin32"
+    SpecGuard.windows?.should be_true
+  end
+end
+
+describe SpecGuard, "#yield?" do
+  before :each do
+    MSpec.clear_modes
+    @guard = SpecGuard.new
+  end
+
+  after :each do
+    MSpec.unregister :add, @guard
+    MSpec.clear_modes
+    SpecGuard.clear_guards
+  end
+
+  it "returns true if MSpec.mode?(:unguarded) is true" do
+    MSpec.register_mode :unguarded
+    @guard.yield?.should == true
+  end
+
+  it "returns true if MSpec.mode?(:verify) is true" do
+    MSpec.register_mode :verify
+    @guard.yield?.should == true
+  end
+
+  it "returns true if MSpec.mode?(:verify) is true regardless of invert being true" do
+    MSpec.register_mode :verify
+    @guard.yield?(true).should == true
+  end
+
+  it "returns true if MSpec.mode?(:report) is true" do
+    MSpec.register_mode :report
+    @guard.yield?.should == true
+  end
+
+  it "returns true if MSpec.mode?(:report) is true regardless of invert being true" do
+    MSpec.register_mode :report
+    @guard.yield?(true).should == true
+  end
+
+  it "returns true if MSpec.mode?(:report_on) is true and SpecGuards.guards contains the named guard" do
+    MSpec.register_mode :report_on
+    SpecGuard.guards << :guard_name
+    @guard.yield?.should == false
+    @guard.name = :guard_name
+    @guard.yield?.should == true
+  end
+
+  it "returns #match? if neither report nor verify mode are true" do
+    @guard.stub!(:match?).and_return(false)
+    @guard.yield?.should == false
+    @guard.stub!(:match?).and_return(true)
+    @guard.yield?.should == true
+  end
+
+  it "returns #match? if invert is true and neither report nor verify mode are true" do
+    @guard.stub!(:match?).and_return(false)
+    @guard.yield?(true).should == true
+    @guard.stub!(:match?).and_return(true)
+    @guard.yield?(true).should == false
+  end
+end
+
+describe SpecGuard, "#===" do
+  it "returns true" do
+    anything = mock("anything")
+    SpecGuard.new.===(anything).should == true
+  end
+end
+
+describe SpecGuard, "#implementation?" do
+  before :all do
+    @verbose = $VERBOSE
+    $VERBOSE = nil
+  end
+
+  after :all do
+    $VERBOSE = @verbose
+  end
+
+  before :each do
+    @ruby_name = Object.const_get :RUBY_NAME
+    @guard = SpecGuard.new
+  end
+
+  after :each do
+    Object.const_set :RUBY_NAME, @ruby_name
+  end
+
+  it "returns true if passed :ruby and RUBY_NAME == 'ruby'" do
+    Object.const_set :RUBY_NAME, 'ruby'
+    @guard.implementation?(:ruby).should == true
+  end
+
+  it "returns true if passed :rubinius and RUBY_NAME == 'rbx'" do
+    Object.const_set :RUBY_NAME, 'rbx'
+    @guard.implementation?(:rubinius).should == true
+  end
+
+  it "returns true if passed :jruby and RUBY_NAME == 'jruby'" do
+    Object.const_set :RUBY_NAME, 'jruby'
+    @guard.implementation?(:jruby).should == true
+  end
+
+  it "returns true if passed :ironruby and RUBY_NAME == 'ironruby'" do
+    Object.const_set :RUBY_NAME, 'ironruby'
+    @guard.implementation?(:ironruby).should == true
+  end
+
+  it "returns true if passed :ruby and RUBY_NAME matches /^ruby/" do
+    Object.const_set :RUBY_NAME, 'ruby'
+    @guard.implementation?(:ruby).should == true
+
+    Object.const_set :RUBY_NAME, 'ruby1.8'
+    @guard.implementation?(:ruby).should == true
+
+    Object.const_set :RUBY_NAME, 'ruby1.9'
+    @guard.implementation?(:ruby).should == true
+  end
+
+  it "returns false when passed an unrecognized name" do
+    Object.const_set :RUBY_NAME, 'ruby'
+    @guard.implementation?(:python).should == false
+  end
+end
+
+describe SpecGuard, "#standard?" do
+  before :each do
+    @guard = SpecGuard.new
+  end
+
+  it "returns true if #implementation? returns true" do
+    @guard.should_receive(:implementation?).with(:ruby).and_return(true)
+    @guard.standard?.should be_true
+  end
+
+  it "returns false if #implementation? returns false" do
+    @guard.should_receive(:implementation?).with(:ruby).and_return(false)
+    @guard.standard?.should be_false
+  end
+end
+
+describe SpecGuard, "#platform?" do
+  before :all do
+    @verbose = $VERBOSE
+    $VERBOSE = nil
+  end
+
+  after :all do
+    $VERBOSE = @verbose
+  end
+
+  before :each do
+    @ruby_platform = Object.const_get :RUBY_PLATFORM
+    Object.const_set :RUBY_PLATFORM, 'solarce'
+    @guard = SpecGuard.new
+  end
+
+  after :each do
+    Object.const_set :RUBY_PLATFORM, @ruby_platform
+  end
+
+  it "returns false when arg does not match RUBY_PLATFORM" do
+    @guard.platform?(:ruby).should == false
+  end
+
+  it "returns false when no arg matches RUBY_PLATFORM" do
+    @guard.platform?(:ruby, :jruby, :rubinius).should == false
+  end
+
+  it "returns true when arg matches RUBY_PLATFORM" do
+    @guard.platform?(:solarce).should == true
+  end
+
+  it "returns true when any arg matches RUBY_PLATFORM" do
+    @guard.platform?(:ruby, :jruby, :solarce, :rubinius).should == true
+  end
+
+  it "returns true when arg is :windows and RUBY_PLATFORM contains 'mswin'" do
+    Object.const_set :RUBY_PLATFORM, 'i386-mswin32'
+    @guard.platform?(:windows).should == true
+  end
+
+  it "returns true when arg is :windows and RUBY_PLATFORM contains 'mingw'" do
+    Object.const_set :RUBY_PLATFORM, 'i386-mingw32'
+    @guard.platform?(:windows).should == true
+  end
+
+  it "returns false when arg is not :windows and Config::CONFIG['host_os'] contains 'mswin'" do
+    Object.const_set :RUBY_PLATFORM, 'i386-mswin32'
+    @guard.platform?(:linux).should == false
+  end
+
+  it "returns false when arg is not :windows and Config::CONFIG['host_os'] contains 'mingw'" do
+    Object.const_set :RUBY_PLATFORM, 'i386-mingw32'
+    @guard.platform?(:linux).should == false
+  end
+end
+
+describe SpecGuard, "#platform? on JRuby" do
+  before :all do
+    @verbose = $VERBOSE
+    $VERBOSE = nil
+  end
+
+  after :all do
+    $VERBOSE = @verbose
+  end
+
+  before :each do
+    @ruby_platform = Object.const_get :RUBY_PLATFORM
+    Object.const_set :RUBY_PLATFORM, 'java'
+    @guard = SpecGuard.new
+  end
+
+  after :each do
+    Object.const_set :RUBY_PLATFORM, @ruby_platform
+  end
+
+  it "returns true when arg is :java and RUBY_PLATFORM contains 'java'" do
+    @guard.platform?(:java).should == true
+  end
+
+  it "returns true when arg is :windows and RUBY_PLATFORM contains 'java' and os?(:windows) is true" do
+    Config::CONFIG.stub!(:[]).and_return('mswin32')
+    @guard.platform?(:windows).should == true
+  end
+
+  it "returns true when RUBY_PLATFORM contains 'java' and os?(argument) is true" do
+    Config::CONFIG.stub!(:[]).and_return('amiga')
+    @guard.platform?(:amiga).should == true
+  end
+end
+
+describe SpecGuard, "#wordsize?" do
+  before :each do
+    @guard = SpecGuard.new
+  end
+
+  it "returns true when arg is 32 and 1.size is 4" do
+    @guard.wordsize?(32).should == (1.size == 4)
+  end
+
+  it "returns true when arg is 64 and 1.size is 8" do
+    @guard.wordsize?(64).should == (1.size == 8)
+  end
+end
+
+describe SpecGuard, "#os?" do
+  before :each do
+    @guard = SpecGuard.new
+    Config::CONFIG.stub!(:[]).and_return('unreal')
+  end
+
+  it "returns true if argument matches Config::CONFIG['host_os']" do
+    @guard.os?(:unreal).should == true
+  end
+
+  it "returns true if any argument matches Config::CONFIG['host_os']" do
+    @guard.os?(:bsd, :unreal, :amiga).should == true
+  end
+
+  it "returns false if no argument matches Config::CONFIG['host_os']" do
+    @guard.os?(:bsd, :netbsd, :amiga, :msdos).should == false
+  end
+
+  it "returns false if argument does not match Config::CONFIG['host_os']" do
+    @guard.os?(:amiga).should == false
+  end
+
+  it "returns true when arg is :windows and Config::CONFIG['host_os'] contains 'mswin'" do
+    Config::CONFIG.stub!(:[]).and_return('i386-mswin32')
+    @guard.os?(:windows).should == true
+  end
+
+  it "returns true when arg is :windows and Config::CONFIG['host_os'] contains 'mingw'" do
+    Config::CONFIG.stub!(:[]).and_return('i386-mingw32')
+    @guard.os?(:windows).should == true
+  end
+
+  it "returns false when arg is not :windows and Config::CONFIG['host_os'] contains 'mswin'" do
+    Config::CONFIG.stub!(:[]).and_return('i386-mingw32')
+    @guard.os?(:linux).should == false
+  end
+
+  it "returns false when arg is not :windows and Config::CONFIG['host_os'] contains 'mingw'" do
+    Config::CONFIG.stub!(:[]).and_return('i386-mingw32')
+    @guard.os?(:linux).should == false
+  end
+end
+
+describe SpecGuard, "windows?" do
+  before :each do
+    @guard = SpecGuard.new
+  end
+
+  it "returns false if not passed :windows" do
+    @guard.windows?(:linux, 'mswin32').should == false
+    @guard.windows?(:linux, 'i386-mingw32').should == false
+  end
+
+  it "returns true if passed :windows and the key matches 'mswin' or 'mingw'" do
+    @guard.windows?(:windows, 'mswin32').should == true
+    @guard.windows?(:windows, 'i386-mingw32').should == true
+  end
+
+  it "returns false if passed :windows and the key matches neither 'mswin' nor 'mingw'" do
+    @guard.windows?(:windows, 'darwin9.0').should == false
+    @guard.windows?(:windows, 'linux').should == false
+  end
+end
+
+describe SpecGuard, "#match?" do
+  before :each do
+    @guard = SpecGuard.new
+    SpecGuard.stub!(:new).and_return(@guard)
+  end
+
+  it "returns true if #platform? or #implementation? return true" do
+    @guard.stub!(:implementation?).and_return(true)
+    @guard.stub!(:platform?).and_return(false)
+    @guard.match?.should == true
+
+    @guard.stub!(:implementation?).and_return(false)
+    @guard.stub!(:platform?).and_return(true)
+    @guard.match?.should == true
+  end
+
+  it "returns false if #platform? and #implementation? return false" do
+    @guard.stub!(:implementation?).and_return(false)
+    @guard.stub!(:platform?).and_return(false)
+    @guard.match?.should == false
+  end
+end
+
+describe SpecGuard, "#unregister" do
+  before :each do
+    MSpec.stub!(:unregister)
+    @guard = SpecGuard.new
+  end
+
+  it "unregisters from MSpec :add actions" do
+    MSpec.should_receive(:unregister).with(:add, @guard)
+    @guard.unregister
+  end
+end
+
+describe SpecGuard, "#record" do
+  after :each do
+    SpecGuard.clear
+  end
+
+  it "saves the name of the guarded spec under the name of the guard" do
+    guard = SpecGuard.new "a", "1.8"..."1.9"
+    guard.name = :named_guard
+    guard.record "SomeClass#action returns true"
+    SpecGuard.report.should == {
+      'named_guard a, 1.8...1.9' => ["SomeClass#action returns true"]
+    }
+  end
+end
+
+describe SpecGuard, ".guards" do
+  it "returns an Array" do
+    SpecGuard.guards.should be_kind_of(Array)
+  end
+end
+
+describe SpecGuard, ".clear_guards" do
+  it "resets the array to empty" do
+    SpecGuard.guards << :guard
+    SpecGuard.guards.should == [:guard]
+    SpecGuard.clear_guards
+    SpecGuard.guards.should == []
+  end
+end
+
+describe SpecGuard, ".finish" do
+  before :each do
+    $stdout = @out = IOStub.new
+  end
+
+  after :each do
+    $stdout = STDOUT
+    SpecGuard.clear
+  end
+
+  it "prints the descriptions of the guarded specs" do
+    guard = SpecGuard.new "a", "1.8"..."1.9"
+    guard.name = :named_guard
+    guard.record "SomeClass#action returns true"
+    guard.record "SomeClass#reverse returns false"
+    SpecGuard.finish
+    $stdout.should == %[
+
+2 specs omitted by guard: named_guard a, 1.8...1.9:
+
+SomeClass#action returns true
+SomeClass#reverse returns false
+
+]
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/guards/noncompliance_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/guards/noncompliance_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/guards/noncompliance_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,69 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/guards/noncompliance'
+
+describe Object, "#deviates_on" do
+  before :all do
+    @verbose = $VERBOSE
+    $VERBOSE = nil
+    @ruby_name = Object.const_get :RUBY_NAME if Object.const_defined? :RUBY_NAME
+  end
+
+  after :all do
+    $VERBOSE = @verbose
+    if @ruby_name
+      Object.const_set :RUBY_NAME, @ruby_name
+    else
+      Object.send :remove_const, :RUBY_NAME
+    end
+  end
+
+  before :each do
+    ScratchPad.clear
+  end
+
+  it "raises an Exception when passed :ruby" do
+    Object.const_set :RUBY_NAME, "jruby"
+    lambda {
+      deviates_on(:ruby) { ScratchPad.record :yield }
+    }.should raise_error(Exception)
+    ScratchPad.recorded.should_not == :yield
+  end
+
+  it "does not yield when #standard? returns true" do
+    Object.const_set :RUBY_NAME, "ruby"
+    deviates_on(:rubinius) { ScratchPad.record :yield }
+    ScratchPad.recorded.should_not == :yield
+  end
+
+  it "does not yield when #implementation? returns false" do
+    Object.const_set :RUBY_NAME, "jruby"
+    deviates_on(:rubinius) { ScratchPad.record :yield }
+    ScratchPad.recorded.should_not == :yield
+  end
+
+  it "yields when #implementation? returns true" do
+    Object.const_set :RUBY_NAME, "jruby"
+    deviates_on(:jruby) { ScratchPad.record :yield }
+    ScratchPad.recorded.should == :yield
+  end
+end
+
+describe Object, "#deviates_on" do
+  before :each do
+    @guard = NonComplianceGuard.new
+    NonComplianceGuard.stub!(:new).and_return(@guard)
+  end
+
+  it "sets the name of the guard to :deviates_on" do
+    deviates_on(:jruby) { }
+    @guard.name.should == :deviates_on
+  end
+
+  it "calls #unregister even when an exception is raised in the guard block" do
+    @guard.should_receive(:match?).and_return(true)
+    @guard.should_receive(:unregister)
+    lambda do
+      deviates_on(:rubinius) { raise Exception }
+    end.should raise_error(Exception)
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/guards/platform_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/guards/platform_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/guards/platform_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,110 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/guards/platform'
+
+describe Object, "#platform_is" do
+  before :each do
+    @guard = PlatformGuard.new :dummy
+    PlatformGuard.stub!(:new).and_return(@guard)
+    ScratchPad.clear
+  end
+
+  it "does not yield when #platform? returns false" do
+    @guard.stub!(:platform?).and_return(false)
+    platform_is(:ruby) { ScratchPad.record :yield }
+    ScratchPad.recorded.should_not == :yield
+  end
+
+  it "yields when #platform? returns true" do
+    @guard.stub!(:platform?).and_return(true)
+    platform_is(:solarce) { ScratchPad.record :yield }
+    ScratchPad.recorded.should == :yield
+  end
+
+  it "sets the name of the guard to :platform_is" do
+    platform_is(:solarce) { }
+    @guard.name.should == :platform_is
+  end
+
+  it "calls #unregister even when an exception is raised in the guard block" do
+    @guard.should_receive(:match?).and_return(true)
+    @guard.should_receive(:unregister)
+    lambda do
+      platform_is(:solarce) { raise Exception }
+    end.should raise_error(Exception)
+  end
+end
+
+describe Object, "#platform_is_not" do
+  before :each do
+    @guard = PlatformGuard.new :dummy
+    PlatformGuard.stub!(:new).and_return(@guard)
+    ScratchPad.clear
+  end
+
+  it "does not yield when #platform? returns true" do
+    @guard.stub!(:platform?).and_return(true)
+    platform_is_not(:ruby) { ScratchPad.record :yield }
+    ScratchPad.recorded.should_not == :yield
+  end
+
+  it "yields when #platform? returns false" do
+    @guard.stub!(:platform?).and_return(false)
+    platform_is_not(:solarce) { ScratchPad.record :yield }
+    ScratchPad.recorded.should == :yield
+  end
+
+  it "sets the name of the guard to :platform_is_not" do
+    platform_is_not(:solarce) { }
+    @guard.name.should == :platform_is_not
+  end
+
+  it "calls #unregister even when an exception is raised in the guard block" do
+    @guard.should_receive(:match?).and_return(false)
+    @guard.should_receive(:unregister)
+    lambda do
+      platform_is_not(:solarce) { raise Exception }
+    end.should raise_error(Exception)
+  end
+end
+
+describe Object, "#platform_is :wordsize => SIZE_SPEC" do
+  before :each do
+    @guard = PlatformGuard.new :darwin, :wordsize => 32
+    @guard.stub!(:platform?).and_return(true)
+    PlatformGuard.stub!(:new).and_return(@guard)
+    ScratchPad.clear
+  end
+
+  it "yields when #wordsize? returns true" do
+    @guard.stub!(:wordsize?).and_return(true)
+    platform_is(:wordsize => 32) { ScratchPad.record :yield }
+    ScratchPad.recorded.should == :yield
+  end
+
+  it "doesn not yield when #wordsize? returns false" do
+    @guard.stub!(:wordsize?).and_return(false)
+    platform_is(:wordsize => 32) { ScratchPad.record :yield }
+    ScratchPad.recorded.should_not == :yield
+  end
+end
+
+describe Object, "#platform_is_not :wordsize => SIZE_SPEC" do
+  before :each do
+    @guard = PlatformGuard.new :darwin, :wordsize => 32
+    @guard.stub!(:platform?).and_return(true)
+    PlatformGuard.stub!(:new).and_return(@guard)
+    ScratchPad.clear
+  end
+
+  it "yields when #wordsize? returns false" do
+    @guard.stub!(:wordsize?).and_return(false)
+    platform_is_not(:wordsize => 32) { ScratchPad.record :yield }
+    ScratchPad.recorded.should == :yield
+  end
+
+  it "doesn not yield when #wordsize? returns true" do
+    @guard.stub!(:wordsize?).and_return(true)
+    platform_is_not(:wordsize => 32) { ScratchPad.record :yield }
+    ScratchPad.recorded.should_not == :yield
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/guards/quarantine_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/guards/quarantine_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/guards/quarantine_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,35 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/guards/quarantine'
+
+describe QuarantineGuard, "#match?" do
+  it "returns false" do
+    QuarantineGuard.new.match?.should == false
+  end
+end
+
+describe Object, "#quarantine!" do
+  before :each do
+    ScratchPad.clear
+
+    @guard = QuarantineGuard.new
+    QuarantineGuard.stub!(:new).and_return(@guard)
+  end
+
+  it "does not yield" do
+    quarantine! { ScratchPad.record :yield }
+    ScratchPad.recorded.should_not == :yield
+  end
+
+  it "sets the name of the guard to :quarantine!" do
+    quarantine! { }
+    @guard.name.should == :quarantine!
+  end
+
+  it "calls #unregister even when an exception is raised in the guard block" do
+    @guard.should_receive(:match?).and_return(true)
+    @guard.should_receive(:unregister)
+    lambda do
+      quarantine! { raise Exception }
+    end.should raise_error(Exception)
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/guards/runner_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/guards/runner_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/guards/runner_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,101 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/guards/runner'
+
+describe RunnerGuard, "#match?" do
+  before :all do
+    @verbose = $VERBOSE
+    $VERBOSE = nil
+  end
+
+  after :all do
+    $VERBOSE = @verbose
+  end
+
+  it "returns true when passed :mspec and ENV['MSPEC_RUNNER'] is true" do
+    ENV['MSPEC_RUNNER'] = '1'
+    RunnerGuard.new(:mspec).match?.should == true
+  end
+
+  it "returns false when passed :mspec and ENV['MSPEC_RUNNER'] is false" do
+    ENV.delete 'MSPEC_RUNNER'
+    RunnerGuard.new(:mspec).match?.should == false
+  end
+
+  it "returns true when passed :rspec and ENV['RSPEC_RUNNER'] is false but the constant Spec exists" do
+    ENV.delete 'RSPEC_RUNNER'
+    Object.const_set(:Spec, 1) unless Object.const_defined?(:Spec)
+    RunnerGuard.new(:rspec).match?.should == true
+  end
+
+  it "returns true when passed :rspec and ENV['RSPEC_RUNNER'] is true but the constant Spec does not exist" do
+    ENV['RSPEC_RUNNER'] = '1'
+    Object.should_receive(:const_defined?).with(:Spec).any_number_of_times.and_return(false)
+    RunnerGuard.new(:rspec).match?.should == true
+  end
+end
+
+describe Object, "#runner_is" do
+  before :each do
+    @guard = RunnerGuard.new
+    RunnerGuard.stub!(:new).and_return(@guard)
+    ScratchPad.clear
+  end
+
+  it "yields when #match? returns true" do
+    @guard.stub!(:match?).and_return(true)
+    runner_is(:mspec) { ScratchPad.record :yield }
+    ScratchPad.recorded.should == :yield
+  end
+
+  it "does not yield when #match? returns false" do
+    @guard.stub!(:match?).and_return(false)
+    runner_is(:mspec) { ScratchPad.record :yield }
+    ScratchPad.recorded.should_not == :yield
+  end
+
+  it "sets the name of the guard to :runner_is" do
+    runner_is(:mspec) { }
+    @guard.name.should == :runner_is
+  end
+
+  it "calls #unregister even when an exception is raised in the guard block" do
+    @guard.should_receive(:match?).and_return(true)
+    @guard.should_receive(:unregister)
+    lambda do
+      runner_is(:mspec) { raise Exception }
+    end.should raise_error(Exception)
+  end
+end
+
+describe Object, "#runner_is_not" do
+  before :each do
+    @guard = RunnerGuard.new
+    RunnerGuard.stub!(:new).and_return(@guard)
+    ScratchPad.clear
+  end
+
+  it "does not yield when #match? returns true" do
+    @guard.stub!(:match?).and_return(true)
+    runner_is_not(:mspec) { ScratchPad.record :yield }
+    ScratchPad.recorded.should_not == :yield
+  end
+
+  it "yields when #match? returns false" do
+    @guard.stub!(:match?).and_return(false)
+    runner_is_not(:mspec) { ScratchPad.record :yield }
+    ScratchPad.recorded.should == :yield
+  end
+
+  it "sets the name of the guard to :runner_is_not" do
+    runner_is_not(:mspec) { }
+    @guard.name.should == :runner_is_not
+  end
+
+  it "calls #unregister even when an exception is raised in the guard block" do
+    @guard.should_receive(:match?).and_return(false)
+    @guard.should_receive(:unregister)
+    lambda do
+      runner_is_not(:mspec) { raise Exception }
+    end.should raise_error(Exception)
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/guards/superuser_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/guards/superuser_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/guards/superuser_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,35 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/guards/superuser'
+
+describe Object, "#as_superuser" do
+  before :each do
+    @guard = SuperUserGuard.new
+    SuperUserGuard.stub!(:new).and_return(@guard)
+    ScratchPad.clear
+  end
+
+  it "does not yield when Process.euid is not 0" do
+    Process.stub!(:euid).and_return(501)
+    as_superuser { ScratchPad.record :yield }
+    ScratchPad.recorded.should_not == :yield
+  end
+
+  it "yields when Process.euid is 0" do
+    Process.stub!(:euid).and_return(0)
+    as_superuser { ScratchPad.record :yield }
+    ScratchPad.recorded.should == :yield
+  end
+
+  it "sets the name of the guard to :as_superuser" do
+    as_superuser { }
+    @guard.name.should == :as_superuser
+  end
+
+  it "calls #unregister even when an exception is raised in the guard block" do
+    @guard.should_receive(:match?).and_return(true)
+    @guard.should_receive(:unregister)
+    lambda do
+      as_superuser { raise Exception }
+    end.should raise_error(Exception)
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/guards/support_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/guards/support_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/guards/support_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,69 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/guards/support'
+
+describe Object, "#not_supported_on" do
+  before :all do
+    @verbose = $VERBOSE
+    $VERBOSE = nil
+    @ruby_name = Object.const_get :RUBY_NAME if Object.const_defined? :RUBY_NAME
+  end
+
+  after :all do
+    $VERBOSE = @verbose
+    if @ruby_name
+      Object.const_set :RUBY_NAME, @ruby_name
+    else
+      Object.send :remove_const, :RUBY_NAME
+    end
+  end
+
+  before :each do
+    ScratchPad.clear
+  end
+
+  it "raises an Exception when passed :ruby" do
+    Object.const_set :RUBY_NAME, "jruby"
+    lambda {
+      not_supported_on(:ruby) { ScratchPad.record :yield }
+    }.should raise_error(Exception)
+    ScratchPad.recorded.should_not == :yield
+  end
+
+  it "does not yield when #implementation? returns true" do
+    Object.const_set :RUBY_NAME, "jruby"
+    not_supported_on(:jruby) { ScratchPad.record :yield }
+    ScratchPad.recorded.should_not == :yield
+  end
+
+  it "yields when #standard? returns true" do
+    Object.const_set :RUBY_NAME, "ruby"
+    not_supported_on(:rubinius) { ScratchPad.record :yield }
+    ScratchPad.recorded.should == :yield
+  end
+
+  it "yields when #implementation? returns false" do
+    Object.const_set :RUBY_NAME, "jruby"
+    not_supported_on(:rubinius) { ScratchPad.record :yield }
+    ScratchPad.recorded.should == :yield
+  end
+end
+
+describe Object, "#not_supported_on" do
+  before :each do
+    @guard = SupportedGuard.new
+    SupportedGuard.stub!(:new).and_return(@guard)
+  end
+
+  it "sets the name of the guard to :not_supported_on" do
+    not_supported_on(:rubinius) { }
+    @guard.name.should == :not_supported_on
+  end
+
+  it "calls #unregister even when an exception is raised in the guard block" do
+    @guard.should_receive(:match?).and_return(true)
+    @guard.should_receive(:unregister)
+    lambda do
+      not_supported_on(:rubinius) { raise Exception }
+    end.should raise_error(Exception)
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/guards/tty_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/guards/tty_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/guards/tty_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,36 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/guards/tty'
+
+describe Object, "#with_tty" do
+  before :each do
+    ScratchPad.clear
+
+    @guard = TTYGuard.new
+    TTYGuard.stub!(:new).and_return(@guard)
+  end
+
+  it "yields if STDOUT is a TTY" do
+    STDOUT.should_receive(:tty?).and_return(true)
+    with_tty { ScratchPad.record :yield }
+    ScratchPad.recorded.should == :yield
+  end
+
+  it "does not yield if STDOUT is not a TTY" do
+    STDOUT.should_receive(:tty?).and_return(false)
+    with_tty { ScratchPad.record :yield }
+    ScratchPad.recorded.should_not == :yield
+  end
+
+  it "sets the name of the guard to :with_tty" do
+    with_tty { }
+    @guard.name.should == :with_tty
+  end
+
+  it "calls #unregister even when an exception is raised in the guard block" do
+    @guard.should_receive(:match?).and_return(true)
+    @guard.should_receive(:unregister)
+    lambda do
+      with_tty { raise Exception }
+    end.should raise_error(Exception)
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/guards/version_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/guards/version_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/guards/version_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,130 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/guards/version'
+
+# The VersionGuard specifies a version of Ruby with a String of
+# the form: v = 'major.minor.tiny.patchlevel'.
+#
+# A VersionGuard instance can be created with a single String,
+# which means any version >= each component of v.
+# Or, the guard can be created with a Range, a..b, or a...b,
+# where a, b are of the same form as v. The meaning of the Range
+# is as typically understood: a..b means v >= a and v <= b;
+# a...b means v >= a and v < b.
+
+describe VersionGuard, "#ruby_version" do
+  before :all do
+    @verbose = $VERBOSE
+    $VERBOSE = nil
+  end
+
+  after :all do
+    $VERBOSE = @verbose
+  end
+
+  before :each do
+    @ruby_version = Object.const_get :RUBY_VERSION
+    @ruby_patch = Object.const_get :RUBY_PATCHLEVEL
+
+    Object.const_set :RUBY_VERSION, '1.8.6'
+    Object.const_set :RUBY_PATCHLEVEL, 114
+
+    @guard = VersionGuard.new 'x.x.x.x'
+  end
+
+  after :each do
+    Object.const_set :RUBY_VERSION, @ruby_version
+    Object.const_set :RUBY_PATCHLEVEL, @ruby_patch
+  end
+
+  it "returns 'RUBY_VERSION.RUBY_PATCHLEVEL'" do
+    @guard.ruby_version.should == 10108060114
+  end
+end
+
+describe VersionGuard, "#match?" do
+  before :all do
+    @verbose = $VERBOSE
+    $VERBOSE = nil
+  end
+
+  after :all do
+    $VERBOSE = @verbose
+  end
+
+  before :each do
+    @ruby_version = Object.const_get :RUBY_VERSION
+    @ruby_patch = Object.const_get :RUBY_PATCHLEVEL
+
+    Object.const_set :RUBY_VERSION, '1.8.6'
+    Object.const_set :RUBY_PATCHLEVEL, 114
+  end
+
+  after :each do
+    Object.const_set :RUBY_VERSION, @ruby_version
+    Object.const_set :RUBY_PATCHLEVEL, @ruby_patch
+  end
+
+  it "returns true when the argument is equal to RUBY_VERSION and RUBY_PATCHLEVEL" do
+    VersionGuard.new('1.8.6.114').match?.should == true
+  end
+
+  it "returns true when the argument is less than RUBY_VERSION and RUBY_PATCHLEVEL" do
+    VersionGuard.new('1.8').match?.should == true
+    VersionGuard.new('1.8.5').match?.should == true
+  end
+
+  it "returns false when the argument is greater than RUBY_VERSION and RUBY_PATCHLEVEL" do
+    VersionGuard.new('1.8.7').match?.should == false
+    VersionGuard.new('1.8.7.000').match?.should == false
+    VersionGuard.new('1.8.7.10').match?.should == false
+  end
+
+  it "returns true when the argument range includes RUBY_VERSION and RUBY_PATCHLEVEL" do
+    VersionGuard.new('1.8.5'..'1.8.7.111').match?.should == true
+    VersionGuard.new('1.8'..'1.9').match?.should == true
+    VersionGuard.new('1.8'...'1.9').match?.should == true
+    VersionGuard.new('1.8'..'1.8.6.114').match?.should == true
+    VersionGuard.new('1.8'...'1.8.6.115').match?.should == true
+  end
+
+  it "returns false when the argument range does not include RUBY_VERSION and RUBY_PATCHLEVEL" do
+    VersionGuard.new('1.8.7'..'1.8.9').match?.should == false
+    VersionGuard.new('1.8.4'..'1.8.5').match?.should == false
+    VersionGuard.new('1.8.5'..'1.8.6.113').match?.should == false
+    VersionGuard.new('1.8.4'...'1.8.6').match?.should == false
+    VersionGuard.new('1.8.5'...'1.8.6.114').match?.should == false
+  end
+end
+
+describe Object, "#ruby_version_is" do
+  before :each do
+    @guard = VersionGuard.new 'x.x.x.x'
+    VersionGuard.stub!(:new).and_return(@guard)
+    ScratchPad.clear
+  end
+
+  it "yields when #match? returns true" do
+    @guard.stub!(:match?).and_return(true)
+    ruby_version_is('x.x.x.x') { ScratchPad.record :yield }
+    ScratchPad.recorded.should == :yield
+  end
+
+  it "does not yield when #match? returns false" do
+    @guard.stub!(:match?).and_return(false)
+    ruby_version_is('x.x.x.x') { ScratchPad.record :yield }
+    ScratchPad.recorded.should_not == :yield
+  end
+
+  it "sets the name of the guard to :ruby_version_is" do
+    ruby_version_is("") { }
+    @guard.name.should == :ruby_version_is
+  end
+
+  it "calls #unregister even when an exception is raised in the guard block" do
+    @guard.should_receive(:match?).and_return(true)
+    @guard.should_receive(:unregister)
+    lambda do
+      ruby_version_is("") { raise Exception }
+    end.should raise_error(Exception)
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/helpers/argv_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/helpers/argv_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/helpers/argv_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,26 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/helpers/argv'
+
+describe Object, "#argv" do
+  before :each do
+    ScratchPad.clear
+
+    @saved_argv = ARGV
+    @argv = ["a", "b"]
+  end
+
+  it "replaces and restores the value of ARGV" do
+    argv @argv
+    ARGV.should == @argv
+    argv :restore
+    ARGV.should == @saved_argv
+  end
+
+  it "yields to the block after setting ARGV" do
+    argv @argv do
+      ScratchPad.record ARGV
+    end
+    ScratchPad.recorded.should == @argv
+    ARGV.should == @saved_argv
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/helpers/bignum_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/helpers/bignum_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/helpers/bignum_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,11 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/helpers/bignum'
+
+describe Object, "#bignum_value" do
+  it "returns a value that is an instance of Bignum on any platform" do
+    bignum_value.should ==  0x8000_0000_0000_0000
+  end
+
+  it "returns the default value incremented by the argument" do
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/helpers/const_lookup_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/helpers/const_lookup_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/helpers/const_lookup_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,48 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/helpers/const_lookup'
+
+CONST = 2
+
+module ConstLookupSpecs
+  class A
+    class B
+      CONST = 1
+    end
+
+    class C; end
+
+    class D
+      def self.const_missing(const)
+        A::B::CONST
+      end
+    end
+  end
+end
+
+describe Kernel, "#const_lookup" do
+  it "returns the constant specified by 'A::B'" do
+    const_lookup("ConstLookupSpecs::A::B").should == ConstLookupSpecs::A::B
+  end
+
+  it "returns a regular constant specified without scoping" do
+    const_lookup("ConstLookupSpecs").should == ConstLookupSpecs
+  end
+
+  it "returns an explicit toplevel constant" do
+    const_lookup("::ConstLookupSpecs").should == ConstLookupSpecs
+  end
+
+  it "returns the constant from the proper scope" do
+    const_lookup("ConstLookupSpecs::A::B::CONST").should == 1
+  end
+
+  it "raises NameError if the constant is not contained within the module's scope" do
+    lambda {
+      const_lookup("ConstLookupSpecs::A::C::CONST")
+    }.should raise_error(NameError)
+  end
+
+  it "returns the value of #const_missing" do
+    const_lookup("ConstLookupSpecs::A::D::CONST").should == 1
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/helpers/environment_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/helpers/environment_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/helpers/environment_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,82 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/helpers/environment'
+
+describe "#env" do
+  before(:all) do
+    @ruby_platform = Object.const_get :RUBY_PLATFORM
+  end
+
+  after(:all) do
+    Object.const_set :RUBY_PLATFORM, @ruby_platform
+  end
+
+  it "returns a hash of variables" do
+    env.class.should == Hash
+  end
+  
+  it "calls `env` on non-Windows" do
+    Object.const_set :RUBY_PLATFORM, "notwindows"
+    should_receive(:`).with("env").and_return("one=two\nthree=four")
+    env
+  end
+
+  it "calls `cmd.exe /C set` on Windows (mswin32)" do
+    Object.const_set :RUBY_PLATFORM, "mswin32"
+    should_receive(:`).with("cmd.exe /C set").and_return("one=two\nthree=four")
+    env
+  end
+
+  it "calls `cmd.exe /C set` on Windows (mingw)" do
+    Object.const_set :RUBY_PLATFORM, "mingw"
+    should_receive(:`).with("cmd.exe /C set").and_return("one=two\nthree=four")
+    env
+  end
+
+  it "returns the current user's environment variables" do
+    Object.const_set :RUBY_PLATFORM, "notwindows"
+    should_receive(:`).with("env").and_return("one=two\nthree=four")
+    env.should == {"one" => "two", "three" => "four"}
+
+    Object.const_set :RUBY_PLATFORM, "mswin32"
+    should_receive(:`).with("cmd.exe /C set").and_return("five=six\nseven=eight")
+    env.should == {"five" => "six", "seven" => "eight"}
+  end
+end
+
+describe "#username" do
+  before(:all) do
+    @ruby_platform = Object.const_get :RUBY_PLATFORM
+  end
+
+  after(:all) do
+    Object.const_set :RUBY_PLATFORM, @ruby_platform
+  end
+
+  it "calls `cmd.exe /C ECHO %USERNAME%` on Windows (mswin32)" do
+    Object.const_set :RUBY_PLATFORM, "mswin32"
+    should_receive(:`).with("cmd.exe /C ECHO %USERNAME%").and_return("john")
+    username
+  end
+
+  it "calls `cmd.exe /C ECHO %USERNAME%` on Windows (mingw)" do
+    Object.const_set :RUBY_PLATFORM, "mingw"
+    should_receive(:`).with("cmd.exe /C ECHO %USERNAME%").and_return("john")
+    username
+  end
+
+  it "calls `env` on non-Windows" do
+    Object.const_set :RUBY_PLATFORM, "notwindows"
+    should_receive(:`).with("whoami").and_return("john")
+    username
+  end
+
+  it "returns the user's username" do
+    Object.const_set :RUBY_PLATFORM, "mswin32"
+    should_receive(:`).with("cmd.exe /C ECHO %USERNAME%").and_return("johnonwin")
+    username.should == "johnonwin"
+
+    Object.const_set :RUBY_PLATFORM, "notwindows"
+    should_receive(:`).with("whoami").and_return("john")
+    username.should == "john"
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/helpers/fixture_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/helpers/fixture_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/helpers/fixture_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,18 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/helpers/fixture'
+
+describe Object, "#fixture" do
+  before :each do
+    @dir = File.expand_path(Dir.pwd)
+  end
+
+  it "returns the expanded path to a fixture file" do
+    name = fixture("some/path/file.rb", "dir", "file.txt")
+    name.should == "#{@dir}/some/path/fixtures/dir/file.txt"
+  end
+
+  it "omits '/shared' if it is the suffix of the directory string" do
+    name = fixture("some/path/shared/file.rb", "dir", "file.txt")
+    name.should == "#{@dir}/some/path/fixtures/dir/file.txt"
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/helpers/flunk_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/helpers/flunk_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/helpers/flunk_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,19 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/helpers/flunk'
+require 'mspec/runner/mspec'
+
+describe Object, "#flunk" do
+  before :each do
+    MSpec.stub!(:actions)
+    MSpec.stub!(:current).and_return(mock("spec state", :null_object => true))
+  end
+
+  it "raises an ExpectationNotMetError unconditionally" do
+    lambda { flunk }.should raise_error(ExpectationNotMetError)
+  end
+
+  it "accepts on argument for an optional message" do
+    lambda {flunk "test"}.should raise_error(ExpectationNotMetError)
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/helpers/io_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/helpers/io_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/helpers/io_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,38 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+
+describe IOStub do
+  before :each do
+    @out = IOStub.new
+    @sep = $\
+  end
+
+  after :each do
+    $\ = @sep
+  end
+
+  it "provides a write method" do
+    @out.write "this"
+    @out.should == "this"
+  end
+
+  it "concatenates the arguments sent to write" do
+    @out.write "flim ", "flam"
+    @out.should == "flim flam"
+  end
+
+  it "provides a print method that appends the default separator" do
+    $\ = " [newline] "
+    @out.print "hello"
+    @out.print "world"
+    @out.should == "hello [newline] world [newline] "
+  end
+
+  it "provides a puts method that appends the default separator" do
+    @out.puts "hello", 1, 2, 3
+    @out.should == "hello\n1\n2\n3\n"
+  end
+
+  it "provides a flush method that does nothing and returns self" do
+    @out.flush.should == @out
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/helpers/language_version_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/helpers/language_version_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/helpers/language_version_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,29 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/helpers/language_version'
+
+describe Object, "#language_version" do
+  before :all do
+    @ruby_version = Object.const_get :RUBY_VERSION
+
+    Object.const_set :RUBY_VERSION, "8.2.3"
+
+    dir = File.dirname(File.expand_path(__FILE__))
+    @name = "#{dir}/versions/method_8.2.rb"
+  end
+
+  after :all do
+    Object.const_set :RUBY_VERSION, @ruby_version
+  end
+
+  it "loads files conditionally based on name and RUBY_VERSION if it exists" do
+    File.should_receive(:exists?).with(@name).and_return(true)
+    should_receive(:require).with(@name)
+    language_version __FILE__, "method"
+  end
+
+  it "does not load the file if it does not exist" do
+    File.should_receive(:exists?).with(@name).and_return(false)
+    should_not_receive(:require).with(@name)
+    language_version __FILE__, "method"
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/helpers/ruby_exe_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/helpers/ruby_exe_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/helpers/ruby_exe_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,160 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/helpers/ruby_exe'
+require 'rbconfig'
+
+class RubyExeSpecs
+end
+
+describe "#ruby_exe_options" do
+  before :all do
+    @verbose = $VERBOSE
+    $VERBOSE = nil
+
+    @ruby_name = Object.const_get :RUBY_NAME
+    @ruby_exe_env = ENV['RUBY_EXE']
+
+    @script = RubyExeSpecs.new
+  end
+
+  after :all do
+    Object.const_set :RUBY_NAME, @ruby_name
+    ENV['RUBY_EXE'] = @ruby_exe_env
+    $VERBOSE = @verbose
+  end
+
+  before :each do
+    @script = RubyExeSpecs.new
+  end
+
+  it "returns ENV['RUBY_EXE'] when passed :env" do
+    ENV['RUBY_EXE'] = "kowabunga"
+    @script.ruby_exe_options(:env).should == "kowabunga"
+  end
+
+  it "returns 'bin/rbx' when passed :engine and RUBY_NAME is 'rbx'" do
+    Object.const_set :RUBY_NAME, 'rbx'
+    @script.ruby_exe_options(:engine).should == 'bin/rbx'
+  end
+
+  it "returns 'bin/jruby' when passed :engine and RUBY_NAME is 'jruby'" do
+    Object.const_set :RUBY_NAME, 'jruby'
+    @script.ruby_exe_options(:engine).should == 'bin/jruby'
+  end
+
+  it "returns 'ir' when passed :engine and RUBY_NAME is 'ironruby'" do
+    Object.const_set :RUBY_NAME, 'ironruby'
+    @script.ruby_exe_options(:engine).should == 'ir'
+  end
+
+  it "returns RUBY_NAME + $(EXEEXT) when passed :name" do
+    bin = RUBY_NAME + (Config::CONFIG['EXEEXT'] || Config::CONFIG['exeext'] || '')
+    name = File.join ".", bin
+    @script.ruby_exe_options(:name).should == name
+  end
+
+  it "returns $(bindir)/$(RUBY_INSTALL_NAME) + $(EXEEXT) when passed :install_name" do
+    bin = Config::CONFIG['RUBY_INSTALL_NAME'] + (Config::CONFIG['EXEEXT'] || Config::CONFIG['exeext'] || '')
+    name = File.join Config::CONFIG['bindir'], bin
+    @script.ruby_exe_options(:install_name).should == name
+  end
+end
+
+describe "#resolve_ruby_exe" do
+  before :all do
+    @verbose = $VERBOSE
+    $VERBOSE = nil
+
+    @name = "ruby_spec_exe"
+  end
+
+  before :each do
+    @ruby_platform = Object.const_get :RUBY_PLATFORM
+
+    @script = RubyExeSpecs.new
+  end
+
+  after :each do
+    Object.const_set :RUBY_PLATFORM, @ruby_platform
+  end
+
+  after :all do
+    $VERBOSE = @verbose
+  end
+
+  it "returns the value returned by #ruby_exe_options if it exists and is executable" do
+    Object.const_set :RUBY_PLATFORM, "notwindows"
+    @script.should_receive(:ruby_exe_options).and_return(@name)
+    File.should_receive(:exists?).with(@name).and_return(true)
+    File.should_receive(:executable?).with(@name).and_return(true)
+    @script.resolve_ruby_exe.should == @name
+  end
+
+  it "returns the value returned by #ruby_exe_options if it exists on Windows platforms" do
+    Object.const_set :RUBY_PLATFORM, "mswin"
+    @script.should_receive(:ruby_exe_options).and_return(@name)
+    File.should_receive(:exists?).with(@name).and_return(true)
+    File.should_not_receive(:executable?)
+    @script.resolve_ruby_exe.should == @name
+  end
+
+  it "returns nil if no exe is found" do
+    File.should_receive(:exists?).at_least(:once).and_return(false)
+    @script.resolve_ruby_exe.should be_nil
+  end
+end
+
+describe Object, "#ruby_exe" do
+  before :all do
+    @verbose = $VERBOSE
+    $VERBOSE = nil
+
+    @ruby_flags = ENV["RUBY_FLAGS"]
+    ENV["RUBY_FLAGS"] = "-w -Q"
+
+    @ruby_exe = Object.const_get :RUBY_EXE
+    Object.const_set :RUBY_EXE, 'ruby_spec_exe'
+
+    @file = "some/ruby/file.rb"
+    @code = %(some "real" 'ruby' code)
+
+    @script = RubyExeSpecs.new
+  end
+
+  after :all do
+    Object.const_set :RUBY_EXE, @ruby_exe
+    ENV["RUBY_FLAGS"] = @ruby_flags
+    $VERBOSE = @verbose
+  end
+
+  it "executes the argument if it is a file that exists" do
+    File.should_receive(:exists?).with(@file).and_return(true)
+    @script.should_receive(:`).with("ruby_spec_exe -w -Q some/ruby/file.rb")
+    @script.ruby_exe @file
+  end
+
+  it "executes the file with options and arguments" do
+    File.should_receive(:exists?).with(@file).and_return(true)
+    @script.should_receive(:`).with(
+      "ruby_spec_exe -w -Q -w -Cdir some/ruby/file.rb < file.txt")
+    @script.ruby_exe @file, :options => "-w -Cdir", :args => "< file.txt"
+  end
+
+  it "executes the argument with -e" do
+    File.should_receive(:exists?).with(@code).and_return(false)
+    @script.should_receive(:`).with(
+      %(ruby_spec_exe -w -Q -e "some \\"real\\" 'ruby' code"))
+    @script.ruby_exe @code
+  end
+
+  it "executes the code with options and arguments" do
+    File.should_receive(:exists?).with(@code).and_return(false)
+    @script.should_receive(:`).with(
+      %(ruby_spec_exe -w -Q -W0 -Cdir -e "some \\"real\\" 'ruby' code" < file.txt))
+    @script.ruby_exe @code, :options => "-W0 -Cdir", :args => "< file.txt"
+  end
+
+  it "executes with options and arguments but without code or file" do
+    @script.should_receive(:`).with("ruby_spec_exe -w -Q -c > file.txt")
+    @script.ruby_exe nil, :options => "-c", :args => "> file.txt"
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/helpers/scratch_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/helpers/scratch_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/helpers/scratch_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,22 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+
+describe ScratchPad do
+  it "records an object and returns a previously recorded object" do
+    ScratchPad.record :this
+    ScratchPad.recorded.should == :this
+  end
+
+  it "clears the recorded object" do
+    ScratchPad.record :that
+    ScratchPad.recorded.should == :that
+    ScratchPad.clear
+    ScratchPad.recorded.should == nil
+  end
+
+  it "provides a convenience shortcut to append to a previously recorded object" do
+    ScratchPad.record []
+    ScratchPad << :new
+    ScratchPad << :another
+    ScratchPad.recorded.should == [:new, :another]
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/helpers/tmp_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/helpers/tmp_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/helpers/tmp_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,72 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/helpers/tmp'
+
+describe Object, "#tmp" do
+  before :each do
+    File.stub!(:directory?).and_return(false)
+    File.stub!(:symlink?).and_return(false)
+    ENV.stub!(:[]).and_return(nil)
+  end
+
+  it "returns /tmp/<name> if /tmp is a writable directory" do
+    dir = "/tmp"
+    File.should_receive(:directory?).with(dir).and_return(true)
+    File.should_receive(:writable?).with(dir).and_return(true)
+    File.should_receive(:expand_path).with(dir).and_return(dir)
+    tmp("test.txt").should == dir + "/test.txt"
+  end
+
+  it "returns /var/tmp/<name> if /var/tmp is a writable directory" do
+    dir = "/var/tmp"
+    File.should_receive(:directory?).with(dir).and_return(true)
+    File.should_receive(:writable?).with(dir).and_return(true)
+    File.should_receive(:expand_path).with(dir).and_return(dir)
+    tmp("test.txt").should == dir + "/test.txt"
+  end
+
+  it "returns ENV['TMPDIR']/<name> if ENV['TMPDIR'] is a writable directory" do
+    dir = "/tmpdir"
+    ENV.should_receive(:[]).with("TMPDIR").and_return(dir)
+    File.should_receive(:directory?).with(dir).and_return(true)
+    File.should_receive(:writable?).with(dir).and_return(true)
+    File.should_receive(:expand_path).with(dir).and_return(dir)
+    tmp("test.txt").should == dir + "/test.txt"
+  end
+
+  it "returns ENV['TMP']/<name> if ENV['TMP'] is a writable directory" do
+    dir = "/tmp/tmp"
+    ENV.should_receive(:[]).with("TMP").and_return(dir)
+    File.should_receive(:directory?).with(dir).and_return(true)
+    File.should_receive(:writable?).with(dir).and_return(true)
+    File.should_receive(:expand_path).with(dir).and_return(dir)
+    tmp("test.txt").should == dir + "/test.txt"
+  end
+
+  it "returns ENV['TEMP']/<name> if ENV['TEMP'] is a writable directory" do
+    dir = "/tmp/temp"
+    ENV.should_receive(:[]).with("TEMP").and_return(dir)
+    File.should_receive(:directory?).with(dir).and_return(true)
+    File.should_receive(:writable?).with(dir).and_return(true)
+    File.should_receive(:expand_path).with(dir).and_return(dir)
+    tmp("test.txt").should == dir + "/test.txt"
+  end
+
+  it "returns ENV['USERPROFILE']/<name> if ENV['USERPROFILE'] is a writable directory" do
+    dir = "/tmp/temp"
+    ENV.should_receive(:[]).with("TEMP").and_return(dir)
+    File.should_receive(:directory?).with(dir).and_return(true)
+    File.should_receive(:writable?).with(dir).and_return(true)
+    File.should_receive(:expand_path).with(dir).and_return(dir)
+    tmp("test.txt").should == dir + "/test.txt"
+  end
+
+  it "returns the actual file name if the file is a symlink" do
+    dir = "/tmp"
+    File.should_receive(:directory?).with(dir).and_return(true)
+    File.should_receive(:writable?).with(dir).and_return(true)
+    File.should_receive(:expand_path).with(dir).and_return(dir)
+    File.should_receive(:symlink?).with(dir).and_return(true)
+    File.should_receive(:readlink).with(dir).and_return("/ponies"+dir)
+    tmp("test.txt").should == "/ponies" + dir + "/test.txt"
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/matchers/base_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/matchers/base_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/matchers/base_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,216 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers/base'
+require 'time'
+
+describe PositiveOperatorMatcher, "== operator" do
+  it "raises an ExpectationNotMetError when expected == actual returns false" do
+    lambda {
+      PositiveOperatorMatcher.new(1) == 2
+    }.should raise_error(ExpectationNotMetError)
+  end
+
+  it "provides a failure message that 'Expected x to equal y'" do
+    Expectation.should_receive(:fail_with).with("Expected 1\n", "to equal 2\n")
+    PositiveOperatorMatcher.new(1) == 2
+  end
+
+  it "does not raise an exception when expected == actual returns true" do
+    PositiveOperatorMatcher.new(1) == 1
+  end
+end
+
+describe PositiveOperatorMatcher, "=~ operator" do
+  it "raises an ExpectationNotMetError when expected =~ actual returns false" do
+    lambda {
+      PositiveOperatorMatcher.new('real') =~ /fake/
+    }.should raise_error(ExpectationNotMetError)
+  end
+
+  it "provides a failure message that 'Expected \"x\" to match y'" do
+    Expectation.should_receive(:fail_with).with("Expected \"real\"\n", "to match /fake/\n")
+    PositiveOperatorMatcher.new('real') =~ /fake/
+  end
+
+  it "does not raise an exception when expected =~ actual returns true" do
+    PositiveOperatorMatcher.new('real') =~ /real/
+  end
+end
+
+describe PositiveOperatorMatcher, "> operator" do
+  it "raises an ExpectationNotMetError when expected > actual returns false" do
+    lambda {
+      PositiveOperatorMatcher.new(4) > 5
+    }.should raise_error(ExpectationNotMetError)
+  end
+
+  it "provides a failure message that 'Expected x to be greater than y'" do
+    Expectation.should_receive(:fail_with).with("Expected 4\n", "to be greater than 5\n")
+    PositiveOperatorMatcher.new(4) > 5
+  end
+
+  it "does not raise an exception when expected > actual returns true" do
+    PositiveOperatorMatcher.new(5) > 4
+  end
+end
+
+describe PositiveOperatorMatcher, ">= operator" do
+  it "raises an ExpectationNotMetError when expected >= actual returns false" do
+    lambda {
+      PositiveOperatorMatcher.new(4) >= 5
+    }.should raise_error(ExpectationNotMetError)
+  end
+
+  it "provides a failure message that 'Expected x to be greater than or equal to y'" do
+    Expectation.should_receive(:fail_with).with("Expected 4\n", "to be greater than or equal to 5\n")
+    PositiveOperatorMatcher.new(4) >= 5
+  end
+
+  it "does not raise an exception when expected > actual returns true" do
+    PositiveOperatorMatcher.new(5) >= 4
+    PositiveOperatorMatcher.new(5) >= 5
+  end
+end
+
+describe PositiveOperatorMatcher, "< operater" do
+  it "raises an ExpectationNotMetError when expected < actual returns false" do
+    lambda {
+      PositiveOperatorMatcher.new(5) < 4
+    }.should raise_error(ExpectationNotMetError)
+  end
+
+  it "provides a failure message that 'Expected x to be less than y'" do
+    Expectation.should_receive(:fail_with).with("Expected 5\n", "to be less than 4\n")
+    PositiveOperatorMatcher.new(5) < 4
+  end
+
+  it "does not raise an exception when expected < actual returns true" do
+    PositiveOperatorMatcher.new(4) < 5
+  end
+end
+
+describe PositiveOperatorMatcher, "<= operater" do
+  it "raises an ExpectationNotMetError when expected < actual returns false" do
+    lambda {
+      PositiveOperatorMatcher.new(5) <= 4
+    }.should raise_error(ExpectationNotMetError)
+  end
+
+  it "provides a failure message that 'Expected x to be less than or equal to y'" do
+    Expectation.should_receive(:fail_with).with("Expected 5\n", "to be less than or equal to 4\n")
+    PositiveOperatorMatcher.new(5) <= 4
+  end
+
+  it "does not raise an exception when expected < actual returns true" do
+    PositiveOperatorMatcher.new(4) <= 5
+    PositiveOperatorMatcher.new(4) <= 4
+  end
+end
+
+describe NegativeOperatorMatcher, "== operator" do
+  it "raises an ExpectationNotMetError when expected == actual returns true" do
+    lambda {
+      NegativeOperatorMatcher.new(1) == 1
+    }.should raise_error(ExpectationNotMetError)
+  end
+
+  it "provides a failure message that 'Expected x not to equal y'" do
+    Expectation.should_receive(:fail_with).with("Expected 1\n", "not to equal 1\n")
+    NegativeOperatorMatcher.new(1) == 1
+  end
+
+  it "does not raise an exception when expected == actual returns false" do
+    NegativeOperatorMatcher.new(1) == 2
+  end
+end
+
+describe NegativeOperatorMatcher, "=~ operator" do
+  it "raises an ExpectationNotMetError when expected =~ actual returns true" do
+    lambda {
+      NegativeOperatorMatcher.new('real') =~ /real/
+    }.should raise_error(ExpectationNotMetError)
+  end
+
+  it "provides a failure message that 'Expected \"x\" not to match /y/'" do
+    Expectation.should_receive(:fail_with).with("Expected \"real\"\n", "not to match /real/\n")
+    NegativeOperatorMatcher.new('real') =~ /real/
+  end
+
+  it "does not raise an exception when expected =~ actual returns false" do
+    NegativeOperatorMatcher.new('real') =~ /fake/
+  end
+end
+
+describe NegativeOperatorMatcher, "< operator" do
+  it "raises an ExpectationNotMetError when expected < actual returns true" do
+    lambda {
+      NegativeOperatorMatcher.new(4) < 5
+    }.should raise_error(ExpectationNotMetError)
+  end
+
+  it "provides a failure message that 'Expected x not to be less than y'" do
+    Expectation.should_receive(:fail_with).with("Expected 4\n", "not to be less than 5\n")
+    NegativeOperatorMatcher.new(4) < 5
+  end
+
+  it "does not raise an exception when expected < actual returns false" do
+    NegativeOperatorMatcher.new(5) < 4
+  end
+end
+
+describe NegativeOperatorMatcher, "<= operator" do
+  it "raises an ExpectationNotMetError when expected <= actual returns true" do
+    lambda {
+      NegativeOperatorMatcher.new(4) <= 5
+    }.should raise_error(ExpectationNotMetError)
+    lambda {
+      NegativeOperatorMatcher.new(5) <= 5
+    }.should raise_error(ExpectationNotMetError)
+  end
+
+  it "provides a failure message that 'Expected x not to be less than or equal to y'" do
+    Expectation.should_receive(:fail_with).with("Expected 4\n", "not to be less than or equal to 5\n")
+    NegativeOperatorMatcher.new(4) <= 5
+  end
+
+  it "does not raise an exception when expected <= actual returns false" do
+    NegativeOperatorMatcher.new(5) <= 4
+  end
+end
+
+describe NegativeOperatorMatcher, "> operator" do
+  it "raises an ExpectationNotMetError when expected > actual returns true" do
+    lambda {
+      NegativeOperatorMatcher.new(5) > 4
+    }.should raise_error(ExpectationNotMetError)
+  end
+
+  it "provides a failure message that 'Expected x not to be greater than y'" do
+    Expectation.should_receive(:fail_with).with("Expected 5\n", "not to be greater than 4\n")
+    NegativeOperatorMatcher.new(5) > 4
+  end
+
+  it "does not raise an exception when expected > actual returns false" do
+    NegativeOperatorMatcher.new(4) > 5
+  end
+end
+
+describe NegativeOperatorMatcher, ">= operator" do
+  it "raises an ExpectationNotMetError when expected >= actual returns true" do
+    lambda {
+      NegativeOperatorMatcher.new(5) >= 4
+    }.should raise_error(ExpectationNotMetError)
+    lambda {
+      NegativeOperatorMatcher.new(5) >= 5
+    }.should raise_error(ExpectationNotMetError)
+  end
+
+  it "provides a failure message that 'Expected x not to be greater than or equal to y'" do
+    Expectation.should_receive(:fail_with).with("Expected 5\n", "not to be greater than or equal to 4\n")
+    NegativeOperatorMatcher.new(5) >= 4
+  end
+
+  it "does not raise an exception when expected >= actual returns false" do
+    NegativeOperatorMatcher.new(4) >= 5
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/matchers/be_an_instance_of_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/matchers/be_an_instance_of_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/matchers/be_an_instance_of_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,50 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers/be_an_instance_of'
+
+module BeAnInOfSpecs
+  class A
+  end
+
+  class B < A
+  end
+
+  class C < B
+  end
+end
+
+describe BeAnInstanceOfMatcher do
+  it "matches when actual is an instance_of? expected" do
+    a = BeAnInOfSpecs::A.new
+    BeAnInstanceOfMatcher.new(BeAnInOfSpecs::A).matches?(a).should be_true
+
+    b = BeAnInOfSpecs::B.new
+    BeAnInstanceOfMatcher.new(BeAnInOfSpecs::B).matches?(b).should be_true
+  end
+
+  it "does not match when actual is not an instance_of? expected" do
+    a = BeAnInOfSpecs::A.new
+    BeAnInstanceOfMatcher.new(BeAnInOfSpecs::B).matches?(a).should be_false
+
+    b = BeAnInOfSpecs::B.new
+    BeAnInstanceOfMatcher.new(BeAnInOfSpecs::A).matches?(b).should be_false
+
+    c = BeAnInOfSpecs::C.new
+    BeAnInstanceOfMatcher.new(BeAnInOfSpecs::A).matches?(c).should be_false
+    BeAnInstanceOfMatcher.new(BeAnInOfSpecs::B).matches?(c).should be_false
+  end
+
+  it "provides a useful failure message" do
+    matcher = BeAnInstanceOfMatcher.new(Numeric)
+    matcher.matches?("string")
+    matcher.failure_message.should == [
+      "Expected \"string\" (String)", "to be an instance of Numeric"]
+  end
+
+  it "provides a useful negative failure message" do
+    matcher = BeAnInstanceOfMatcher.new(Numeric)
+    matcher.matches?(4.2)
+    matcher.negative_failure_message.should == [
+      "Expected 4.2 (Float)", "not to be an instance of Numeric"]
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/matchers/be_ancestor_of_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/matchers/be_ancestor_of_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/matchers/be_ancestor_of_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,28 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers/be_ancestor_of'
+
+class Parent; end
+class Child < Parent; end
+
+describe BeAncestorOfMatcher do
+  it "matches when actual is an ancestor of expected" do
+    BeAncestorOfMatcher.new(Child).matches?(Parent).should == true
+  end
+
+  it "does not match when actual is not an ancestor of expected" do
+    BeAncestorOfMatcher.new(Parent).matches?(Child).should == false
+  end
+
+  it "provides a useful failure message" do
+    matcher = BeAncestorOfMatcher.new(Parent)
+    matcher.matches?(Child)
+    matcher.failure_message.should == ["Expected Child", "to be an ancestor of Parent"]
+  end
+
+  it "provides a useful negative failure message" do
+    matcher = BeAncestorOfMatcher.new(Child)
+    matcher.matches?(Parent)
+    matcher.negative_failure_message.should == ["Expected Parent", "not to be an ancestor of Child"]
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/matchers/be_close_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/matchers/be_close_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/matchers/be_close_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,46 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers/be_close'
+
+# Adapted from RSpec 1.0.8
+describe BeCloseMatcher do
+  it "matches when actual == expected" do
+    BeCloseMatcher.new(5.0, 0.5).matches?(5.0).should == true
+  end
+
+  it "matches when actual < (expected + tolerance)" do
+    BeCloseMatcher.new(5.0, 0.5).matches?(5.49).should == true
+  end
+
+  it "matches when actual > (expected - tolerance)" do
+    BeCloseMatcher.new(5.0, 0.5).matches?(4.51).should == true
+  end
+
+  it "does not match when actual == (expected + tolerance)" do
+    BeCloseMatcher.new(5.0, 0.5).matches?(5.5).should == false
+  end
+
+  it "does not match when actual == (expected - tolerance)" do
+    BeCloseMatcher.new(5.0, 0.5).matches?(4.5).should == false
+  end
+
+  it "does not match when actual < (expected - tolerance)" do
+    BeCloseMatcher.new(5.0, 0.5).matches?(4.49).should == false
+  end
+
+  it "does not match when actual > (expected + tolerance)" do
+    BeCloseMatcher.new(5.0, 0.5).matches?(5.51).should == false
+  end
+
+  it "provides a useful failure message" do
+    matcher = BeCloseMatcher.new(5.0, 0.5)
+    matcher.matches?(5.51)
+    matcher.failure_message.should == ["Expected 5.0", "to be within +/- 0.5 of 5.51"]
+  end
+
+  it "provides a useful negative failure message" do
+    matcher = BeCloseMatcher.new(5.0, 0.5)
+    matcher.matches?(5.0)
+    matcher.negative_failure_message.should == ["Expected 5.0", "not to be within +/- 0.5 of 5.0"]
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/matchers/be_empty_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/matchers/be_empty_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/matchers/be_empty_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,26 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers/be_empty'
+
+describe BeEmptyMatcher do
+  it "matches when actual is empty" do
+    BeEmptyMatcher.new.matches?("").should == true
+  end
+
+  it "does not match when actual is not empty" do
+    BeEmptyMatcher.new.matches?([10]).should == false
+  end
+
+  it "provides a useful failure message" do
+    matcher = BeEmptyMatcher.new
+    matcher.matches?("not empty string")
+    matcher.failure_message.should == ["Expected \"not empty string\"", "to be empty"]
+  end
+
+  it "provides a useful negative failure message" do
+    matcher = BeEmptyMatcher.new
+    matcher.matches?("")
+    matcher.negative_failure_message.should == ["Expected \"\"", "not to be empty"]
+  end
+end
+

Added: MacRuby/branches/experimental/mspec/spec/matchers/be_false_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/matchers/be_false_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/matchers/be_false_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,28 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers/be_false'
+
+describe BeFalseMatcher do
+  it "matches when actual is false" do
+    BeFalseMatcher.new.matches?(false).should == true
+  end
+
+  it "does not match when actual is not false" do
+    BeFalseMatcher.new.matches?("").should == false
+    BeFalseMatcher.new.matches?(true).should == false
+    BeFalseMatcher.new.matches?(nil).should == false
+    BeFalseMatcher.new.matches?(0).should == false
+  end
+
+  it "provides a useful failure message" do
+    matcher = BeFalseMatcher.new
+    matcher.matches?("some string")
+    matcher.failure_message.should == ["Expected \"some string\"", "to be false"]
+  end
+
+  it "provides a useful negative failure message" do
+    matcher = BeFalseMatcher.new
+    matcher.matches?(false)
+    matcher.negative_failure_message.should == ["Expected false", "not to be false"]
+  end
+end
\ No newline at end of file

Added: MacRuby/branches/experimental/mspec/spec/matchers/be_kind_of_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/matchers/be_kind_of_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/matchers/be_kind_of_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,31 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers/be_kind_of'
+
+describe BeKindOfMatcher do
+  it "matches when actual is a kind_of? expected" do
+    BeKindOfMatcher.new(Integer).matches?(1).should == true
+    BeKindOfMatcher.new(Fixnum).matches?(2).should == true
+    BeKindOfMatcher.new(Regexp).matches?(/m/).should == true
+  end
+
+  it "does not match when actual is not a kind_of? expected" do
+    BeKindOfMatcher.new(Integer).matches?(1.5).should == false
+    BeKindOfMatcher.new(String).matches?(:a).should == false
+    BeKindOfMatcher.new(Hash).matches?([]).should == false
+  end
+
+  it "provides a useful failure message" do
+    matcher = BeKindOfMatcher.new(Numeric)
+    matcher.matches?('string')
+    matcher.failure_message.should == [
+      "Expected \"string\" (String)", "to be kind of Numeric"]
+  end
+
+  it "provides a useful negative failure message" do
+    matcher = BeKindOfMatcher.new(Numeric)
+    matcher.matches?(4.2)
+    matcher.negative_failure_message.should == [
+      "Expected 4.2 (Float)", "not to be kind of Numeric"]
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/matchers/be_nil_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/matchers/be_nil_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/matchers/be_nil_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,27 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers/be_nil'
+
+describe BeNilMatcher do
+  it "matches when actual is nil" do
+    BeNilMatcher.new.matches?(nil).should == true
+  end
+
+  it "does not match when actual is not nil" do
+    BeNilMatcher.new.matches?("").should == false
+    BeNilMatcher.new.matches?(false).should == false
+    BeNilMatcher.new.matches?(0).should == false
+  end
+
+  it "provides a useful failure message" do
+    matcher = BeNilMatcher.new
+    matcher.matches?("some string")
+    matcher.failure_message.should == ["Expected \"some string\"", "to be nil"]
+  end
+
+  it "provides a useful negative failure message" do
+    matcher = BeNilMatcher.new
+    matcher.matches?(nil)
+    matcher.negative_failure_message.should == ["Expected nil", "not to be nil"]
+  end
+end
\ No newline at end of file

Added: MacRuby/branches/experimental/mspec/spec/matchers/be_true_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/matchers/be_true_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/matchers/be_true_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,28 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers/be_true'
+
+describe BeTrueMatcher do
+  it "matches when actual is true" do
+    BeTrueMatcher.new.matches?(true).should == true
+  end
+
+  it "does not match when actual is not true" do
+    BeTrueMatcher.new.matches?("").should == false
+    BeTrueMatcher.new.matches?(false).should == false
+    BeTrueMatcher.new.matches?(nil).should == false
+    BeTrueMatcher.new.matches?(0).should == false
+  end
+
+  it "provides a useful failure message" do
+    matcher = BeTrueMatcher.new
+    matcher.matches?("some string")
+    matcher.failure_message.should == ["Expected \"some string\"", "to be true"]
+  end
+
+  it "provides a useful negative failure message" do
+    matcher = BeTrueMatcher.new
+    matcher.matches?(true)
+    matcher.negative_failure_message.should == ["Expected true", "not to be true"]
+  end
+end
\ No newline at end of file

Added: MacRuby/branches/experimental/mspec/spec/matchers/complain_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/matchers/complain_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/matchers/complain_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,52 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers/complain'
+
+describe ComplainMatcher do
+  it "matches when executing the proc results in output to $stderr" do
+    proc = lambda { warn "I'm gonna tell yo mama" }
+    ComplainMatcher.new(nil).matches?(proc).should == true
+  end
+
+  it "maches when executing the proc results in the expected output to $stderr" do
+    proc = lambda { warn "Que haces?" }
+    ComplainMatcher.new("Que haces?\n").matches?(proc).should == true
+    ComplainMatcher.new("Que pasa?\n").matches?(proc).should == false
+    ComplainMatcher.new(/Que/).matches?(proc).should == true
+    ComplainMatcher.new(/Quoi/).matches?(proc).should == false
+  end
+
+  it "does not match when there is no output to $stderr" do
+    ComplainMatcher.new(nil).matches?(lambda {}).should == false
+  end
+
+  it "provides a useful failure message" do
+    matcher = ComplainMatcher.new(nil)
+    matcher.matches?(lambda { })
+    matcher.failure_message.should == ["Expected a warning", "but received none"]
+    matcher = ComplainMatcher.new("listen here")
+    matcher.matches?(lambda { warn "look out" })
+    matcher.failure_message.should ==
+      ["Expected warning: \"listen here\"", "but got: \"look out\""]
+    matcher = ComplainMatcher.new(/talk/)
+    matcher.matches?(lambda { warn "listen up" })
+    matcher.failure_message.should ==
+      ["Expected warning to match:", "/talk/"]
+  end
+
+  it "provides a useful negative failure message" do
+    proc = lambda { warn "ouch" }
+    matcher = ComplainMatcher.new(nil)
+    matcher.matches?(proc)
+    matcher.negative_failure_message.should ==
+      ["Unexpected warning: ", "\"ouch\""]
+    matcher = ComplainMatcher.new("ouchy")
+    matcher.matches?(proc)
+    matcher.negative_failure_message.should ==
+      ["Expected warning: \"ouchy\"", "but got: \"ouch\""]
+    matcher = ComplainMatcher.new(/ou/)
+    matcher.matches?(proc)
+    matcher.negative_failure_message.should ==
+      ["Expected warning not to match:", "/ou/"]
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/matchers/eql_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/matchers/eql_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/matchers/eql_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,33 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers/eql'
+
+describe EqlMatcher do
+  it "matches when actual is eql? to expected" do
+    EqlMatcher.new(1).matches?(1).should == true
+    EqlMatcher.new(1.5).matches?(1.5).should == true
+    EqlMatcher.new("red").matches?("red").should == true
+    EqlMatcher.new(:blue).matches?(:blue).should == true
+    EqlMatcher.new(Object).matches?(Object).should == true
+
+    o = Object.new
+    EqlMatcher.new(o).matches?(o).should == true
+  end
+
+  it "does not match when actual is not eql? to expected" do
+    EqlMatcher.new(1).matches?(1.0).should == false
+    EqlMatcher.new(Hash).matches?(Object).should == false
+  end
+
+  it "provides a useful failure message" do
+    matcher = EqlMatcher.new("red")
+    matcher.matches?("red")
+    matcher.failure_message.should == ["Expected \"red\"\n", "to have same value and type as \"red\"\n"]
+  end
+
+  it "provides a useful negative failure message" do
+    matcher = EqlMatcher.new(1)
+    matcher.matches?(1.0)
+    matcher.negative_failure_message.should == ["Expected 1.0\n", "not to have same value or type as 1\n"]
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/matchers/equal_element_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/matchers/equal_element_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/matchers/equal_element_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,75 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers/equal_element'
+
+describe EqualElementMatcher do
+  it "matches if it finds an element with the passed name, no matter what attributes/content" do
+    EqualElementMatcher.new("A").matches?('<A></A>').should be_true
+    EqualElementMatcher.new("A").matches?('<A HREF="http://example.com"></A>').should be_true
+    EqualElementMatcher.new("A").matches?('<A HREF="http://example.com"></A>').should be_true
+
+    EqualElementMatcher.new("BASE").matches?('<BASE></A>').should be_false
+    EqualElementMatcher.new("BASE").matches?('<A></BASE>').should be_false
+    EqualElementMatcher.new("BASE").matches?('<A></A>').should be_false
+    EqualElementMatcher.new("BASE").matches?('<A HREF="http://example.com"></A>').should be_false
+    EqualElementMatcher.new("BASE").matches?('<A HREF="http://example.com"></A>').should be_false
+  end
+
+  it "matches if it finds an element with the passed name and the passed attributes" do
+    EqualElementMatcher.new("A", {}).matches?('<A></A>').should be_true
+    EqualElementMatcher.new("A", nil).matches?('<A HREF="http://example.com"></A>').should be_true
+    EqualElementMatcher.new("A", "HREF" => "http://example.com").matches?('<A HREF="http://example.com"></A>').should be_true
+
+    EqualElementMatcher.new("A", {}).matches?('<A HREF="http://example.com"></A>').should be_false
+    EqualElementMatcher.new("A", "HREF" => "http://example.com").matches?('<A></A>').should be_false
+    EqualElementMatcher.new("A", "HREF" => "http://example.com").matches?('<A HREF="http://test.com"></A>').should be_false
+    EqualElementMatcher.new("A", "HREF" => "http://example.com").matches?('<A HREF="http://example.com" HREF="http://example.com"></A>').should be_false
+  end
+
+  it "matches if it finds an element with the passed name, the passed attributes and the passed content" do
+    EqualElementMatcher.new("A", {}, "").matches?('<A></A>').should be_true
+    EqualElementMatcher.new("A", {"HREF" => "http://example.com"}, "Example").matches?('<A HREF="http://example.com">Example</A>').should be_true
+
+    EqualElementMatcher.new("A", {}, "Test").matches?('<A></A>').should be_false
+    EqualElementMatcher.new("A", {"HREF" => "http://example.com"}, "Example").matches?('<A HREF="http://example.com"></A>').should be_false
+    EqualElementMatcher.new("A", {"HREF" => "http://example.com"}, "Example").matches?('<A HREF="http://example.com">Test</A>').should be_false
+  end
+  
+  it "can match unclosed elements" do
+    EqualElementMatcher.new("BASE", nil, nil, :not_closed => true).matches?('<BASE>').should be_true
+    EqualElementMatcher.new("BASE", {"HREF" => "http://example.com"}, nil, :not_closed => true).matches?('<BASE HREF="http://example.com">').should be_true
+    EqualElementMatcher.new("BASE", {"HREF" => "http://example.com"}, "Example", :not_closed => true).matches?('<BASE HREF="http://example.com">Example').should be_true
+
+    EqualElementMatcher.new("BASE", {}, nil, :not_closed => true).matches?('<BASE HREF="http://example.com">').should be_false
+    EqualElementMatcher.new("BASE", {"HREF" => "http://example.com"}, "", :not_closed => true).matches?('<BASE HREF="http://example.com">Example').should be_false
+    EqualElementMatcher.new("BASE", {"HREF" => "http://example.com"}, "Test", :not_closed => true).matches?('<BASE HREF="http://example.com">Example').should be_false
+  end
+
+  it "provides a useful failure message" do
+    equal_element = EqualElementMatcher.new("A", {}, "Test")
+    equal_element.matches?('<A></A>').should be_false
+    equal_element.failure_message.should == [%{Expected "<A></A>"\n}, %{to be a 'A' element with no attributes and "Test" as content}]
+
+    equal_element = EqualElementMatcher.new("A", {}, "")
+    equal_element.matches?('<A>Test</A>').should be_false
+    equal_element.failure_message.should == [%{Expected "<A>Test</A>"\n}, %{to be a 'A' element with no attributes and no content}]
+
+    equal_element = EqualElementMatcher.new("A", "HREF" => "http://www.example.com")
+    equal_element.matches?('<A>Test</A>').should be_false
+    equal_element.failure_message.should == [%{Expected "<A>Test</A>"\n}, %{to be a 'A' element with HREF="http://www.example.com" and any content}]
+  end
+
+  it "provides a useful negative failure message" do
+    equal_element = EqualElementMatcher.new("A", {}, "Test")
+    equal_element.matches?('<A></A>').should be_false
+    equal_element.negative_failure_message.should == [%{Expected "<A></A>"\n}, %{not to be a 'A' element with no attributes and "Test" as content}]
+
+    equal_element = EqualElementMatcher.new("A", {}, "")
+    equal_element.matches?('<A>Test</A>').should be_false
+    equal_element.negative_failure_message.should == [%{Expected "<A>Test</A>"\n}, %{not to be a 'A' element with no attributes and no content}]
+
+    equal_element = EqualElementMatcher.new("A", "HREF" => "http://www.example.com")
+    equal_element.matches?('<A>Test</A>').should be_false
+    equal_element.negative_failure_message.should == [%{Expected "<A>Test</A>"\n}, %{not to be a 'A' element with HREF="http://www.example.com" and any content}]
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/matchers/equal_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/matchers/equal_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/matchers/equal_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,33 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers/equal'
+
+describe EqualMatcher do
+  it "matches when actual is equal? to expected" do
+    EqualMatcher.new(1).matches?(1).should == true
+    EqualMatcher.new(:blue).matches?(:blue).should == true
+    EqualMatcher.new(Object).matches?(Object).should == true
+
+    o = Object.new
+    EqualMatcher.new(o).matches?(o).should == true
+  end
+
+  it "does not match when actual is not a equal? to expected" do
+    EqualMatcher.new(1).matches?(1.0).should == false
+    EqualMatcher.new(1.5).matches?(1.5).should == false
+    EqualMatcher.new("blue").matches?("blue").should == false
+    EqualMatcher.new(Hash).matches?(Object).should == false
+  end
+
+  it "provides a useful failure message" do
+    matcher = EqualMatcher.new("red")
+    matcher.matches?("red")
+    matcher.failure_message.should == ["Expected \"red\"\n", "to be identical to \"red\"\n"]
+  end
+
+  it "provides a useful negative failure message" do
+    matcher = EqualMatcher.new(1)
+    matcher.matches?(1)
+    matcher.negative_failure_message.should == ["Expected 1\n", "not to be identical to 1\n"]
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/matchers/equal_utf16_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/matchers/equal_utf16_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/matchers/equal_utf16_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,47 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers/equal_utf16'
+
+describe EqualUtf16Matcher do
+  it "when given strings, matches when actual == expected" do
+    EqualUtf16Matcher.new("abcd").matches?("abcd").should == true
+  end
+
+  it "when given strings, matches when actual == expected, with byte order reversed" do
+    EqualUtf16Matcher.new("abcd").matches?("badc").should == true
+  end
+
+  it "when given arrays, matches when actual == expected" do
+    EqualUtf16Matcher.new(["abcd"]).matches?(["abcd"]).should == true
+    EqualUtf16Matcher.new(["abcd", "efgh"]).matches?(["abcd", "efgh"]).should == true
+  end
+
+  it "when given arrays, matches when actual == a version of expected with byte order reversed for all strings contained" do
+    EqualUtf16Matcher.new(["abcd"]).matches?(["badc"]).should == true
+    EqualUtf16Matcher.new(["abcd", "efgh"]).matches?(["badc", "fehg"]).should == true
+  end
+
+  it "when given strings, does not match when actual != expected AND != expected with byte order reversed" do
+    EqualUtf16Matcher.new("abcd").matches?("").should == false
+    EqualUtf16Matcher.new("abcd").matches?(nil).should == false
+    EqualUtf16Matcher.new("abcd").matches?("acbd").should == false
+  end
+
+  it "when given arrays, does not match when actual is not == expected or == a version of expected with byte order reversed for all strings contained simultaneously" do
+    EqualUtf16Matcher.new(["abcd"]).matches?([]).should == false
+    EqualUtf16Matcher.new(["abcd"]).matches?(["dcba"]).should == false
+    EqualUtf16Matcher.new(["abcd", "efgh"]).matches?(["abcd", "fehg"]).should == false
+  end
+
+  it "provides a useful failure message" do
+    matcher = EqualUtf16Matcher.new("a\0b\0")
+    matcher.matches?("a\0b\0c\0")
+    matcher.failure_message.should == ["Expected \"a\\000b\\000c\\000\"\n", "to equal \"a\\000b\\000\"\n or \"\\000a\\000b\"\n"]
+  end
+
+  it "provides a useful negative failure message" do
+    matcher = EqualUtf16Matcher.new("a\0b\0")
+    matcher.matches?("\0a\0b")
+    matcher.negative_failure_message.should == ["Expected \"\\000a\\000b\"\n", "not to equal \"a\\000b\\000\"\n nor \"\\000a\\000b\"\n"]
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/matchers/have_constant_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/matchers/have_constant_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/matchers/have_constant_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,37 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers/have_constant'
+
+class HCMSpecs
+  X = :x
+end
+
+describe HaveConstantMatcher do
+  it "matches when mod has the constant" do
+    matcher = HaveConstantMatcher.new :X
+    matcher.matches?(HCMSpecs).should be_true
+  end
+
+  it "does not match when mod does not have the constant" do
+    matcher = HaveConstantMatcher.new :A
+    matcher.matches?(HCMSpecs).should be_false
+  end
+
+  it "provides a failure message for #should" do
+    matcher = HaveConstantMatcher.new :A
+    matcher.matches?(HCMSpecs)
+    matcher.failure_message.should == [
+      "Expected HCMSpecs to have constant 'A'",
+      "but it does not"
+    ]
+  end
+
+  it "provides a failure messoge for #should_not" do
+    matcher = HaveConstantMatcher.new :X
+    matcher.matches?(HCMSpecs)
+    matcher.negative_failure_message.should == [
+      "Expected HCMSpecs NOT to have constant 'X'",
+      "but it does"
+    ]
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/matchers/have_instance_method_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/matchers/have_instance_method_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/matchers/have_instance_method_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,53 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers/have_instance_method'
+
+class HIMMSpecs
+  def instance_method
+  end
+
+  class Subclass < HIMMSpecs
+    def instance_sub_method
+    end
+  end
+end
+
+describe HaveInstanceMethodMatcher do
+  it "inherits from MethodMatcher" do
+    HaveInstanceMethodMatcher.new(:m).should be_kind_of(MethodMatcher)
+  end
+
+  it "matches when mod has the instance method" do
+    matcher = HaveInstanceMethodMatcher.new :instance_method
+    matcher.matches?(HIMMSpecs).should be_true
+    matcher.matches?(HIMMSpecs::Subclass).should be_true
+  end
+
+  it "does not match when mod does not have the instance method" do
+    matcher = HaveInstanceMethodMatcher.new :another_method
+    matcher.matches?(HIMMSpecs).should be_false
+  end
+
+  it "does not match if the method is in a superclass and include_super is false" do
+    matcher = HaveInstanceMethodMatcher.new :instance_method, false
+    matcher.matches?(HIMMSpecs::Subclass).should be_false
+  end
+
+  it "provides a failure message for #should" do
+    matcher = HaveInstanceMethodMatcher.new :some_method
+    matcher.matches?(HIMMSpecs)
+    matcher.failure_message.should == [
+      "Expected HIMMSpecs to have instance method 'some_method'",
+      "but it does not"
+    ]
+  end
+
+  it "provides a failure messoge for #should_not" do
+    matcher = HaveInstanceMethodMatcher.new :some_method
+    matcher.matches?(HIMMSpecs)
+    matcher.negative_failure_message.should == [
+      "Expected HIMMSpecs NOT to have instance method 'some_method'",
+      "but it does"
+    ]
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/matchers/have_method_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/matchers/have_method_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/matchers/have_method_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,55 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers/have_method'
+
+class HMMSpecs
+  def instance_method
+  end
+
+  class Subclass < HMMSpecs
+    def instance_sub_method
+    end
+  end
+end
+
+describe HaveMethodMatcher do
+  it "inherits from MethodMatcher" do
+    HaveMethodMatcher.new(:m).should be_kind_of(MethodMatcher)
+  end
+
+  it "matches when mod has the method" do
+    matcher = HaveMethodMatcher.new :instance_method
+    matcher.matches?(HMMSpecs).should be_true
+    matcher.matches?(HMMSpecs.new).should be_true
+    matcher.matches?(HMMSpecs::Subclass).should be_true
+    matcher.matches?(HMMSpecs::Subclass.new).should be_true
+  end
+
+  it "does not match when mod does not have the method" do
+    matcher = HaveMethodMatcher.new :another_method
+    matcher.matches?(HMMSpecs).should be_false
+  end
+
+  it "does not match if the method is in a superclass and include_super is false" do
+    matcher = HaveMethodMatcher.new :instance_method, false
+    matcher.matches?(HMMSpecs::Subclass).should be_false
+  end
+
+  it "provides a failure message for #should" do
+    matcher = HaveMethodMatcher.new :some_method
+    matcher.matches?(HMMSpecs)
+    matcher.failure_message.should == [
+      "Expected HMMSpecs to have method 'some_method'",
+      "but it does not"
+    ]
+  end
+
+  it "provides a failure messoge for #should_not" do
+    matcher = HaveMethodMatcher.new :some_method
+    matcher.matches?(HMMSpecs)
+    matcher.negative_failure_message.should == [
+      "Expected HMMSpecs NOT to have method 'some_method'",
+      "but it does"
+    ]
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/matchers/have_private_instance_method_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/matchers/have_private_instance_method_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/matchers/have_private_instance_method_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,57 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers/have_private_instance_method'
+
+class HPIMMSpecs
+  private
+
+  def private_method
+  end
+
+  class Subclass < HPIMMSpecs
+    private
+
+    def private_sub_method
+    end
+  end
+end
+
+describe HavePrivateInstanceMethodMatcher do
+  it "inherits from MethodMatcher" do
+    HavePrivateInstanceMethodMatcher.new(:m).should be_kind_of(MethodMatcher)
+  end
+
+  it "matches when mod has the private instance method" do
+    matcher = HavePrivateInstanceMethodMatcher.new :private_method
+    matcher.matches?(HPIMMSpecs).should be_true
+    matcher.matches?(HPIMMSpecs::Subclass).should be_true
+  end
+
+  it "does not match when mod does not have the private instance method" do
+    matcher = HavePrivateInstanceMethodMatcher.new :another_method
+    matcher.matches?(HPIMMSpecs).should be_false
+  end
+
+  it "does not match if the method is in a superclass and include_super is false" do
+    matcher = HavePrivateInstanceMethodMatcher.new :private_method, false
+    matcher.matches?(HPIMMSpecs::Subclass).should be_false
+  end
+
+  it "provides a failure message for #should" do
+    matcher = HavePrivateInstanceMethodMatcher.new :some_method
+    matcher.matches?(HPIMMSpecs)
+    matcher.failure_message.should == [
+      "Expected HPIMMSpecs to have private instance method 'some_method'",
+      "but it does not"
+    ]
+  end
+
+  it "provides a failure messoge for #should_not" do
+    matcher = HavePrivateInstanceMethodMatcher.new :some_method
+    matcher.matches?(HPIMMSpecs)
+    matcher.negative_failure_message.should == [
+      "Expected HPIMMSpecs NOT to have private instance method 'some_method'",
+      "but it does"
+    ]
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/matchers/include_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/matchers/include_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/matchers/include_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,37 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers/include'
+
+describe IncludeMatcher do
+  it "matches when actual includes expected" do
+    IncludeMatcher.new(2).matches?([1,2,3]).should == true
+    IncludeMatcher.new("b").matches?("abc").should == true
+  end
+
+  it "does not match when actual does not include expected" do
+    IncludeMatcher.new(4).matches?([1,2,3]).should == false
+    IncludeMatcher.new("d").matches?("abc").should == false
+  end
+
+  it "matches when actual includes all expected" do
+    IncludeMatcher.new(3, 2, 1).matches?([1,2,3]).should == true
+    IncludeMatcher.new("a", "b", "c").matches?("abc").should == true
+  end
+
+  it "does not match when actual does not include all expected" do
+    IncludeMatcher.new(3, 2, 4).matches?([1,2,3]).should == false
+    IncludeMatcher.new("a", "b", "c", "d").matches?("abc").should == false
+  end
+
+  it "provides a useful failure message" do
+    matcher = IncludeMatcher.new(5, 2)
+    matcher.matches?([1,2,3])
+    matcher.failure_message.should == ["Expected [1, 2, 3]", "to include 5"]
+  end
+
+  it "provides a useful negative failure message" do
+    matcher = IncludeMatcher.new(1, 2, 3)
+    matcher.matches?([1,2,3])
+    matcher.negative_failure_message.should == ["Expected [1, 2, 3]", "not to include 3"]
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/matchers/match_yaml_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/matchers/match_yaml_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/matchers/match_yaml_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,39 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers/match_yaml'
+
+describe MatchYAMLMatcher do
+  before :each do
+    @matcher = MatchYAMLMatcher.new("--- \nfoo: bar\n")
+  end
+
+  it "compares YAML documents and matches if they're equivalent" do
+      @matcher.matches?("--- \nfoo: bar\n").should == true
+  end
+
+  it "compares YAML documents and does not match if they're not equivalent" do
+      @matcher.matches?("--- \nbar: foo\n").should == false
+      @matcher.matches?("--- \nfoo: \nbar\n").should == false  
+  end
+
+  it "also receives objects that respond_to to_yaml" do
+    matcher = MatchYAMLMatcher.new("some string")
+    matcher.matches?("some string").should == true
+
+    matcher = MatchYAMLMatcher.new(['a', 'b'])
+    matcher.matches?("--- \n- a\n- b\n").should == true
+
+    matcher = MatchYAMLMatcher.new("foo" => "bar")
+    matcher.matches?("--- \nfoo: bar\n").should == true
+  end
+
+  it "matches documents with trailing whitespace" do
+    @matcher.matches?("--- \nfoo: bar   \n").should == true
+    @matcher.matches?("---       \nfoo: bar   \n").should == true
+  end
+
+  it "fails with a descriptive error message" do
+    @matcher.matches?("foo").should == false
+    @matcher.failure_message.should == ["Expected \"foo\"", " to match \"--- \\nfoo: bar\\n\""]
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/matchers/output_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/matchers/output_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/matchers/output_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,74 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers/output'
+
+describe OutputMatcher do
+  it "matches when executing the proc results in the expected output to $stdout" do
+    proc = Proc.new { puts "bang!" }
+    OutputMatcher.new("bang!\n", nil).matches?(proc).should == true
+    OutputMatcher.new("pop", nil).matches?(proc).should == false
+    OutputMatcher.new(/bang/, nil).matches?(proc).should == true
+    OutputMatcher.new(/po/, nil).matches?(proc).should == false
+  end
+
+  it "matches when executing the proc results in the expected output to $stderr" do
+    proc = Proc.new { $stderr.write "boom!" }
+    OutputMatcher.new(nil, "boom!").matches?(proc).should == true
+    OutputMatcher.new(nil, "fizzle").matches?(proc).should == false
+    OutputMatcher.new(nil, /boom/).matches?(proc).should == true
+    OutputMatcher.new(nil, /fizzl/).matches?(proc).should == false
+  end
+
+  it "provides a useful failure message" do
+    proc = Proc.new { puts "unexpected"; $stderr.puts "unerror" }
+    matcher = OutputMatcher.new("expected", "error")
+    matcher.matches?(proc)
+    matcher.failure_message.should ==
+      ["Expected:\n  $stdout: \"expected\"\n  $stderr: \"error\"\n",
+       "     got:\n  $stdout: \"unexpected\"\n  $stderr: \"unerror\"\n"]
+    matcher = OutputMatcher.new("expected", nil)
+    matcher.matches?(proc)
+    matcher.failure_message.should ==
+      ["Expected:\n  $stdout: \"expected\"\n",
+       "     got:\n  $stdout: \"unexpected\"\n"]
+    matcher = OutputMatcher.new(nil, "error")
+    matcher.matches?(proc)
+    matcher.failure_message.should ==
+      ["Expected:\n  $stderr: \"error\"\n",
+       "     got:\n  $stderr: \"unerror\"\n"]
+    matcher = OutputMatcher.new(/base/, nil)
+    matcher.matches?(proc)
+    matcher.failure_message.should ==
+      ["Expected:\n  $stdout: /base/\n",
+       "     got:\n  $stdout: \"unexpected\"\n"]
+    matcher = OutputMatcher.new(nil, /octave/)
+    matcher.matches?(proc)
+    matcher.failure_message.should ==
+      ["Expected:\n  $stderr: /octave/\n",
+       "     got:\n  $stderr: \"unerror\"\n"]
+  end
+
+  it "provides a useful negative failure message" do
+    proc = Proc.new { puts "expected"; $stderr.puts "error" }
+    matcher = OutputMatcher.new("expected", "error")
+    matcher.matches?(proc)
+    matcher.negative_failure_message.should ==
+      ["Expected output not to be:\n", "  $stdout: \"expected\"\n  $stderr: \"error\"\n"]
+    matcher = OutputMatcher.new("expected", nil)
+    matcher.matches?(proc)
+    matcher.negative_failure_message.should ==
+      ["Expected output not to be:\n", "  $stdout: \"expected\"\n"]
+    matcher = OutputMatcher.new(nil, "error")
+    matcher.matches?(proc)
+    matcher.negative_failure_message.should ==
+      ["Expected output not to be:\n", "  $stderr: \"error\"\n"]
+    matcher = OutputMatcher.new(/expect/, nil)
+    matcher.matches?(proc)
+    matcher.negative_failure_message.should ==
+      ["Expected output not to be:\n", "  $stdout: \"expected\"\n"]
+    matcher = OutputMatcher.new(nil, /err/)
+    matcher.matches?(proc)
+    matcher.negative_failure_message.should ==
+      ["Expected output not to be:\n", "  $stderr: \"error\"\n"]
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/matchers/output_to_fd_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/matchers/output_to_fd_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/matchers/output_to_fd_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,33 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers/output_to_fd'
+
+describe OutputToFDMatcher do
+  # Figure out how in the hell to achieve this
+  it "matches when running the block produces the expected output to the given FD" do
+    output_to_fd("Hi\n", STDERR).matches?(lambda { $stderr.print "Hi\n" }).should == true
+  end
+
+  it "does not match if running the block does not produce the expected output to the FD" do
+    output_to_fd("Hi\n", STDERR).matches?(lambda { $stderr.puts("Hello\n") }).should == false
+  end
+
+  it "defaults to matching against STDOUT" do
+    output_to_fd("Hi\n").matches?(lambda { $stdout.print "Hi\n" }).should == true
+  end
+
+  it "accepts any IO instance" do
+    io = IO.new STDOUT.fileno
+    output_to_fd("Hi\n", io).matches?(lambda { io.print "Hi\n" }).should == true
+  end
+
+  it "allows matching with a Regexp" do
+    s = "Hi there\n"
+    output_to_fd(/Hi/, STDERR).matches?(lambda { $stderr.print s }).should == true
+    output_to_fd(/Hi?/, STDERR).matches?(lambda { $stderr.print s }).should == true
+    output_to_fd(/[hH]i?/, STDERR).matches?(lambda { $stderr.print s }).should == true
+    output_to_fd(/.*/, STDERR).matches?(lambda { $stderr.print s }).should == true
+    output_to_fd(/H.*?here/, STDERR).matches?(lambda { $stderr.print s }).should == true
+    output_to_fd(/Ahoy/, STDERR).matches?(lambda { $stderr.print s }).should == false
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/matchers/raise_error_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/matchers/raise_error_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/matchers/raise_error_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,56 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers/raise_error'
+
+class ExpectedException < Exception; end
+class UnexpectedException < Exception; end
+
+describe RaiseErrorMatcher do
+  it "matches when the proc raises the expected exception" do
+    proc = Proc.new { raise ExpectedException }
+    RaiseErrorMatcher.new(ExpectedException, nil).matches?(proc).should == true
+  end
+
+  it "executes it's optional block if matched" do
+    run = false
+    proc = Proc.new { raise ExpectedException }
+    matcher = RaiseErrorMatcher.new(ExpectedException, nil) { |error|
+      run = true
+      error.class.should == ExpectedException
+    }
+
+    matcher.matches?(proc).should == true
+    run.should == true
+  end
+
+  it "matches when the proc raises the expected exception with the expected message" do
+    proc = Proc.new { raise ExpectedException, "message" }
+    RaiseErrorMatcher.new(ExpectedException, "message").matches?(proc).should == true
+  end
+
+  it "does not match when the proc does not raise the expected exception" do
+    proc = Proc.new { raise UnexpectedException }
+    RaiseErrorMatcher.new(ExpectedException, nil).matches?(proc).should == false
+  end
+
+  it "does not match when the proc raises the expected exception with an unexpected message" do
+    proc = Proc.new { raise UnexpectedException, "unexpected" }
+    RaiseErrorMatcher.new(ExpectedException, "expected").matches?(proc).should == false
+  end
+
+  it "provides a useful failure message" do
+    proc = Proc.new { raise UnexpectedException, "unexpected" }
+    matcher = RaiseErrorMatcher.new(ExpectedException, "expected")
+    matcher.matches?(proc)
+    matcher.failure_message.should ==
+      ["Expected ExpectedException (expected)", "but got UnexpectedException (unexpected)"]
+  end
+
+  it "provides a useful negative failure message" do
+    proc = Proc.new { raise ExpectedException, "expected" }
+    matcher = RaiseErrorMatcher.new(ExpectedException, "expected")
+    matcher.matches?(proc)
+    matcher.negative_failure_message.should ==
+      ["Expected to not get ExpectedException (expected)", ""]
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/matchers/respond_to_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/matchers/respond_to_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/matchers/respond_to_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,31 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers/respond_to'
+
+describe RespondToMatcher do
+  it "matches when actual does respond_to? expected" do
+    RespondToMatcher.new(:to_s).matches?(Object.new).should == true
+    RespondToMatcher.new(:inject).matches?([]).should == true
+    RespondToMatcher.new(:[]).matches?(1).should == true
+    RespondToMatcher.new(:[]=).matches?("string").should == true
+  end
+
+  it "does not match when actual does not respond_to? expected" do
+    RespondToMatcher.new(:to_i).matches?(Object.new).should == false
+    RespondToMatcher.new(:inject).matches?(1).should == false
+    RespondToMatcher.new(:non_existent_method).matches?([]).should == false
+    RespondToMatcher.new(:[]=).matches?(1).should == false
+  end
+
+  it "provides a useful failure message" do
+    matcher = RespondToMatcher.new(:non_existent_method)
+    matcher.matches?('string')
+    matcher.failure_message.should == ["Expected \"string\" (String)", "to respond to non_existent_method"]
+  end
+
+  it "provides a useful negative failure message" do
+    matcher = RespondToMatcher.new(:to_i)
+    matcher.matches?(4.2)
+    matcher.negative_failure_message.should == ["Expected 4.2 (Float)", "not to respond to to_i"]
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/matchers/stringsymboladapter_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/matchers/stringsymboladapter_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/matchers/stringsymboladapter_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,40 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers/stringsymboladapter'
+
+class StringSymbolSpecs
+  include StringSymbolAdapter
+end
+
+describe StringSymbolAdapter, "#convert_name" do
+  before :all do
+    @verbose = $VERBOSE
+    $VERBOSE = nil
+  end
+
+  after :all do
+    $VERBOSE = @verbose
+  end
+
+  before :each do
+    @ruby_version = Object.const_get :RUBY_VERSION
+
+    @name = mock("name")
+  end
+
+  after :each do
+    Object.const_set :RUBY_VERSION, @ruby_version
+  end
+
+  it "converts the name to a string if RUBY_VERSION < 1.9" do
+    Object.const_set :RUBY_VERSION, "1.8.6"
+    @name.should_receive(:to_s).and_return("method_name")
+    StringSymbolSpecs.new.convert_name @name
+  end
+
+  it "does not convert the name to a string if RUBY_VERSION >= 1.9" do
+    Object.const_set :RUBY_VERSION, "1.9.0"
+    @name.should_not_receive(:to_s)
+    StringSymbolSpecs.new.convert_name @name
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/mocks/mock_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/mocks/mock_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/mocks/mock_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,448 @@
+# This is a bit awkward. Currently the way to verify that the
+# opposites are true (for example a failure when the specified
+# arguments are NOT provided) is to simply alter the particular
+# spec to a failure condition.
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/runner/mspec'
+require 'mspec/mocks/mock'
+require 'mspec/mocks/proxy'
+
+describe Mock, ".mocks" do
+  it "returns a Hash" do
+    Mock.mocks.should be_kind_of(Hash)
+  end
+end
+
+describe Mock, ".stubs" do
+  it "returns a Hash" do
+    Mock.stubs.should be_kind_of(Hash)
+  end
+end
+
+describe Mock, ".replaced_name" do
+  it "returns the name for a method that is being replaced by a mock method" do
+    m = mock('a fake id')
+    m.stub!(:__id__).and_return(42)
+    Mock.replaced_name(m, :method_call).should == :__ms_42_method_call__
+  end
+end
+
+describe Mock, ".replaced_key" do
+  it "returns a key used internally by Mock" do
+    m = mock('a fake id')
+    m.stub!(:__id__).and_return(42)
+    Mock.replaced_key(m, :method_call).should == [:__ms_42_method_call__, m, :method_call]
+  end
+end
+
+describe Mock, ".replaced?" do
+  before :each do
+    @mock = mock('install_method')
+    MSpec.stub!(:actions)
+    MSpec.stub!(:current).and_return(mock("spec state", :null_object => true))
+  end
+
+  it "returns true if a method has been stubbed on an object" do
+    Mock.install_method @mock, :method_call
+    Mock.replaced?(Mock.replaced_key(@mock, :method_call)).should be_true
+  end
+
+  it "returns true if a method has been mocked on an object" do
+    Mock.install_method @mock, :method_call, :stub
+    Mock.replaced?(Mock.replaced_key(@mock, :method_call)).should be_true
+  end
+
+  it "returns false if a method has not been stubbed or mocked" do
+    Mock.replaced?(Mock.replaced_key(@mock, :method_call)).should be_false
+  end
+end
+
+describe Mock, ".name_or_inspect" do
+  before :each do
+    @mock = mock("I have a #name")
+  end
+
+  it "returns the value of @name if set" do
+    @mock.instance_variable_set(:@name, "Myself")
+    Mock.name_or_inspect(@mock).should == "Myself"
+  end
+end
+
+describe Mock, ".install_method for mocks" do
+  before :each do
+    @mock = mock('install_method')
+    MSpec.stub!(:actions)
+    MSpec.stub!(:current).and_return(mock("spec state", :null_object => true))
+  end
+
+  after :each do
+    Mock.cleanup
+  end
+
+  it "returns a MockProxy instance" do
+    Mock.install_method(@mock, :method_call).should be_an_instance_of(MockProxy)
+  end
+
+  it "does not override a previously mocked method with the same name" do
+    Mock.install_method(@mock, :method_call).with(:a, :b).and_return(1)
+    Mock.install_method(@mock, :method_call).with(:c).and_return(2)
+    @mock.method_call(:a, :b)
+    @mock.method_call(:c)
+    lambda { @mock.method_call(:d) }.should raise_error(ExpectationNotMetError)
+  end
+
+  # This illustrates RSpec's behavior. This spec fails in mock call count verification
+  # on RSpec (i.e. Mock 'foo' expected :foo with (any args) once, but received it 0 times)
+  # and we mimic the behavior of RSpec.
+  #
+  # describe "A mock receiving multiple calls to #should_receive" do
+  #   it "returns the first value mocked" do
+  #     m = mock 'multiple #should_receive'
+  #     m.should_receive(:foo).and_return(true)
+  #     m.foo.should == true
+  #     m.should_receive(:foo).and_return(false)
+  #     m.foo.should == true
+  #   end
+  # end
+  #
+  it "does not override a previously mocked method having the same arguments" do
+    Mock.install_method(@mock, :method_call).with(:a).and_return(true)
+    @mock.method_call(:a).should == true
+    Mock.install_method(@mock, :method_call).with(:a).and_return(false)
+    @mock.method_call(:a).should == true
+    lambda { Mock.verify_count }.should raise_error(ExpectationNotMetError)
+  end
+
+  it "properly sends #respond_to? calls to the aliased respond_to? method when not matching mock expectations" do
+    Mock.install_method(@mock, :respond_to?).with(:to_str).and_return('mock to_str')
+    Mock.install_method(@mock, :respond_to?).with(:to_int).and_return('mock to_int')
+    @mock.respond_to?(:to_str).should == 'mock to_str'
+    @mock.respond_to?(:to_int).should == 'mock to_int'
+    @mock.respond_to?(:to_s).should == true
+    @mock.respond_to?(:not_really_a_real_method_seriously).should == false
+  end
+
+  it "adds to the expectation tally" do
+    state = mock("run state", :null_object => true)
+    state.stub!(:state).and_return(mock("spec state"))
+    MSpec.should_receive(:current).and_return(state)
+    MSpec.should_receive(:actions).with(:expectation, state.state)
+    Mock.install_method(@mock, :method_call).and_return(1)
+    @mock.method_call.should == 1
+  end
+
+  it "registers that an expectation has been encountered" do
+    state = mock("run state", :null_object => true)
+    state.stub!(:state).and_return(mock("spec state"))
+    MSpec.should_receive(:expectation)
+    Mock.install_method(@mock, :method_call).and_return(1)
+    @mock.method_call.should == 1
+  end
+end
+
+describe Mock, ".install_method for stubs" do
+  before :each do
+    @mock = mock('install_method')
+    MSpec.stub!(:actions)
+    MSpec.stub!(:current).and_return(mock("spec state", :null_object => true))
+  end
+
+  after :each do
+    Mock.cleanup
+  end
+
+  it "returns a MockProxy instance" do
+    Mock.install_method(@mock, :method_call, :stub).should be_an_instance_of(MockProxy)
+  end
+
+  # This illustrates RSpec's behavior. This spec passes on RSpec and we mimic it
+  #
+  # describe "A mock receiving multiple calls to #stub!" do
+  #   it "returns the last value stubbed" do
+  #     m = mock 'multiple #stub!'
+  #     m.stub!(:foo).and_return(true)
+  #     m.foo.should == true
+  #     m.stub!(:foo).and_return(false)
+  #     m.foo.should == false
+  #   end
+  # end
+  it "inserts new stubs before old stubs" do
+    Mock.install_method(@mock, :method_call, :stub).with(:a).and_return(true)
+    @mock.method_call(:a).should == true
+    Mock.install_method(@mock, :method_call, :stub).with(:a).and_return(false)
+    @mock.method_call(:a).should == false
+    Mock.verify_count
+  end
+
+  it "does not add to the expectation tally" do
+    state = mock("run state", :null_object => true)
+    state.stub!(:state).and_return(mock("spec state"))
+    MSpec.should_not_receive(:actions)
+    Mock.install_method(@mock, :method_call, :stub).and_return(1)
+    @mock.method_call.should == 1
+  end
+end
+
+describe Mock, ".install_method" do
+  before :each do
+    @mock = mock('install_method')
+    MSpec.stub!(:actions)
+    MSpec.stub!(:current).and_return(mock("spec state", :null_object => true))
+  end
+
+  after :each do
+    Mock.cleanup
+  end
+
+  it "does not alias a mocked or stubbed method when installing a new mock or stub" do
+    @mock.should_not respond_to(:method_call)
+
+    Mock.install_method @mock, :method_call
+    @mock.should respond_to(:method_call)
+    @mock.should_not respond_to(Mock.replaced_name(@mock, :method_call))
+
+    Mock.install_method @mock, :method_call, :stub
+    @mock.should respond_to(:method_call)
+    @mock.should_not respond_to(Mock.replaced_name(@mock, :method_call))
+  end
+end
+
+describe Mock, ".verify_call" do
+  before :each do
+    MSpec.stub!(:actions)
+    MSpec.stub!(:current).and_return(mock("spec state", :null_object => true))
+
+    @mock = mock('verify_call')
+    @proxy = Mock.install_method @mock, :method_call
+  end
+
+  after :each do
+    ScratchPad.clear
+    Mock.cleanup
+  end
+
+  it "does not raise an exception when the mock method receives the expected arguments" do
+    @proxy.with(1, 'two', :three)
+    Mock.verify_call @mock, :method_call, 1, 'two', :three
+  end
+
+  it "raises an ExpectationNotMetError when the mock method does not receive the expected arguments" do
+    @proxy.with(4, 2)
+    lambda {
+      Mock.verify_call @mock, :method_call, 42
+    }.should raise_error(ExpectationNotMetError)
+  end
+
+  it "raises an ExpectationNotMetError when the mock method is called with arguments but expects none" do
+    lambda {
+      @proxy.with(:no_args)
+      Mock.verify_call @mock, :method_call, "hello"
+    }.should raise_error(ExpectationNotMetError)
+  end
+
+  it "raises an ExpectationNotMetError when the mock method is called with no arguments but expects some" do
+    @proxy.with("hello", "beautiful", "world")
+    lambda {
+      Mock.verify_call @mock, :method_call
+    }.should raise_error(ExpectationNotMetError)
+  end
+
+  it "does not raise an exception when the mock method is called with arguments and is expecting :any_args" do
+    @proxy.with(:any_args)
+    Mock.verify_call @mock, :method_call, 1, 2, 3
+  end
+
+  it "yields a passed block when it is expected to" do
+    @proxy.and_yield()
+    Mock.verify_call @mock, :method_call do
+      ScratchPad.record true
+    end
+    ScratchPad.recorded.should == true
+  end
+
+  it "does not yield a passed block when it is not expected to" do
+    Mock.verify_call @mock, :method_call do
+      ScratchPad.record true
+    end
+    ScratchPad.recorded.should == nil
+  end
+
+  it "can yield subsequently" do
+    @proxy.and_yield(1).and_yield(2).and_yield(3)
+
+    ScratchPad.record []
+    Mock.verify_call @mock, :method_call do |arg|
+      ScratchPad << arg
+    end
+    ScratchPad.recorded.should == [1, 2, 3]
+  end
+
+  it "can yield and return an expected value" do
+    @proxy.and_yield(1).and_return(3)
+
+    Mock.verify_call(@mock, :method_call) { |arg| ScratchPad.record arg }.should == 3
+    ScratchPad.recorded.should == 1
+  end
+
+  it "raises an expection when it is expected to yield but no block is given" do
+    @proxy.and_yield(1, 2, 3)
+    lambda {
+      Mock.verify_call(@mock, :method_call)
+    }.should raise_error(ExpectationNotMetError)
+  end
+
+  it "raises an expection when it is expected to yield more arguments than the block can take" do
+    @proxy.and_yield(1, 2, 3)
+    lambda {
+      Mock.verify_call(@mock, :method_call) {|a, b|}
+    }.should raise_error(ExpectationNotMetError)
+  end
+
+  it "does not raise an expection when it is expected to yield to a block that can take any number of arguments" do
+    @proxy.and_yield(1, 2, 3)
+    lambda {
+      Mock.verify_call(@mock, :method_call) {|*a|}
+    }.should_not raise_error(ExpectationNotMetError)
+  end
+end
+
+describe Mock, ".verify_count" do
+  before :each do
+    MSpec.stub!(:actions)
+    MSpec.stub!(:current).and_return(mock("spec state", :null_object => true))
+
+    @mock = mock('verify_count')
+    @proxy = Mock.install_method @mock, :method_call
+  end
+
+  after :each do
+    Mock.cleanup
+  end
+
+  it "does not raise an exception when the mock receives at least the expected number of calls" do
+    @proxy.at_least(2)
+    @mock.method_call
+    @mock.method_call
+    Mock.verify_count
+  end
+
+  it "raises an ExpectationNotMetError when the mock receives less than at least the expected number of calls" do
+    @proxy.at_least(2)
+    @mock.method_call
+    lambda { Mock.verify_count }.should raise_error(ExpectationNotMetError)
+  end
+
+  it "does not raise an exception when the mock receives at most the expected number of calls" do
+    @proxy.at_most(2)
+    @mock.method_call
+    @mock.method_call
+    Mock.verify_count
+  end
+
+  it "raises an ExpectationNotMetError when the mock receives more than at most the expected number of calls" do
+    @proxy.at_most(2)
+    @mock.method_call
+    @mock.method_call
+    @mock.method_call
+    lambda { Mock.verify_count }.should raise_error(ExpectationNotMetError)
+  end
+
+  it "does not raise an exception when the mock receives exactly the expected number of calls" do
+    @proxy.exactly(2)
+    @mock.method_call
+    @mock.method_call
+    Mock.verify_count
+  end
+
+  it "raises an ExpectationNotMetError when the mock receives less than exactly the expected number of calls" do
+    @proxy.exactly(2)
+    @mock.method_call
+    lambda { Mock.verify_count }.should raise_error(ExpectationNotMetError)
+  end
+
+  it "raises an ExpectationNotMetError when the mock receives more than exactly the expected number of calls" do
+    @proxy.exactly(2)
+    @mock.method_call
+    @mock.method_call
+    @mock.method_call
+    lambda { Mock.verify_count }.should raise_error(ExpectationNotMetError)
+  end
+end
+
+describe Mock, ".verify_count mixing mocks and stubs" do
+  before :each do
+    MSpec.stub!(:actions)
+    MSpec.stub!(:current).and_return(mock("spec state", :null_object => true))
+
+    @mock = mock('verify_count')
+  end
+
+  after :each do
+    Mock.cleanup
+  end
+
+  it "does not raise an exception for a stubbed method that is never called" do
+    Mock.install_method @mock, :method_call, :stub
+    Mock.verify_count
+  end
+
+  it "verifies the calls to the mocked method when a mock is defined after a stub" do
+    Mock.install_method @mock, :method_call, :stub
+    Mock.install_method @mock, :method_call, :mock
+    @mock.method_call
+    Mock.verify_count
+  end
+
+  it "verifies the calls to the mocked method when a mock is defined before a stub" do
+    Mock.install_method @mock, :method_call, :mock
+    Mock.install_method @mock, :method_call, :stub
+    @mock.method_call
+    Mock.verify_count
+  end
+end
+
+describe Mock, ".cleanup" do
+  before :each do
+    MSpec.stub!(:actions)
+    MSpec.stub!(:current).and_return(mock("spec state", :null_object => true))
+
+    @mock = mock('cleanup')
+    @proxy = Mock.install_method @mock, :method_call
+    @stub = Mock.install_method @mock, :method_call, :stub
+  end
+
+  after :each do
+    Mock.cleanup
+  end
+
+  it "removes the mock method call if it did not override an existing method" do
+    @mock.should respond_to(:method_call)
+
+    Mock.cleanup
+    @mock.should_not respond_to(:method_call)
+  end
+
+  it "removes the replaced method if the mock method overrides an existing method" do
+    def @mock.already_here() :hey end
+    @mock.should respond_to(:already_here)
+    Mock.install_method @mock, :already_here
+    @mock.should respond_to(Mock.replaced_name(@mock, :already_here))
+
+    Mock.cleanup
+    @mock.should_not respond_to(Mock.replaced_name(@mock, :already_here))
+    @mock.should respond_to(:already_here)
+    @mock.already_here.should == :hey
+  end
+
+  it "removes all mock expectations" do
+    Mock.mocks.should == { Mock.replaced_key(@mock, :method_call) => [@proxy] }
+    Mock.cleanup
+    Mock.mocks.should == {}
+  end
+
+  it "removes all stubs" do
+    Mock.stubs.should == { Mock.replaced_key(@mock, :method_call) => [@stub] }
+    Mock.cleanup
+    Mock.stubs.should == {}
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/mocks/proxy_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/mocks/proxy_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/mocks/proxy_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,366 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/mocks/proxy'
+
+describe MockObject, ".new" do
+  it "creates a new mock object" do
+    m = MockObject.new('not a null object')
+    lambda { m.not_a_method }.should raise_error(NoMethodError)
+  end
+
+  it "creates a new mock object that follows the NullObject pattern" do
+    m = MockObject.new('null object', :null_object => true)
+    m.not_really_a_method.should equal(m)
+  end
+end
+
+describe MockProxy, ".new" do
+  it "creates a mock proxy by default" do
+    MockProxy.new.mock?.should be_true
+  end
+
+  it "creates a stub proxy by request" do
+    MockProxy.new(:stub).stub?.should be_true
+  end
+
+  it "sets the call expectation to 1 call for a mock" do
+    MockProxy.new.count.should == [:exactly, 1]
+  end
+
+  it "sets the call expectation to any number of times for a stub" do
+    MockProxy.new(:stub).count.should == [:any_number_of_times, 0]
+  end
+end
+
+describe MockProxy, "#count" do
+  before :each do
+    @proxy = MockProxy.new
+  end
+
+  it "returns the expected number of calls the mock should receive" do
+    @proxy.count.should == [:exactly, 1]
+    @proxy.at_least(3).count.should == [:at_least, 3]
+  end
+end
+
+describe MockProxy, "#arguments" do
+  before :each do
+    @proxy = MockProxy.new
+  end
+
+  it "returns the expected arguments" do
+    @proxy.arguments.should == :any_args
+  end
+end
+
+describe MockProxy, "#with" do
+  before :each do
+    @proxy = MockProxy.new
+  end
+
+  it "returns self" do
+    @proxy.with(:a).should be_equal(@proxy)
+  end
+
+  it "raises an ArgumentError if no arguments are given" do
+    lambda { @proxy.with }.should raise_error(ArgumentError)
+  end
+
+  it "accepts any number of arguments" do
+    @proxy.with(1, 2, 3).should be_an_instance_of(MockProxy)
+    @proxy.arguments.should == [1,2,3]
+  end
+end
+
+describe MockProxy, "#once" do
+  before :each do
+    @proxy = MockProxy.new
+  end
+
+  it "returns self" do
+    @proxy.once.should be_equal(@proxy)
+  end
+
+  it "sets the expected calls to 1" do
+    @proxy.once
+    @proxy.count.should == [:exactly, 1]
+  end
+
+  it "accepts no arguments" do
+    lambda { @proxy.once(:a) }.should raise_error
+  end
+end
+
+describe MockProxy, "#twice" do
+  before :each do
+    @proxy = MockProxy.new
+  end
+
+  it "returns self" do
+    @proxy.twice.should be_equal(@proxy)
+  end
+
+  it "sets the expected calls to 2" do
+    @proxy.twice
+    @proxy.count.should == [:exactly, 2]
+  end
+
+  it "accepts no arguments" do
+    lambda { @proxy.twice(:b) }.should raise_error
+  end
+end
+
+describe MockProxy, "#exactly" do
+  before :each do
+    @proxy = MockProxy.new
+  end
+
+  it "returns self" do
+    @proxy.exactly(2).should be_equal(@proxy)
+  end
+
+  it "sets the expected calls to exactly n" do
+    @proxy.exactly(5)
+    @proxy.count.should == [:exactly, 5]
+  end
+
+  it "does not accept an argument that Integer() cannot convert" do
+    lambda { @proxy.exactly('x') }.should raise_error
+  end
+end
+
+describe MockProxy, "#at_least" do
+  before :each do
+    @proxy = MockProxy.new
+  end
+
+  it "returns self" do
+    @proxy.at_least(3).should be_equal(@proxy)
+  end
+
+  it "sets the expected calls to at least n" do
+    @proxy.at_least(3)
+    @proxy.count.should == [:at_least, 3]
+  end
+
+  it "accepts :once :twice" do
+    @proxy.at_least(:once)
+    @proxy.count.should == [:at_least, 1]
+    @proxy.at_least(:twice)
+    @proxy.count.should == [:at_least, 2]
+  end
+
+  it "does not accept an argument that Integer() cannot convert" do
+    lambda { @proxy.at_least('x') }.should raise_error
+  end
+end
+
+describe MockProxy, "#at_most" do
+  before :each do
+    @proxy = MockProxy.new
+  end
+
+  it "returns self" do
+    @proxy.at_most(2).should be_equal(@proxy)
+  end
+
+  it "sets the expected calls to at most n" do
+    @proxy.at_most(2)
+    @proxy.count.should == [:at_most, 2]
+  end
+
+  it "accepts :once, :twice" do
+    @proxy.at_most(:once)
+    @proxy.count.should == [:at_most, 1]
+    @proxy.at_most(:twice)
+    @proxy.count.should == [:at_most, 2]
+  end
+
+  it "does not accept an argument that Integer() cannot convert" do
+    lambda { @proxy.at_most('x') }.should raise_error
+  end
+end
+
+describe MockProxy, "#any_number_of_times" do
+  before :each do
+    @proxy = MockProxy.new
+  end
+
+  it "returns self" do
+    @proxy.any_number_of_times.should be_equal(@proxy)
+  end
+
+  it "sets the expected calls to any number of times" do
+    @proxy.any_number_of_times
+    @proxy.count.should == [:any_number_of_times, 0]
+  end
+
+  it "does not accept an argument" do
+    lambda { @proxy.any_number_of_times(2) }.should raise_error
+  end
+end
+
+describe MockProxy, "#and_return" do
+  before :each do
+    @proxy = MockProxy.new
+  end
+
+  it "returns self" do
+    @proxy.and_return(false).should equal(@proxy)
+  end
+
+  it "sets the expected return value" do
+    @proxy.and_return(false)
+    @proxy.returning.should == false
+  end
+
+  it "accepts any number of return values" do
+    @proxy.and_return(1, 2, 3)
+    @proxy.returning.should == 1
+    @proxy.returning.should == 2
+    @proxy.returning.should == 3
+  end
+  
+  it "implicitly sets the expected number of calls" do
+    @proxy.and_return(1, 2, 3)
+    @proxy.count.should == [:exactly, 3]
+  end
+  
+  it "only sets the expected number of calls if it is higher than what is already set" do
+    @proxy.at_least(5).times.and_return(1, 2, 3)
+    @proxy.count.should == [:at_least, 5]
+
+    @proxy.at_least(2).times.and_return(1, 2, 3)
+    @proxy.count.should == [:at_least, 3]
+  end
+end
+
+describe MockProxy, "#returning" do
+  before :each do
+    @proxy = MockProxy.new
+  end
+
+  it "returns nil by default" do
+    @proxy.returning.should be_nil
+  end
+
+  it "returns the value set by #and_return" do
+    @proxy.and_return(2)
+    @proxy.returning.should == 2
+    @proxy.returning.should == 2
+  end
+
+  it "returns a sequence of values set by #and_return" do
+    @proxy.and_return(1,2,3,4)
+    @proxy.returning.should == 1
+    @proxy.returning.should == 2
+    @proxy.returning.should == 3
+    @proxy.returning.should == 4
+    @proxy.returning.should == 4
+    @proxy.returning.should == 4
+  end
+end
+
+describe MockProxy, "#calls" do
+  before :each do
+    @proxy = MockProxy.new
+  end
+
+  it "returns the number of times the proxy is called" do
+    @proxy.calls.should == 0
+  end
+end
+
+describe MockProxy, "#called" do
+  before :each do
+    @proxy = MockProxy.new
+  end
+
+  it "increments the number of times the proxy is called" do
+    @proxy.called
+    @proxy.called
+    @proxy.calls.should == 2
+  end
+end
+
+describe MockProxy, "#times" do
+  before :each do
+    @proxy = MockProxy.new
+  end
+
+  it "is a no-op" do
+    @proxy.times.should == @proxy
+  end
+end
+
+describe MockProxy, "#stub?" do
+  it "returns true if the proxy is created as a stub" do
+    MockProxy.new(:stub).stub?.should be_true
+  end
+
+  it "returns false if the proxy is created as a mock" do
+    MockProxy.new(:mock).stub?.should be_false
+  end
+end
+
+describe MockProxy, "#mock?" do
+  it "returns true if the proxy is created as a mock" do
+    MockProxy.new(:mock).mock?.should be_true
+  end
+
+  it "returns false if the proxy is created as a stub" do
+    MockProxy.new(:stub).mock?.should be_false
+  end
+end
+
+describe MockProxy, "#and_yield" do
+  before :each do
+    @proxy = MockProxy.new
+  end
+
+  it "returns self" do
+    @proxy.and_yield(false).should equal(@proxy)
+  end
+
+  it "sets the expected values to yield" do
+    @proxy.and_yield(1).yielding.should == [[1]]
+  end
+
+  it "accepts multiple values to yield" do
+    @proxy.and_yield(1, 2, 3).yielding.should == [[1, 2, 3]]
+  end
+end
+
+describe MockProxy, "#yielding" do
+  before :each do
+    @proxy = MockProxy.new
+  end
+
+  it "returns an empty array by default" do
+    @proxy.yielding.should == []
+  end
+
+  it "returns an array of arrays of values the proxy should yield" do
+    @proxy.and_yield(3)
+    @proxy.yielding.should == [[3]]
+  end
+
+  it "returns an accumulation of arrays of values the proxy should yield" do
+    @proxy.and_yield(1).and_yield(2, 3)
+    @proxy.yielding.should == [[1], [2, 3]]
+  end
+end
+
+describe MockProxy, "#yielding?" do
+  before :each do
+    @proxy = MockProxy.new
+  end
+
+  it "returns false if the proxy is not yielding" do
+    @proxy.yielding?.should be_false
+  end
+
+  it "returns true if the proxy is yielding" do
+    @proxy.and_yield(1)
+    @proxy.yielding?.should be_true
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/runner/actions/debug_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/runner/actions/debug_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/runner/actions/debug_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,62 @@
+require File.dirname(__FILE__) + '/../../spec_helper'
+require 'mspec/runner/actions/debug'
+require 'mspec/runner/mspec'
+require 'mspec/runner/context'
+require 'mspec/runner/example'
+
+describe DebugAction do
+  before :each do
+    MSpec.stub!(:read_tags).and_return([])
+  end
+
+  it "creates an MatchFilter with its tag and desc arguments" do
+    filter = mock('action filter', :null_object => true)
+    MatchFilter.should_receive(:new).with(nil, "some", "thing").and_return(filter)
+    DebugAction.new ["tag", "key"], ["some", "thing"]
+  end
+end
+
+describe DebugAction, "#before" do
+  before :each do
+    MSpec.stub!(:read_tags).and_return([])
+    @state = ExampleState.new ContextState.new("Catch#me"), "if you can"
+  end
+
+  it "does not invoke the debugger if the description does not match" do
+    Kernel.should_not_receive(:debugger)
+    action = DebugAction.new nil, "match"
+    action.before @state
+  end
+
+  it "invokes the debugger if the description matches" do
+    Kernel.should_receive(:debugger)
+    action = DebugAction.new nil, "can"
+    action.before @state
+  end
+end
+
+describe DebugAction, "#register" do
+  before :each do
+    MSpec.stub!(:read_tags).and_return([])
+    MSpec.stub!(:register)
+    @action = DebugAction.new nil, nil
+  end
+
+  it "registers itself with MSpec for the :before action" do
+    MSpec.should_receive(:register).with(:before, @action)
+    @action.register
+  end
+end
+
+describe DebugAction, "#unregister" do
+  before :each do
+    MSpec.stub!(:read_tags).and_return([])
+    MSpec.stub!(:unregister)
+    @action = DebugAction.new nil, nil
+  end
+
+  it "unregisters itself with MSpec for the :before action" do
+    MSpec.should_receive(:unregister).with(:before, @action)
+    @action.unregister
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/runner/actions/filter_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/runner/actions/filter_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/runner/actions/filter_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,84 @@
+require File.dirname(__FILE__) + '/../../spec_helper'
+require 'mspec/runner/actions/filter'
+require 'mspec/runner/mspec'
+require 'mspec/runner/tag'
+
+describe ActionFilter do
+  it "creates a filter when not passed a description" do
+    MatchFilter.should_not_receive(:new)
+    ActionFilter.new(nil, nil)
+  end
+
+  it "creates a filter from a single description" do
+    MatchFilter.should_receive(:new).with(nil, "match me")
+    ActionFilter.new(nil, "match me")
+  end
+
+  it "creates a filter from an array of descriptions" do
+    MatchFilter.should_receive(:new).with(nil, "match me", "again")
+    ActionFilter.new(nil, ["match me", "again"])
+  end
+end
+
+describe ActionFilter, "#===" do
+  before :each do
+    MSpec.stub!(:read_tags).and_return(["match"])
+    @action = ActionFilter.new(nil, ["catch", "if you"])
+  end
+
+  it "returns false if there are no filters" do
+    action = ActionFilter.new
+    action.===("anything").should == false
+  end
+
+  it "returns true if the argument matches any of the descriptions" do
+    @action.===("catch").should == true
+    @action.===("if you can").should == true
+  end
+
+  it "returns false if the argument does not match any of the descriptions" do
+    @action.===("patch me").should == false
+    @action.===("if I can").should == false
+  end
+end
+
+describe ActionFilter, "#load" do
+  before :each do
+    @tag = SpecTag.new "tag(comment):description"
+  end
+
+  it "creates a filter from a single tag" do
+    MSpec.should_receive(:read_tags).with(["tag"]).and_return([@tag])
+    MatchFilter.should_receive(:new).with(nil, "description")
+    ActionFilter.new("tag", nil).load
+  end
+
+  it "creates a filter from an array of tags" do
+    MSpec.should_receive(:read_tags).with(["tag", "key"]).and_return([@tag])
+    MatchFilter.should_receive(:new).with(nil, "description")
+    ActionFilter.new(["tag", "key"], nil).load
+  end
+
+  it "creates a filter from both tags and descriptions" do
+    MSpec.should_receive(:read_tags).and_return([@tag])
+    filter = ActionFilter.new("tag", ["match me", "again"])
+    MatchFilter.should_receive(:new).with(nil, "description")
+    filter.load
+  end
+end
+
+describe ActionFilter, "#register" do
+  it "registers itself with MSpec for the :load actions" do
+    filter = ActionFilter.new
+    MSpec.should_receive(:register).with(:load, filter)
+    filter.register
+  end
+end
+
+describe ActionFilter, "#unregister" do
+  it "unregisters itself with MSpec for the :load actions" do
+    filter = ActionFilter.new
+    MSpec.should_receive(:unregister).with(:load, filter)
+    filter.unregister
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/runner/actions/gdb_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/runner/actions/gdb_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/runner/actions/gdb_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,61 @@
+require File.dirname(__FILE__) + '/../../spec_helper'
+require 'mspec/runner/actions/gdb'
+require 'mspec/runner/mspec'
+require 'mspec/runner/example'
+
+describe GdbAction do
+  before :each do
+    MSpec.stub!(:read_tags).and_return([])
+  end
+
+  it "creates an MatchFilter with its tag and desc arguments" do
+    filter = mock('action filter', :null_object => true)
+    MatchFilter.should_receive(:new).with(nil, "some", "thing").and_return(filter)
+    GdbAction.new ["tag", "key"], ["some", "thing"]
+  end
+end
+
+describe GdbAction, "#before" do
+  before :each do
+    MSpec.stub!(:read_tags).and_return([])
+    @state = ExampleState.new ContextState.new("Catch#me"), "if you can"
+  end
+
+  it "does not invoke the debugger if the description does not match" do
+    Kernel.should_not_receive(:yield_gdb)
+    action = GdbAction.new nil, "match"
+    action.before @state
+  end
+
+  it "invokes the debugger if the description matches" do
+    Kernel.should_receive(:yield_gdb).with(true)
+    action = GdbAction.new nil, "can"
+    action.before @state
+  end
+end
+
+describe GdbAction, "#register" do
+  before :each do
+    MSpec.stub!(:read_tags).and_return([])
+    MSpec.stub!(:register)
+    @action = GdbAction.new nil, nil
+  end
+
+  it "registers itself with MSpec for the :before action" do
+    MSpec.should_receive(:register).with(:before, @action)
+    @action.register
+  end
+end
+
+describe GdbAction, "#unregister" do
+  before :each do
+    MSpec.stub!(:read_tags).and_return([])
+    MSpec.stub!(:unregister)
+    @action = GdbAction.new nil, nil
+  end
+
+  it "unregisters itself with MSpec for the :before action" do
+    MSpec.should_receive(:unregister).with(:before, @action)
+    @action.unregister
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/runner/actions/tag_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/runner/actions/tag_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/runner/actions/tag_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,315 @@
+require File.dirname(__FILE__) + '/../../spec_helper'
+require 'mspec/runner/actions/tag'
+require 'mspec/runner/mspec'
+require 'mspec/runner/example'
+require 'mspec/runner/tag'
+
+describe TagAction, ".new" do
+  it "creates an MatchFilter with its tag and desc arguments" do
+    filter = mock('action filter', :null_object => true)
+    MatchFilter.should_receive(:new).with(nil, "some", "thing").and_return(filter)
+    TagAction.new :add, :all, nil, nil, ["tag", "key"], ["some", "thing"]
+  end
+end
+
+describe TagAction, "#===" do
+  before :each do
+    MSpec.stub!(:read_tags).and_return(["match"])
+    @action = TagAction.new :add, :fail, nil, nil, nil, ["catch", "if you"]
+  end
+
+  it "returns true if there are no filters" do
+    action = TagAction.new :add, :all, nil, nil
+    action.===("anything").should == true
+  end
+
+  it "returns true if the argument matches any of the descriptions" do
+    @action.===("catch").should == true
+    @action.===("if you can").should == true
+  end
+
+  it "returns false if the argument does not match any of the descriptions" do
+    @action.===("patch me").should == false
+    @action.===("if I can").should == false
+  end
+end
+
+describe TagAction, "#exception?" do
+  before :each do
+    @action = TagAction.new :add, :fail, nil, nil, nil, nil
+  end
+
+  it "returns false if no exception has been raised while evaluating an example" do
+    @action.exception?.should be_false
+  end
+
+  it "returns true if an exception was raised while evaluating an example" do
+    @action.exception ExceptionState.new nil, nil, Exception.new("failed")
+    @action.exception?.should be_true
+  end
+end
+
+describe TagAction, "#outcome?" do
+  before :each do
+    MSpec.stub!(:read_tags).and_return([])
+    @exception = ExceptionState.new nil, nil, Exception.new("failed")
+  end
+
+  it "returns true if outcome is :fail and the spec fails" do
+    action = TagAction.new :add, :fail, nil, nil, nil, nil
+    action.exception @exception
+    action.outcome?.should == true
+  end
+
+  it "returns false if the outcome is :fail and the spec passes" do
+    action = TagAction.new :add, :fail, nil, nil, nil, nil
+    action.outcome?.should == false
+  end
+
+  it "returns true if the outcome is :pass and the spec passes" do
+    action = TagAction.new :del, :pass, nil, nil, nil, nil
+    action.outcome?.should == true
+  end
+
+  it "returns false if the outcome is :pass and the spec fails" do
+    action = TagAction.new :del, :pass, nil, nil, nil, nil
+    action.exception @exception
+    action.outcome?.should == false
+  end
+
+  it "returns true if the outcome is :all" do
+    action = TagAction.new :add, :all, nil, nil, nil, nil
+    action.exception @exception
+    action.outcome?.should == true
+  end
+end
+
+describe TagAction, "#before" do
+  it "resets the #exception? flag to false" do
+    action = TagAction.new :add, :fail, nil, nil, nil, nil
+    action.exception?.should be_false
+    action.exception ExceptionState.new(nil, nil, Exception.new("Fail!"))
+    action.exception?.should be_true
+    action.before(ExampleState.new(ContextState.new("describe"), "it"))
+    action.exception?.should be_false
+  end
+end
+
+describe TagAction, "#exception" do
+  it "sets the #exception? flag" do
+    action = TagAction.new :add, :fail, nil, nil, nil, nil
+    action.exception?.should be_false
+    action.exception ExceptionState.new(nil, nil, Exception.new("Fail!"))
+    action.exception?.should be_true
+  end
+end
+
+describe TagAction, "#after when action is :add" do
+  before :each do
+    MSpec.stub!(:read_tags).and_return([])
+    context = ContextState.new "Catch#me"
+    @state = ExampleState.new context, "if you can"
+    @tag = SpecTag.new "tag(comment):Catch#me if you can"
+    SpecTag.stub!(:new).and_return(@tag)
+    @exception = ExceptionState.new nil, nil, Exception.new("failed")
+  end
+
+  it "does not write a tag if the description does not match" do
+    MSpec.should_not_receive(:write_tag)
+    action = TagAction.new :add, :all, "tag", "comment", nil, "match"
+    action.after @state
+  end
+
+  it "does not write a tag if outcome is :fail and the spec passed" do
+    MSpec.should_not_receive(:write_tag)
+    action = TagAction.new :add, :fail, "tag", "comment", nil, "can"
+    action.after @state
+  end
+
+  it "writes a tag if the outcome is :fail and the spec failed" do
+    MSpec.should_receive(:write_tag).with(@tag)
+    action = TagAction.new :add, :fail, "tag", "comment", nil, "can"
+    action.exception @exception
+    action.after @state
+  end
+
+  it "does not write a tag if outcome is :pass and the spec failed" do
+    MSpec.should_not_receive(:write_tag)
+    action = TagAction.new :add, :pass, "tag", "comment", nil, "can"
+    action.exception @exception
+    action.after @state
+  end
+
+  it "writes a tag if the outcome is :pass and the spec passed" do
+    MSpec.should_receive(:write_tag).with(@tag)
+    action = TagAction.new :add, :pass, "tag", "comment", nil, "can"
+    action.after @state
+  end
+
+  it "writes a tag if the outcome is :all" do
+    MSpec.should_receive(:write_tag).with(@tag)
+    action = TagAction.new :add, :all, "tag", "comment", nil, "can"
+    action.after @state
+  end
+end
+
+describe TagAction, "#after when action is :del" do
+  before :each do
+    MSpec.stub!(:read_tags).and_return([])
+    context = ContextState.new "Catch#me"
+    @state = ExampleState.new context, "if you can"
+    @tag = SpecTag.new "tag(comment):Catch#me if you can"
+    SpecTag.stub!(:new).and_return(@tag)
+    @exception = ExceptionState.new nil, nil, Exception.new("failed")
+  end
+
+  it "does not delete a tag if the description does not match" do
+    MSpec.should_not_receive(:delete_tag)
+    action = TagAction.new :del, :all, "tag", "comment", nil, "match"
+    action.after @state
+  end
+
+  it "does not delete a tag if outcome is :fail and the spec passed" do
+    MSpec.should_not_receive(:delete_tag)
+    action = TagAction.new :del, :fail, "tag", "comment", nil, "can"
+    action.after @state
+  end
+
+  it "deletes a tag if the outcome is :fail and the spec failed" do
+    MSpec.should_receive(:delete_tag).with(@tag)
+    action = TagAction.new :del, :fail, "tag", "comment", nil, "can"
+    action.exception @exception
+    action.after @state
+  end
+
+  it "does not delete a tag if outcome is :pass and the spec failed" do
+    MSpec.should_not_receive(:delete_tag)
+    action = TagAction.new :del, :pass, "tag", "comment", nil, "can"
+    action.exception @exception
+    action.after @state
+  end
+
+  it "deletes a tag if the outcome is :pass and the spec passed" do
+    MSpec.should_receive(:delete_tag).with(@tag)
+    action = TagAction.new :del, :pass, "tag", "comment", nil, "can"
+    action.after @state
+  end
+
+  it "deletes a tag if the outcome is :all" do
+    MSpec.should_receive(:delete_tag).with(@tag)
+    action = TagAction.new :del, :all, "tag", "comment", nil, "can"
+    action.after @state
+  end
+end
+
+describe TagAction, "#finish" do
+  before :each do
+    $stdout = @out = IOStub.new
+    context = ContextState.new "Catch#me"
+    @state = ExampleState.new context, "if you can"
+    MSpec.stub!(:write_tag).and_return(true)
+    MSpec.stub!(:delete_tag).and_return(true)
+  end
+
+  after :each do
+    $stdout = STDOUT
+  end
+
+  it "reports no specs tagged if none where tagged" do
+    action = TagAction.new :add, :fail, "tag", "comment", nil, "can"
+    action.stub!(:outcome?).and_return(false)
+    action.after @state
+    action.finish
+    @out.should == "\nTagAction: no specs were tagged with 'tag'\n"
+  end
+
+  it "reports no specs tagged if none where tagged" do
+    action = TagAction.new :del, :fail, "tag", "comment", nil, "can"
+    action.stub!(:outcome?).and_return(false)
+    action.after @state
+    action.finish
+    @out.should == "\nTagAction: no tags 'tag' were deleted\n"
+  end
+
+  it "reports the spec descriptions that were tagged" do
+    action = TagAction.new :add, :fail, "tag", "comment", nil, "can"
+    action.stub!(:outcome?).and_return(true)
+    action.after @state
+    action.finish
+    @out.should ==
+%[
+TagAction: specs tagged with 'tag':
+
+Catch#me if you can
+]
+  end
+
+  it "reports the spec descriptions for the tags that were deleted" do
+    action = TagAction.new :del, :fail, "tag", "comment", nil, "can"
+    action.stub!(:outcome?).and_return(true)
+    action.after @state
+    action.finish
+    @out.should ==
+%[
+TagAction: tag 'tag' deleted for specs:
+
+Catch#me if you can
+]
+  end
+end
+
+describe TagAction, "#register" do
+  before :each do
+    MSpec.stub!(:register)
+    MSpec.stub!(:read_tags).and_return([])
+    @action = TagAction.new :add, :all, nil, nil, nil, nil
+  end
+
+  it "registers itself with MSpec for the :before event" do
+    MSpec.should_receive(:register).with(:before, @action)
+    @action.register
+  end
+
+  it "registers itself with MSpec for the :after event" do
+    MSpec.should_receive(:register).with(:after, @action)
+    @action.register
+  end
+
+  it "registers itself with MSpec for the :exception event" do
+    MSpec.should_receive(:register).with(:exception, @action)
+    @action.register
+  end
+
+  it "registers itself with MSpec for the :finish event" do
+    MSpec.should_receive(:register).with(:finish, @action)
+    @action.register
+  end
+end
+
+describe TagAction, "#unregister" do
+  before :each do
+    MSpec.stub!(:unregister)
+    MSpec.stub!(:read_tags).and_return([])
+    @action = TagAction.new :add, :all, nil, nil, nil, nil
+  end
+
+  it "unregisters itself with MSpec for the :before event" do
+    MSpec.should_receive(:unregister).with(:before, @action)
+    @action.unregister
+  end
+
+  it "unregisters itself with MSpec for the :after event" do
+    MSpec.should_receive(:unregister).with(:after, @action)
+    @action.unregister
+  end
+
+  it "unregisters itself with MSpec for the :exception event" do
+    MSpec.should_receive(:unregister).with(:exception, @action)
+    @action.unregister
+  end
+
+  it "unregisters itself with MSpec for the :finish event" do
+    MSpec.should_receive(:unregister).with(:finish, @action)
+    @action.unregister
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/runner/actions/taglist_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/runner/actions/taglist_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/runner/actions/taglist_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,152 @@
+require File.dirname(__FILE__) + '/../../spec_helper'
+require 'mspec/runner/actions/taglist'
+require 'mspec/runner/mspec'
+require 'mspec/runner/example'
+require 'mspec/runner/tag'
+
+describe TagListAction, "#include?" do
+  it "returns true" do
+    TagListAction.new.include?(:anything).should be_true
+  end
+end
+
+describe TagListAction, "#===" do
+  before :each do
+    tag = SpecTag.new "fails:description"
+    MSpec.stub!(:read_tags).and_return([tag])
+    @filter = mock("MatchFilter", :null_object => true)
+    MatchFilter.stub!(:new).and_return(@filter)
+    @action = TagListAction.new
+    @action.load
+  end
+
+  it "returns true if filter === string returns true" do
+    @filter.should_receive(:===).with("str").and_return(true)
+    @action.===("str").should be_true
+  end
+
+  it "returns false if filter === string returns false" do
+    @filter.should_receive(:===).with("str").and_return(false)
+    @action.===("str").should be_false
+  end
+end
+
+describe TagListAction, "#start" do
+  before :each do
+    @stdout = $stdout
+    $stdout = IOStub.new
+  end
+
+  after :each do
+    $stdout = @stdout
+  end
+
+  it "prints a banner for specific tags" do
+    action = TagListAction.new ["fails", "unstable"]
+    action.start
+    $stdout.should == "\nListing specs tagged with 'fails', 'unstable'\n\n"
+  end
+
+  it "prints a banner for all tags" do
+    action = TagListAction.new
+    action.start
+    $stdout.should == "\nListing all tagged specs\n\n"
+  end
+end
+
+describe TagListAction, "#load" do
+  before :each do
+    @t1 = SpecTag.new "fails:I fail"
+    @t2 = SpecTag.new "unstable:I'm unstable"
+  end
+
+  it "creates a MatchFilter for matching tags" do
+    MSpec.should_receive(:read_tags).with(["fails"]).and_return([@t1])
+    MatchFilter.should_receive(:new).with(nil, "I fail")
+    TagListAction.new(["fails"]).load
+  end
+
+  it "creates a MatchFilter for all tags" do
+    MSpec.should_receive(:read_tags).and_return([@t1, @t2])
+    MatchFilter.should_receive(:new).with(nil, "I fail", "I'm unstable")
+    TagListAction.new.load
+  end
+
+  it "does not create a MatchFilter if there are no matching tags" do
+    MSpec.stub!(:read_tags).and_return([])
+    MatchFilter.should_not_receive(:new)
+    TagListAction.new(["fails"]).load
+  end
+end
+
+describe TagListAction, "#after" do
+  before :each do
+    @stdout = $stdout
+    $stdout = IOStub.new
+
+    @state = mock("ExampleState")
+    @state.stub!(:description).and_return("str")
+
+    @action = TagListAction.new
+  end
+
+  after :each do
+    $stdout = @stdout
+  end
+
+  it "prints nothing if the filter does not match" do
+    @action.should_receive(:===).with("str").and_return(false)
+    @action.after(@state)
+    $stdout.should == ""
+  end
+
+  it "prints the example description if the filter matches" do
+    @action.should_receive(:===).with("str").and_return(true)
+    @action.after(@state)
+    $stdout.should == "str\n"
+  end
+end
+
+describe TagListAction, "#register" do
+  before :each do
+    MSpec.stub!(:register)
+    @action = TagListAction.new
+  end
+
+  it "registers itself with MSpec for the :start event" do
+    MSpec.should_receive(:register).with(:start, @action)
+    @action.register
+  end
+
+  it "registers itself with MSpec for the :load event" do
+    MSpec.should_receive(:register).with(:load, @action)
+    @action.register
+  end
+
+  it "registers itself with MSpec for the :after event" do
+    MSpec.should_receive(:register).with(:after, @action)
+    @action.register
+  end
+end
+
+describe TagListAction, "#unregister" do
+  before :each do
+    MSpec.stub!(:unregister)
+    @action = TagListAction.new
+  end
+
+  it "unregisters itself with MSpec for the :start event" do
+    MSpec.should_receive(:unregister).with(:start, @action)
+    @action.unregister
+  end
+
+  it "unregisters itself with MSpec for the :load event" do
+    MSpec.should_receive(:unregister).with(:load, @action)
+    @action.unregister
+  end
+
+  it "unregisters itself with MSpec for the :after event" do
+    MSpec.should_receive(:unregister).with(:after, @action)
+    @action.unregister
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/runner/actions/tagpurge_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/runner/actions/tagpurge_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/runner/actions/tagpurge_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,154 @@
+require File.dirname(__FILE__) + '/../../spec_helper'
+require 'mspec/runner/actions/tagpurge'
+require 'mspec/runner/mspec'
+require 'mspec/runner/example'
+require 'mspec/runner/tag'
+
+describe TagPurgeAction, "#start" do
+  before :each do
+    @stdout = $stdout
+    $stdout = IOStub.new
+  end
+
+  after :each do
+    $stdout = @stdout
+  end
+
+  it "prints a banner" do
+    action = TagPurgeAction.new
+    action.start
+    $stdout.should == "\nRemoving tags not matching any specs\n\n"
+  end
+end
+
+describe TagPurgeAction, "#load" do
+  before :each do
+    @t1 = SpecTag.new "fails:I fail"
+    @t2 = SpecTag.new "unstable:I'm unstable"
+  end
+
+  it "creates a MatchFilter for all tags" do
+    MSpec.should_receive(:read_tags).and_return([@t1, @t2])
+    MatchFilter.should_receive(:new).with(nil, "I fail", "I'm unstable")
+    TagPurgeAction.new.load
+  end
+end
+
+describe TagPurgeAction, "#after" do
+  before :each do
+    @state = mock("ExampleState")
+    @state.stub!(:description).and_return("str")
+
+    @action = TagPurgeAction.new
+  end
+
+  it "does not save the description if the filter does not match" do
+    @action.should_receive(:===).with("str").and_return(false)
+    @action.after @state
+    @action.matching.should == []
+  end
+
+  it "saves the description if the filter matches" do
+    @action.should_receive(:===).with("str").and_return(true)
+    @action.after @state
+    @action.matching.should == ["str"]
+  end
+end
+
+describe TagPurgeAction, "#unload" do
+  before :each do
+    @stdout = $stdout
+    $stdout = IOStub.new
+
+    @t1 = SpecTag.new "fails:I fail"
+    @t2 = SpecTag.new "unstable:I'm unstable"
+    @t3 = SpecTag.new "fails:I'm unstable"
+
+    MSpec.stub!(:read_tags).and_return([@t1, @t2, @t3])
+    MSpec.stub!(:write_tags)
+
+    @state = mock("ExampleState")
+    @state.stub!(:description).and_return("I'm unstable")
+
+    @action = TagPurgeAction.new
+    @action.load
+    @action.after @state
+  end
+
+  after :each do
+    $stdout = @stdout
+  end
+
+  it "does not rewrite any tags if there were no tags for the specs" do
+    MSpec.should_receive(:read_tags).and_return([])
+    MSpec.should_receive(:delete_tags)
+    MSpec.should_not_receive(:write_tags)
+
+    @action.load
+    @action.after @state
+    @action.unload
+
+    $stdout.should == ""
+  end
+
+  it "rewrites tags that were matched" do
+    MSpec.should_receive(:write_tags).with([@t2, @t3])
+    @action.unload
+  end
+
+  it "prints tags that were not matched" do
+    @action.unload
+    $stdout.should == "I fail\n"
+  end
+end
+
+describe TagPurgeAction, "#unload" do
+  before :each do
+    @stdout = $stdout
+    $stdout = IOStub.new
+
+    MSpec.stub!(:read_tags).and_return([])
+
+    @state = mock("ExampleState")
+    @state.stub!(:description).and_return("I'm unstable")
+
+    @action = TagPurgeAction.new
+    @action.load
+    @action.after @state
+  end
+
+  after :each do
+    $stdout = @stdout
+  end
+
+  it "deletes the tag file if no tags were found" do
+    MSpec.should_not_receive(:write_tags)
+    MSpec.should_receive(:delete_tags)
+    @action.unload
+    $stdout.should == ""
+  end
+end
+
+describe TagPurgeAction, "#register" do
+  before :each do
+    MSpec.stub!(:register)
+    @action = TagPurgeAction.new
+  end
+
+  it "registers itself with MSpec for the :unload event" do
+    MSpec.should_receive(:register).with(:unload, @action)
+    @action.register
+  end
+end
+
+describe TagPurgeAction, "#unregister" do
+  before :each do
+    MSpec.stub!(:unregister)
+    @action = TagPurgeAction.new
+  end
+
+  it "unregisters itself with MSpec for the :unload event" do
+    MSpec.should_receive(:unregister).with(:unload, @action)
+    @action.unregister
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/runner/actions/tally_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/runner/actions/tally_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/runner/actions/tally_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,347 @@
+require File.dirname(__FILE__) + '/../../spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/runner/actions/tally'
+require 'mspec/runner/mspec'
+require 'mspec/runner/example'
+
+describe Tally, "#files!" do
+  before :each do
+    @tally = Tally.new
+  end
+
+  it "increments the count returned by #files" do
+    @tally.files! 3
+    @tally.files.should == 3
+    @tally.files!
+    @tally.files.should == 4
+  end
+end
+
+describe Tally, "#examples!" do
+  before :each do
+    @tally = Tally.new
+  end
+
+  it "increments the count returned by #examples" do
+    @tally.examples! 2
+    @tally.examples.should == 2
+    @tally.examples! 2
+    @tally.examples.should == 4
+  end
+end
+
+describe Tally, "#expectations!" do
+  before :each do
+    @tally = Tally.new
+  end
+
+  it "increments the count returned by #expectations" do
+    @tally.expectations!
+    @tally.expectations.should == 1
+    @tally.expectations! 3
+    @tally.expectations.should == 4
+  end
+end
+
+describe Tally, "#failures!" do
+  before :each do
+    @tally = Tally.new
+  end
+
+  it "increments the count returned by #failures" do
+    @tally.failures! 1
+    @tally.failures.should == 1
+    @tally.failures!
+    @tally.failures.should == 2
+  end
+end
+
+describe Tally, "#errors!" do
+  before :each do
+    @tally = Tally.new
+  end
+
+  it "increments the count returned by #errors" do
+    @tally.errors!
+    @tally.errors.should == 1
+    @tally.errors! 2
+    @tally.errors.should == 3
+  end
+end
+
+describe Tally, "#guards!" do
+  before :each do
+    @tally = Tally.new
+  end
+
+  it "increments the count returned by #guards" do
+    @tally.guards!
+    @tally.guards.should == 1
+    @tally.guards! 2
+    @tally.guards.should == 3
+  end
+end
+
+describe Tally, "#file" do
+  before :each do
+    @tally = Tally.new
+  end
+
+  it "returns a formatted string of the number of #files" do
+    @tally.file.should == "0 files"
+    @tally.files!
+    @tally.file.should == "1 file"
+    @tally.files!
+    @tally.file.should == "2 files"
+  end
+end
+
+describe Tally, "#example" do
+  before :each do
+    @tally = Tally.new
+  end
+
+  it "returns a formatted string of the number of #examples" do
+    @tally.example.should == "0 examples"
+    @tally.examples!
+    @tally.example.should == "1 example"
+    @tally.examples!
+    @tally.example.should == "2 examples"
+  end
+end
+
+describe Tally, "#expectation" do
+  before :each do
+    @tally = Tally.new
+  end
+
+  it "returns a formatted string of the number of #expectations" do
+    @tally.expectation.should == "0 expectations"
+    @tally.expectations!
+    @tally.expectation.should == "1 expectation"
+    @tally.expectations!
+    @tally.expectation.should == "2 expectations"
+  end
+end
+
+describe Tally, "#failure" do
+  before :each do
+    @tally = Tally.new
+  end
+
+  it "returns a formatted string of the number of #failures" do
+    @tally.failure.should == "0 failures"
+    @tally.failures!
+    @tally.failure.should == "1 failure"
+    @tally.failures!
+    @tally.failure.should == "2 failures"
+  end
+end
+
+describe Tally, "#error" do
+  before :each do
+    @tally = Tally.new
+  end
+
+  it "returns a formatted string of the number of #errors" do
+    @tally.error.should == "0 errors"
+    @tally.errors!
+    @tally.error.should == "1 error"
+    @tally.errors!
+    @tally.error.should == "2 errors"
+  end
+end
+
+describe Tally, "#guard" do
+  before :each do
+    @tally = Tally.new
+  end
+
+  it "returns a formatted string of the number of #guards" do
+    @tally.guard.should == "0 guards"
+    @tally.guards!
+    @tally.guard.should == "1 guard"
+    @tally.guards!
+    @tally.guard.should == "2 guards"
+  end
+end
+
+describe Tally, "#format" do
+  before :each do
+    @tally = Tally.new
+  end
+
+  after :each do
+    MSpec.clear_modes
+  end
+
+  it "returns a formatted string of counts" do
+    @tally.files!
+    @tally.examples! 2
+    @tally.expectations! 4
+    @tally.errors!
+    @tally.format.should == "1 file, 2 examples, 4 expectations, 0 failures, 1 error"
+  end
+
+  it "includes guards if MSpec is in verify mode" do
+    MSpec.register_mode :verify
+    @tally.files!
+    @tally.examples! 2
+    @tally.expectations! 4
+    @tally.errors!
+    @tally.guards!
+    @tally.format.should ==
+      "1 file, 2 examples, 4 expectations, 0 failures, 1 error, 1 guard"
+  end
+
+  it "includes guards if MSpec is in report mode" do
+    MSpec.register_mode :report
+    @tally.files!
+    @tally.examples! 2
+    @tally.expectations! 4
+    @tally.errors!
+    @tally.guards! 2
+    @tally.format.should ==
+      "1 file, 2 examples, 4 expectations, 0 failures, 1 error, 2 guards"
+  end
+
+  it "includes guards if MSpec is in report_on mode" do
+    MSpec.register_mode :report_on
+    @tally.files!
+    @tally.examples! 2
+    @tally.expectations! 4
+    @tally.errors!
+    @tally.guards! 2
+    @tally.format.should ==
+      "1 file, 2 examples, 4 expectations, 0 failures, 1 error, 2 guards"
+  end
+end
+
+describe TallyAction, "#counter" do
+  before :each do
+    @tally = TallyAction.new
+    @state = ExampleState.new("describe", "it")
+  end
+
+  it "returns the Tally object" do
+    @tally.counter.should be_kind_of(Tally)
+  end
+end
+
+describe TallyAction, "#load" do
+  before :each do
+    @tally = TallyAction.new
+    @state = ExampleState.new("describe", "it")
+  end
+
+  it "increments the count returned by Tally#files" do
+    @tally.load
+    @tally.counter.files.should == 1
+  end
+end
+
+describe TallyAction, "#expectation" do
+  before :each do
+    @tally = TallyAction.new
+    @state = ExampleState.new("describe", "it")
+  end
+
+  it "increments the count returned by Tally#expectations" do
+    @tally.expectation @state
+    @tally.counter.expectations.should == 1
+  end
+end
+
+describe TallyAction, "#example" do
+  before :each do
+    @tally = TallyAction.new
+    @state = ExampleState.new("describe", "it")
+  end
+
+  it "increments counts returned by Tally#examples" do
+    @tally.example @state, nil
+    @tally.counter.examples.should == 1
+    @tally.counter.expectations.should == 0
+    @tally.counter.failures.should == 0
+    @tally.counter.errors.should == 0
+  end
+end
+
+describe TallyAction, "#exception" do
+  before :each do
+    @tally = TallyAction.new
+    @state = ExampleState.new("describe", "it")
+  end
+
+  it "increments counts returned by Tally#failures" do
+    exc = ExceptionState.new nil, nil, ExpectationNotMetError.new("Failed!")
+    @tally.exception exc
+    @tally.counter.examples.should == 0
+    @tally.counter.expectations.should == 0
+    @tally.counter.failures.should == 1
+    @tally.counter.errors.should == 0
+  end
+end
+
+describe TallyAction, "#exception" do
+  before :each do
+    @tally = TallyAction.new
+    @state = ExampleState.new("describe", "it")
+  end
+
+  it "increments counts returned by Tally#errors" do
+    exc = ExceptionState.new nil, nil, Exception.new("Error!")
+    @tally.exception exc
+    @tally.counter.examples.should == 0
+    @tally.counter.expectations.should == 0
+    @tally.counter.failures.should == 0
+    @tally.counter.errors.should == 1
+  end
+end
+
+describe TallyAction, "#format" do
+  before :each do
+    @tally = TallyAction.new
+    @state = ExampleState.new("describe", "it")
+  end
+
+  it "returns a readable string of counts" do
+    @tally.load
+    @tally.example @state, nil
+    @tally.expectation @state
+    @tally.expectation @state
+    exc = ExceptionState.new nil, nil, ExpectationNotMetError.new("Failed!")
+    @tally.exception exc
+    @tally.format.should == "1 file, 1 example, 2 expectations, 1 failure, 0 errors"
+  end
+end
+
+describe TallyAction, "#register" do
+  before :each do
+    @tally = TallyAction.new
+    @state = ExampleState.new("describe", "it")
+  end
+
+  it "registers itself with MSpec for appropriate actions" do
+    MSpec.should_receive(:register).with(:load, @tally)
+    MSpec.should_receive(:register).with(:example, @tally)
+    MSpec.should_receive(:register).with(:exception, @tally)
+    MSpec.should_receive(:register).with(:expectation, @tally)
+    @tally.register
+  end
+end
+
+describe TallyAction, "#unregister" do
+  before :each do
+    @tally = TallyAction.new
+    @state = ExampleState.new("describe", "it")
+  end
+
+  it "unregisters itself with MSpec for appropriate actions" do
+    MSpec.should_receive(:unregister).with(:load, @tally)
+    MSpec.should_receive(:unregister).with(:exception, @tally)
+    MSpec.should_receive(:unregister).with(:example, @tally)
+    MSpec.should_receive(:unregister).with(:expectation, @tally)
+    @tally.unregister
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/runner/actions/timer_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/runner/actions/timer_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/runner/actions/timer_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,42 @@
+require File.dirname(__FILE__) + '/../../spec_helper'
+require 'mspec/runner/actions/timer'
+require 'mspec/runner/mspec'
+require 'time'
+
+describe TimerAction do
+  before :each do
+    @timer = TimerAction.new
+  end
+
+  it "responds to #start by recording the current time" do
+    Time.should_receive(:now)
+    @timer.start
+  end
+
+  it "responds to #finish by recording the current time" do
+    Time.should_receive(:now)
+    @timer.finish
+  end
+
+  it "responds to #elapsed by returning the difference between stop and start" do
+    Time.stub!(:now).and_return(Time.parse('10/18/2007 5:43:18'))
+    @timer.start
+    Time.stub!(:now).and_return(Time.parse('10/18/2007 5:43:51'))
+    @timer.finish
+    @timer.elapsed.should == 33
+  end
+
+  it "responds to #format by returning a readable string of elapsed time" do
+    Time.stub!(:now).and_return(Time.parse('10/18/2007 5:43:18'))
+    @timer.start
+    Time.stub!(:now).and_return(Time.parse('10/18/2007 5:43:51'))
+    @timer.finish
+    @timer.format.should == "Finished in 33.000000 seconds"
+  end
+
+  it "responds to #register by registering itself with MSpec for appropriate actions" do
+    MSpec.should_receive(:register).with(:start, @timer)
+    MSpec.should_receive(:register).with(:finish, @timer)
+    @timer.register
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/runner/context_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/runner/context_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/runner/context_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,993 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers/base'
+require 'mspec/runner/mspec'
+require 'mspec/mocks/mock'
+require 'mspec/runner/context'
+require 'mspec/runner/example'
+
+describe ContextState, "#describe" do
+  before :each do
+    @state = ContextState.new "C#m"
+    @proc = lambda { ScratchPad.record :a }
+    ScratchPad.clear
+  end
+
+  it "evaluates the passed block" do
+    @state.describe(&@proc)
+    ScratchPad.recorded.should == :a
+  end
+
+  it "evaluates the passed block via #protect" do
+    @state.should_receive(:protect).with("C#m", @proc, false)
+    @state.describe(&@proc)
+  end
+
+  it "registers #parent as the current MSpec ContextState" do
+    parent = ContextState.new ""
+    @state.parent = parent
+    MSpec.should_receive(:register_current).with(parent)
+    @state.describe { }
+  end
+
+  it "registers self with MSpec when #shared? is true" do
+    state = ContextState.new "something shared", :shared => true
+    MSpec.should_receive(:register_shared).with(state)
+    state.describe { }
+  end
+end
+
+describe ContextState, "#shared?" do
+  it "returns false when the ContextState is not shared" do
+    ContextState.new("").shared?.should be_false
+  end
+
+  it "returns true when the ContextState is shared" do
+    ContextState.new("", {:shared => true}).shared?.should be_true
+  end
+end
+
+describe ContextState, "#to_s" do
+  it "returns a description string for self when passed a Module" do
+    ContextState.new(Object).to_s.should == "Object"
+  end
+
+  it "returns a description string for self when passed a String" do
+    ContextState.new("SomeClass").to_s.should == "SomeClass"
+  end
+
+  it "returns a description string for self when passed a Module, String" do
+    ContextState.new(Object, "when empty").to_s.should == "Object when empty"
+  end
+
+  it "returns a description string for self when passed a Module and String beginning with '#'" do
+    ContextState.new(Object, "#to_s").to_s.should == "Object#to_s"
+  end
+
+  it "returns a description string for self when passed a Module and String beginning with '.'" do
+    ContextState.new(Object, ".to_s").to_s.should == "Object.to_s"
+  end
+
+  it "returns a description string for self when passed a Module and String beginning with '::'" do
+    ContextState.new(Object, "::to_s").to_s.should == "Object::to_s"
+  end
+end
+
+describe ContextState, "#description" do
+  before :each do
+    @state = ContextState.new "when empty"
+    @parent = ContextState.new "Toplevel"
+  end
+
+  it "returns a composite description string from self and all parents" do
+    @parent.description.should == "Toplevel"
+    @state.description.should == "when empty"
+    @state.parent = @parent
+    @state.description.should == "Toplevel when empty"
+  end
+end
+
+describe ContextState, "#it" do
+  before :each do
+    @state = ContextState.new ""
+    @proc = lambda { }
+
+    @ex = ExampleState.new("", "", &@proc)
+  end
+
+  it "creates an ExampleState instance for the block" do
+    ExampleState.should_receive(:new).with(@state, "it", @proc).and_return(@ex)
+    @state.describe(&@proc)
+    @state.it("it", &@proc)
+  end
+
+  it "calls registered :add actions" do
+    ExampleState.should_receive(:new).with(@state, "it", @proc).and_return(@ex)
+
+    add_action = mock("add")
+    add_action.should_receive(:add).with(@ex).and_return { ScratchPad.record :add }
+    MSpec.register :add, add_action
+
+    @state.it("it", &@proc)
+    ScratchPad.recorded.should == :add
+    MSpec.unregister :add, add_action
+  end
+end
+
+describe ContextState, "#examples" do
+  before :each do
+    @state = ContextState.new ""
+  end
+
+  it "returns a list of all examples in this ContextState" do
+    @state.it("first") { }
+    @state.it("second") { }
+    @state.examples.size.should == 2
+  end
+end
+
+describe ContextState, "#before" do
+  before :each do
+    @state = ContextState.new ""
+    @proc = lambda { }
+  end
+
+  it "records the block for :each" do
+    @state.before(:each, &@proc)
+    @state.before(:each).should == [@proc]
+  end
+
+  it "records the block for :all" do
+    @state.before(:all, &@proc)
+    @state.before(:all).should == [@proc]
+  end
+end
+
+describe ContextState, "#after" do
+  before :each do
+    @state = ContextState.new ""
+    @proc = lambda { }
+  end
+
+  it "records the block for :each" do
+    @state.after(:each, &@proc)
+    @state.after(:each).should == [@proc]
+  end
+
+  it "records the block for :all" do
+    @state.after(:all, &@proc)
+    @state.after(:all).should == [@proc]
+  end
+end
+
+describe ContextState, "#pre" do
+  before :each do
+    @a = lambda { }
+    @b = lambda { }
+    @c = lambda { }
+
+    parent = ContextState.new ""
+    parent.before(:each, &@c)
+    parent.before(:all, &@c)
+
+    @state = ContextState.new ""
+    @state.parent = parent
+  end
+
+  it "returns before(:each) actions in the order they were defined" do
+    @state.before(:each, &@a)
+    @state.before(:each, &@b)
+    @state.pre(:each).should == [@c, @a, @b]
+  end
+
+  it "returns before(:all) actions in the order they were defined" do
+    @state.before(:all, &@a)
+    @state.before(:all, &@b)
+    @state.pre(:all).should == [@c, @a, @b]
+  end
+end
+
+describe ContextState, "#post" do
+  before :each do
+    @a = lambda { }
+    @b = lambda { }
+    @c = lambda { }
+
+    parent = ContextState.new ""
+    parent.after(:each, &@c)
+    parent.after(:all, &@c)
+
+    @state = ContextState.new ""
+    @state.parent = parent
+  end
+
+  it "returns after(:each) actions in the reverse order they were defined" do
+    @state.after(:each, &@a)
+    @state.after(:each, &@b)
+    @state.post(:each).should == [@b, @a, @c]
+  end
+
+  it "returns after(:all) actions in the reverse order they were defined" do
+    @state.after(:all, &@a)
+    @state.after(:all, &@b)
+    @state.post(:all).should == [@b, @a, @c]
+  end
+end
+
+describe ContextState, "#protect" do
+  before :each do
+    ScratchPad.record []
+    @a = lambda { ScratchPad << :a }
+    @b = lambda { ScratchPad << :b }
+    @c = lambda { raise Exception, "Fail!" }
+  end
+
+  it "returns true and does execute any blocks if check and MSpec.mode?(:pretend) are true" do
+    MSpec.should_receive(:mode?).with(:pretend).and_return(true)
+    ContextState.new("").protect("message", [@a, @b]).should be_true
+    ScratchPad.recorded.should == []
+  end
+
+  it "executes the blocks if MSpec.mode?(:pretend) is false" do
+    MSpec.should_receive(:mode?).with(:pretend).and_return(false)
+    ContextState.new("").protect("message", [@a, @b])
+    ScratchPad.recorded.should == [:a, :b]
+  end
+
+  it "executes the blocks if check is false" do
+    ContextState.new("").protect("message", [@a, @b], false)
+    ScratchPad.recorded.should == [:a, :b]
+  end
+
+  it "returns true if none of the blocks raise an exception" do
+    ContextState.new("").protect("message", [@a, @b]).should be_true
+  end
+
+  it "returns false if any of the blocks raise an exception" do
+    ContextState.new("").protect("message", [@a, @c, @b]).should be_false
+  end
+end
+
+describe ContextState, "#parent=" do
+  before :each do
+    @state = ContextState.new ""
+    @parent = mock("describe")
+    @parent.stub!(:parent).and_return(nil)
+    @parent.stub!(:child)
+  end
+
+  it "does not set self as a child of parent if shared" do
+    @parent.should_not_receive(:child)
+    state = ContextState.new "", :shared => true
+    state.parent = @parent
+  end
+
+  it "sets self as a child of parent" do
+    @parent.should_receive(:child).with(@state)
+    @state.parent = @parent
+  end
+
+  it "creates the list of parents" do
+    @state.parent = @parent
+    @state.parents.should == [@parent, @state]
+  end
+end
+
+describe ContextState, "#parent" do
+  before :each do
+    @state = ContextState.new ""
+    @parent = mock("describe")
+    @parent.stub!(:parent).and_return(nil)
+    @parent.stub!(:child)
+  end
+
+  it "returns nil if parent has not been set" do
+    @state.parent.should be_nil
+  end
+
+  it "returns the parent" do
+    @state.parent = @parent
+    @state.parent.should == @parent
+  end
+end
+
+describe ContextState, "#parents" do
+  before :each do
+    @first = ContextState.new ""
+    @second = ContextState.new ""
+    @parent = mock("describe")
+    @parent.stub!(:parent).and_return(nil)
+    @parent.stub!(:child)
+  end
+
+  it "returns a list of all enclosing ContextState instances" do
+    @first.parent = @parent
+    @second.parent = @first
+    @second.parents.should == [@parent, @first, @second]
+  end
+end
+
+describe ContextState, "#child" do
+  before :each do
+    @first = ContextState.new ""
+    @second = ContextState.new ""
+    @parent = mock("describe")
+    @parent.stub!(:parent).and_return(nil)
+    @parent.stub!(:child)
+  end
+
+  it "adds the ContextState to the list of contained ContextStates" do
+    @first.child @second
+    @first.children.should == [@second]
+  end
+end
+
+describe ContextState, "#children" do
+  before :each do
+    @parent = ContextState.new ""
+    @first = ContextState.new ""
+    @second = ContextState.new ""
+  end
+
+  it "returns the list of directly contained ContextStates" do
+    @first.parent = @parent
+    @second.parent = @first
+    @parent.children.should == [@first]
+    @first.children.should == [@second]
+  end
+end
+
+describe ContextState, "#state" do
+  before :each do
+    MSpec.store :before, []
+    MSpec.store :after, []
+
+    @state = ContextState.new ""
+  end
+
+  it "returns nil if no spec is being executed" do
+    @state.state.should == nil
+  end
+
+  it "returns a ExampleState instance if an example is being executed" do
+    ScratchPad.record @state
+    @state.describe { }
+    @state.it("") { ScratchPad.record ScratchPad.recorded.state }
+    @state.process
+    @state.state.should == nil
+    ScratchPad.recorded.should be_kind_of(ExampleState)
+  end
+end
+
+describe ContextState, "#process" do
+  before :each do
+    MSpec.store :before, []
+    MSpec.store :after, []
+    MSpec.stub!(:register_current)
+
+    @state = ContextState.new ""
+    @state.describe { }
+
+    @a = lambda { ScratchPad << :a }
+    @b = lambda { ScratchPad << :b }
+    ScratchPad.record []
+  end
+
+  it "calls each before(:all) block" do
+    @state.before(:all, &@a)
+    @state.before(:all, &@b)
+    @state.it("") { }
+    @state.process
+    ScratchPad.recorded.should == [:a, :b]
+  end
+
+  it "calls each after(:all) block" do
+    @state.after(:all, &@a)
+    @state.after(:all, &@b)
+    @state.it("") { }
+    @state.process
+    ScratchPad.recorded.should == [:b, :a]
+  end
+
+  it "calls each it block" do
+    @state.it("one", &@a)
+    @state.it("two", &@b)
+    @state.process
+    ScratchPad.recorded.should == [:a, :b]
+  end
+
+  it "does not call the #it block if #filtered? returns true" do
+    @state.it("one", &@a)
+    @state.it("two", &@b)
+    @state.examples.first.stub!(:filtered?).and_return(true)
+    @state.process
+    ScratchPad.recorded.should == [:b]
+  end
+
+  it "calls each before(:each) block" do
+    @state.before(:each, &@a)
+    @state.before(:each, &@b)
+    @state.it("") { }
+    @state.process
+    ScratchPad.recorded.should == [:a, :b]
+  end
+
+  it "calls each after(:each) block" do
+    @state.after(:each, &@a)
+    @state.after(:each, &@b)
+    @state.it("") { }
+    @state.process
+    ScratchPad.recorded.should == [:b, :a]
+  end
+
+  it "calls Mock.cleanup for each it block" do
+    @state.it("") { }
+    @state.it("") { }
+    Mock.should_receive(:cleanup).twice
+    @state.process
+  end
+
+  it "calls Mock.verify_count for each it block" do
+    @state.it("") { }
+    @state.it("") { }
+    Mock.should_receive(:verify_count).twice
+    @state.process
+  end
+
+  it "calls the describe block" do
+    ScratchPad.record []
+    @state.describe { ScratchPad << :a }
+    @state.process
+    ScratchPad.recorded.should == [:a]
+  end
+
+  it "creates a new ExampleState instance for each example" do
+    ScratchPad.record @state
+    @state.describe { }
+    @state.it("it") { ScratchPad.record ScratchPad.recorded.state }
+    @state.process
+    ScratchPad.recorded.should be_kind_of(ExampleState)
+  end
+
+  it "clears the expectations flag before evaluating the #it block" do
+    MSpec.clear_expectations
+    MSpec.should_receive(:clear_expectations)
+    @state.it("it") { ScratchPad.record MSpec.expectation? }
+    @state.process
+    ScratchPad.recorded.should be_false
+  end
+
+  it "shuffles the spec list if MSpec.randomize? is true" do
+    MSpec.randomize
+    MSpec.should_receive(:shuffle)
+    @state.it("") { }
+    @state.process
+    MSpec.randomize false
+  end
+
+  it "sets the current MSpec ContextState" do
+    MSpec.should_receive(:register_current).with(@state)
+    @state.process
+  end
+
+  it "resets the current MSpec ContextState to nil when there are examples" do
+    MSpec.should_receive(:register_current).with(nil)
+    @state.it("") { }
+    @state.process
+  end
+
+  it "resets the current MSpec ContextState to nil when there are no examples" do
+    MSpec.should_receive(:register_current).with(nil)
+    @state.process
+  end
+
+  it "call #process on children when there are examples" do
+    child = ContextState.new ""
+    child.should_receive(:process)
+    @state.child child
+    @state.it("") { }
+    @state.process
+  end
+
+  it "call #process on children when there are no examples" do
+    child = ContextState.new ""
+    child.should_receive(:process)
+    @state.child child
+    @state.process
+  end
+end
+
+describe ContextState, "#process" do
+  before :each do
+    MSpec.store :exception, []
+
+    @state = ContextState.new ""
+    @state.describe { }
+
+    action = mock("action")
+    def action.exception(exc)
+      ScratchPad.record :exception if exc.exception.is_a? ExpectationNotFoundError
+    end
+    MSpec.register :exception, action
+
+    MSpec.clear_expectations
+    ScratchPad.clear
+  end
+
+  after :each do
+    MSpec.store :exception, nil
+  end
+
+  it "raises an ExpectationNotFoundError if an #it block does not contain an expectation" do
+    @state.it("it") { }
+    @state.process
+    ScratchPad.recorded.should == :exception
+  end
+
+  it "does not raise an ExpectationNotFoundError if an #it block does contain an expectation" do
+    @state.it("it") { MSpec.expectation }
+    @state.process
+    ScratchPad.recorded.should be_nil
+  end
+
+  it "does not raise an ExpectationNotFoundError if the #it block causes a failure" do
+    @state.it("it") { raise Exception, "Failed!" }
+    @state.process
+    ScratchPad.recorded.should be_nil
+  end
+end
+
+describe ContextState, "#process" do
+  before :each do
+    MSpec.store :example, []
+
+    @state = ContextState.new ""
+    @state.describe { }
+
+    example = mock("example")
+    def example.example(state, spec)
+      ScratchPad << state << spec
+    end
+    MSpec.register :example, example
+
+    ScratchPad.record []
+  end
+
+  after :each do
+    MSpec.store :example, nil
+  end
+
+  it "calls registered :example actions with the current ExampleState and block" do
+    @state.it("") { MSpec.expectation }
+    @state.process
+
+    ScratchPad.recorded.first.should be_kind_of(ExampleState)
+    ScratchPad.recorded.last.should be_kind_of(Proc)
+  end
+
+  it "does not call registered example actions if the example has no block" do
+    @state.it("empty example")
+    @state.process
+    ScratchPad.recorded.should == []
+  end
+end
+
+describe ContextState, "#process" do
+  before :each do
+    MSpec.store :before, []
+    MSpec.store :after, []
+
+    @state = ContextState.new ""
+    @state.describe { }
+    @state.it("") { MSpec.expectation }
+  end
+
+  after :each do
+    MSpec.store :before, nil
+    MSpec.store :after, nil
+  end
+
+  it "calls registered :before actions with the current ExampleState instance" do
+    before = mock("before")
+    before.should_receive(:before).and_return {
+      ScratchPad.record :before
+      @spec_state = @state.state
+    }
+    MSpec.register :before, before
+    @state.process
+    ScratchPad.recorded.should == :before
+    @spec_state.should be_kind_of(ExampleState)
+  end
+
+  it "calls registered :after actions with the current ExampleState instance" do
+    after = mock("after")
+    after.should_receive(:after).and_return {
+      ScratchPad.record :after
+      @spec_state = @state.state
+    }
+    MSpec.register :after, after
+    @state.process
+    ScratchPad.recorded.should == :after
+    @spec_state.should be_kind_of(ExampleState)
+  end
+end
+
+describe ContextState, "#process" do
+  before :each do
+    MSpec.store :enter, []
+    MSpec.store :leave, []
+
+    @state = ContextState.new "C#m"
+    @state.describe { }
+    @state.it("") { MSpec.expectation }
+  end
+
+  after :each do
+    MSpec.store :enter, nil
+    MSpec.store :leave, nil
+  end
+
+  it "calls registered :enter actions with the current #describe string" do
+    enter = mock("enter")
+    enter.should_receive(:enter).with("C#m").and_return { ScratchPad.record :enter }
+    MSpec.register :enter, enter
+    @state.process
+    ScratchPad.recorded.should == :enter
+  end
+
+  it "calls registered :leave actions" do
+    leave = mock("leave")
+    leave.should_receive(:leave).and_return { ScratchPad.record :leave }
+    MSpec.register :leave, leave
+    @state.process
+    ScratchPad.recorded.should == :leave
+  end
+end
+
+describe ContextState, "#process when an exception is raised in before(:all)" do
+  before :each do
+    MSpec.store :before, []
+    MSpec.store :after, []
+
+    @state = ContextState.new ""
+    @state.describe { }
+
+    @a = lambda { ScratchPad << :a }
+    @b = lambda { ScratchPad << :b }
+    ScratchPad.record []
+
+    @state.before(:all) { raise Exception, "Fail!" }
+  end
+
+  after :each do
+    MSpec.store :before, nil
+    MSpec.store :after, nil
+  end
+
+  it "does not call before(:each)" do
+    @state.before(:each, &@a)
+    @state.it("") { }
+    @state.process
+    ScratchPad.recorded.should == []
+  end
+
+  it "does not call the it block" do
+    @state.it("one", &@a)
+    @state.process
+    ScratchPad.recorded.should == []
+  end
+
+  it "does not call after(:each)" do
+    @state.after(:each, &@a)
+    @state.it("") { }
+    @state.process
+    ScratchPad.recorded.should == []
+  end
+
+  it "does not call after(:each)" do
+    @state.after(:all, &@a)
+    @state.it("") { }
+    @state.process
+    ScratchPad.recorded.should == []
+  end
+
+  it "does not call Mock.verify_count" do
+    @state.it("") { }
+    Mock.should_not_receive(:verify_count)
+    @state.process
+  end
+
+  it "calls Mock.cleanup" do
+    @state.it("") { }
+    Mock.should_receive(:cleanup)
+    @state.process
+  end
+end
+
+describe ContextState, "#process when an exception is raised in before(:each)" do
+  before :each do
+    MSpec.store :before, []
+    MSpec.store :after, []
+
+    @state = ContextState.new ""
+    @state.describe { }
+
+    @a = lambda { ScratchPad << :a }
+    @b = lambda { ScratchPad << :b }
+    ScratchPad.record []
+
+    @state.before(:each) { raise Exception, "Fail!" }
+  end
+
+  after :each do
+    MSpec.store :before, nil
+    MSpec.store :after, nil
+  end
+
+  it "does not call the it block" do
+    @state.it("one", &@a)
+    @state.process
+    ScratchPad.recorded.should == []
+  end
+
+  it "does not call after(:each)" do
+    @state.after(:each, &@a)
+    @state.it("") { }
+    @state.process
+    ScratchPad.recorded.should == []
+  end
+
+  it "does not call Mock.verify_count" do
+    @state.it("") { }
+    Mock.should_not_receive(:verify_count)
+    @state.process
+  end
+end
+
+describe ContextState, "#process in pretend mode" do
+  before :all do
+    MSpec.register_mode :pretend
+  end
+
+  after :all do
+    MSpec.register_mode nil
+  end
+
+  before :each do
+    ScratchPad.clear
+    MSpec.store :before, []
+    MSpec.store :after, []
+
+    @state = ContextState.new ""
+    @state.describe { }
+    @state.it("") { }
+  end
+
+  after :each do
+    MSpec.store :before, nil
+    MSpec.store :after, nil
+  end
+
+  it "calls registered :before actions with the current ExampleState instance" do
+    before = mock("before")
+    before.should_receive(:before).and_return {
+      ScratchPad.record :before
+      @spec_state = @state.state
+    }
+    MSpec.register :before, before
+    @state.process
+    ScratchPad.recorded.should == :before
+    @spec_state.should be_kind_of(ExampleState)
+  end
+
+  it "calls registered :after actions with the current ExampleState instance" do
+    after = mock("after")
+    after.should_receive(:after).and_return {
+      ScratchPad.record :after
+      @spec_state = @state.state
+    }
+    MSpec.register :after, after
+    @state.process
+    ScratchPad.recorded.should == :after
+    @spec_state.should be_kind_of(ExampleState)
+  end
+end
+
+describe ContextState, "#process in pretend mode" do
+  before :all do
+    MSpec.register_mode :pretend
+  end
+
+  after :all do
+    MSpec.register_mode nil
+  end
+
+  before :each do
+    MSpec.store :before, []
+    MSpec.store :after, []
+
+    @state = ContextState.new ""
+    @state.describe { }
+
+    @a = lambda { ScratchPad << :a }
+    @b = lambda { ScratchPad << :b }
+    ScratchPad.record []
+  end
+
+  it "calls the describe block" do
+    ScratchPad.record []
+    @state.describe { ScratchPad << :a }
+    @state.process
+    ScratchPad.recorded.should == [:a]
+  end
+
+  it "does not call any before(:all) block" do
+    @state.before(:all, &@a)
+    @state.before(:all, &@b)
+    @state.it("") { }
+    @state.process
+    ScratchPad.recorded.should == []
+  end
+
+  it "does not call any after(:all) block" do
+    @state.after(:all, &@a)
+    @state.after(:all, &@b)
+    @state.it("") { }
+    @state.process
+    ScratchPad.recorded.should == []
+  end
+
+  it "does not call any it block" do
+    @state.it("one", &@a)
+    @state.it("two", &@b)
+    @state.process
+    ScratchPad.recorded.should == []
+  end
+
+  it "does not call any before(:each) block" do
+    @state.before(:each, &@a)
+    @state.before(:each, &@b)
+    @state.it("") { }
+    @state.process
+    ScratchPad.recorded.should == []
+  end
+
+  it "does not call any after(:each) block" do
+    @state.after(:each, &@a)
+    @state.after(:each, &@b)
+    @state.it("") { }
+    @state.process
+    ScratchPad.recorded.should == []
+  end
+
+  it "does not call Mock.cleanup" do
+    @state.it("") { }
+    @state.it("") { }
+    Mock.should_not_receive(:cleanup)
+    @state.process
+  end
+end
+
+describe ContextState, "#process in pretend mode" do
+  before :all do
+    MSpec.register_mode :pretend
+  end
+
+  after :all do
+    MSpec.register_mode nil
+  end
+
+  before :each do
+    MSpec.store :enter, []
+    MSpec.store :leave, []
+
+    @state = ContextState.new ""
+    @state.describe { }
+    @state.it("") { }
+  end
+
+  after :each do
+    MSpec.store :enter, nil
+    MSpec.store :leave, nil
+  end
+
+  it "calls registered :enter actions with the current #describe string" do
+    enter = mock("enter")
+    enter.should_receive(:enter).and_return { ScratchPad.record :enter }
+    MSpec.register :enter, enter
+    @state.process
+    ScratchPad.recorded.should == :enter
+  end
+
+  it "calls registered :leave actions" do
+    leave = mock("leave")
+    leave.should_receive(:leave).and_return { ScratchPad.record :leave }
+    MSpec.register :leave, leave
+    @state.process
+    ScratchPad.recorded.should == :leave
+  end
+end
+
+describe ContextState, "#it_should_behave_like" do
+  before :each do
+    @shared = ContextState.new("", :shared => true)
+    MSpec.stub!(:retrieve_shared).and_return(@shared)
+
+    @state = ContextState.new ""
+    @a = lambda { }
+    @b = lambda { }
+  end
+
+  it "raises an Exception if unable to find the shared ContextState" do
+    MSpec.should_receive(:retrieve_shared).and_return(nil)
+    lambda { @state.it_should_behave_like "this" }.should raise_error(Exception)
+  end
+
+  it "adds examples from the shared ContextState" do
+    @shared.it "some", &@a
+    @shared.it "thing", &@b
+    @state.it_should_behave_like ""
+    @state.examples.should include(*@shared.examples)
+  end
+
+  it "sets the containing ContextState for the examples" do
+    @shared.it "some", &@a
+    @shared.it "thing", &@b
+    @shared.examples.each { |ex| ex.should_receive(:context=).with(@state) }
+    @state.it_should_behave_like ""
+  end
+
+  it "adds before(:all) blocks from the shared ContextState" do
+    @shared.before :all, &@a
+    @shared.before :all, &@b
+    @state.it_should_behave_like ""
+    @state.before(:all).should include(*@shared.before(:all))
+  end
+
+  it "adds before(:each) blocks from the shared ContextState" do
+    @shared.before :each, &@a
+    @shared.before :each, &@b
+    @state.it_should_behave_like ""
+    @state.before(:each).should include(*@shared.before(:each))
+  end
+
+  it "adds after(:each) blocks from the shared ContextState" do
+    @shared.after :each, &@a
+    @shared.after :each, &@b
+    @state.it_should_behave_like ""
+    @state.after(:each).should include(*@shared.after(:each))
+  end
+
+  it "adds after(:all) blocks from the shared ContextState" do
+    @shared.after :all, &@a
+    @shared.after :all, &@b
+    @state.it_should_behave_like ""
+    @state.after(:all).should include(*@shared.after(:all))
+  end
+end
+
+describe ContextState, "#filter_examples" do
+  before :each do
+    @state = ContextState.new ""
+    @state.it("one") { }
+    @state.it("two") { }
+  end
+
+  it "removes examples that are filtered" do
+    @state.examples.first.stub!(:filtered?).and_return(true)
+    @state.examples.size.should == 2
+    @state.filter_examples
+    @state.examples.size.should == 1
+  end
+
+  it "returns true if there are remaining examples to evaluate" do
+    @state.examples.first.stub!(:filtered?).and_return(true)
+    @state.filter_examples.should be_true
+  end
+
+  it "returns false if there are no remaining examples to evaluate" do
+    @state.examples.first.stub!(:filtered?).and_return(true)
+    @state.examples.last.stub!(:filtered?).and_return(true)
+    @state.filter_examples.should be_false
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/runner/example_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/runner/example_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/runner/example_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,112 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/matchers/base'
+require 'mspec/runner/mspec'
+require 'mspec/mocks/mock'
+require 'mspec/runner/example'
+
+describe ExampleState do
+  it "is initialized with the ContextState, #it string, and #it block" do
+    prc = lambda { }
+    context = ContextState.new ""
+    ExampleState.new(context, "does", prc).should be_kind_of(ExampleState)
+  end
+end
+
+describe ExampleState, "#describe" do
+  before :each do
+    @context = ContextState.new Object, "#to_s"
+    @state = ExampleState.new @context, "it"
+  end
+
+  it "returns the ContextState#description" do
+    @state.describe.should == @context.description
+  end
+end
+
+describe ExampleState, "#it" do
+  before :each do
+    @state = ExampleState.new ContextState.new("describe"), "it"
+  end
+
+  it "returns the argument to the #it block" do
+    @state.it.should == "it"
+  end
+end
+
+describe ExampleState, "#context=" do
+  before :each do
+    @state = ExampleState.new ContextState.new("describe"), "it"
+    @context = ContextState.new "New#context"
+  end
+
+  it "sets the containing ContextState" do
+    @state.context = @context
+    @state.context.should == @context
+  end
+
+  it "resets the description" do
+    @state.description.should == "describe it"
+    @state.context = @context
+    @state.description.should == "New#context it"
+  end
+end
+
+describe ExampleState, "#example" do
+  before :each do
+    @proc = lambda { }
+    @state = ExampleState.new ContextState.new("describe"), "it", @proc
+  end
+
+  it "returns the #it block" do
+    @state.example.should == @proc
+  end
+end
+
+describe ExampleState, "#filtered?" do
+  before :each do
+    MSpec.store :include, nil
+    MSpec.store :exclude, nil
+
+    @state = ExampleState.new ContextState.new("describe"), "it"
+    @filter = mock("filter")
+  end
+
+  it "returns false if MSpec include filters list is empty" do
+    @state.filtered?.should == false
+  end
+
+  it "returns false if MSpec include filters match this spec" do
+    @filter.should_receive(:===).and_return(true)
+    MSpec.register :include, @filter
+    @state.filtered?.should == false
+  end
+
+  it "returns true if MSpec include filters do not match this spec" do
+    @filter.should_receive(:===).and_return(false)
+    MSpec.register :include, @filter
+    @state.filtered?.should == true
+  end
+
+  it "returns false if MSpec exclude filters list is empty" do
+    @state.filtered?.should == false
+  end
+
+  it "returns false if MSpec exclude filters do not match this spec" do
+    @filter.should_receive(:===).and_return(false)
+    MSpec.register :exclude, @filter
+    @state.filtered?.should == false
+  end
+
+  it "returns true if MSpec exclude filters match this spec" do
+    @filter.should_receive(:===).and_return(true)
+    MSpec.register :exclude, @filter
+    @state.filtered?.should == true
+  end
+
+  it "returns true if MSpec include and exclude filters match this spec" do
+    @filter.should_receive(:===).twice.and_return(true)
+    MSpec.register :include, @filter
+    MSpec.register :exclude, @filter
+    @state.filtered?.should == true
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/runner/exception_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/runner/exception_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/runner/exception_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,134 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/runner/example'
+require 'mspec/runner/exception'
+
+describe ExceptionState, "#initialize" do
+  it "takes a state, location (e.g. before :each), and exception" do
+    context = ContextState.new "Class#method"
+    state = ExampleState.new context, "does something"
+    exc = Exception.new "Fail!"
+    ExceptionState.new(state, "location", exc).should be_kind_of(ExceptionState)
+  end
+end
+
+describe ExceptionState, "#description" do
+  before :each do
+    context = ContextState.new "Class#method"
+    @state = ExampleState.new context, "does something"
+  end
+
+  it "returns the state description if state was not nil" do
+    exc = ExceptionState.new(@state, nil, nil)
+    exc.description.should == "Class#method does something"
+  end
+
+  it "returns the location if it is not nil and description is nil" do
+    exc = ExceptionState.new(nil, "location", nil)
+    exc.description.should == "An exception occurred during: location"
+  end
+
+  it "returns both description and location if neither are nil" do
+    exc = ExceptionState.new(@state, "location", nil)
+    exc.description.should == "An exception occurred during: location\nClass#method does something"
+  end
+end
+
+describe ExceptionState, "#describe" do
+  before :each do
+    context = ContextState.new "Class#method"
+    @state = ExampleState.new context, "does something"
+  end
+
+  it "returns the ExampleState#describe string if created with a non-nil state" do
+    ExceptionState.new(@state, nil, nil).describe.should == @state.describe
+  end
+
+  it "returns an empty string if created with a nil state" do
+    ExceptionState.new(nil, nil, nil).describe.should == ""
+  end
+end
+
+describe ExceptionState, "#it" do
+  before :each do
+    context = ContextState.new "Class#method"
+    @state = ExampleState.new context, "does something"
+  end
+
+  it "returns the ExampleState#it string if created with a non-nil state" do
+    ExceptionState.new(@state, nil, nil).it.should == @state.it
+  end
+
+  it "returns an empty string if created with a nil state" do
+    ExceptionState.new(nil, nil, nil).it.should == ""
+  end
+end
+
+describe ExceptionState, "#failure?" do
+  before :each do
+    @state = ExampleState.new ContextState.new("C#m"), "works"
+  end
+
+  it "returns true if the exception is an ExpectationNotMetError" do
+    exc = ExceptionState.new @state, "", ExpectationNotMetError.new("Fail!")
+    exc.failure?.should be_true
+  end
+
+  it "returns true if the exception is an ExpectationNotFoundError" do
+    exc = ExceptionState.new @state, "", ExpectationNotFoundError.new("Fail!")
+    exc.failure?.should be_true
+  end
+
+  it "returns false if the exception is not an ExpectationNotMetError or an ExpectationNotFoundError" do
+    exc = ExceptionState.new @state, "", Exception.new("Fail!")
+    exc.failure?.should be_false
+  end
+end
+
+describe ExceptionState, "#message" do
+  it "returns <No message> if the exception message is empty" do
+    exc = ExceptionState.new @state, "", Exception.new("")
+    exc.message.should == "<No message>"
+  end
+
+  it "returns the message without exception class when the exception is an ExpectationNotMetError" do
+    exc = ExceptionState.new @state, "", ExpectationNotMetError.new("Fail!")
+    exc.message.should == "Fail!"
+  end
+
+  it "returns ExpectationNotFoundError#message when the exception is an ExpectationNotFoundError" do
+    e = ExpectationNotFoundError.new
+    exc = ExceptionState.new @state, "", e
+    exc.message.should == e.message
+  end
+
+  it "returns the message with exception class when the exception is not an ExpectationNotMetError or an ExpectationNotFoundError" do
+    exc = ExceptionState.new @state, "", Exception.new("Fail!")
+    exc.message.should == "Exception: Fail!"
+  end
+end
+
+describe ExceptionState, "#backtrace" do
+  before :each do
+    @action = mock("action")
+    def @action.exception(exc)
+      ScratchPad.record exc.exception
+    end
+    MSpec.register :exception, @action
+
+    ScratchPad.clear
+    MSpec.protect("") { raise Exception }
+
+    @exc = ExceptionState.new @state, "", ScratchPad.recorded
+  end
+
+  it "returns a string representation of the exception backtrace" do
+    @exc.backtrace.should be_kind_of(String)
+  end
+
+  it "strips MSpec files from the backtrace" do
+    @exc.backtrace.split("\n").each do |line|
+      line.should_not =~ ExceptionState::PATH
+    end
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/runner/filters/a.yaml
===================================================================
--- MacRuby/branches/experimental/mspec/spec/runner/filters/a.yaml	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/runner/filters/a.yaml	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,4 @@
+---
+A#: 
+- a
+- aa

Added: MacRuby/branches/experimental/mspec/spec/runner/filters/b.yaml
===================================================================
--- MacRuby/branches/experimental/mspec/spec/runner/filters/b.yaml	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/runner/filters/b.yaml	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,11 @@
+---
+B.: 
+- b
+- bb
+B::C#:
+- b!
+- b=
+- b?
+- "-"
+- "[]"
+- "[]="

Added: MacRuby/branches/experimental/mspec/spec/runner/filters/match_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/runner/filters/match_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/runner/filters/match_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,44 @@
+require File.dirname(__FILE__) + '/../../spec_helper'
+require 'mspec/runner/mspec'
+require 'mspec/runner/filters/match'
+
+describe MatchFilter, "#to_regexp" do
+  before :each do
+    @filter = MatchFilter.new nil
+  end
+
+  it "converts its arguments to Regexp instances" do
+    @filter.to_regexp('a', 'b', 'c').should == [/a/, /b/, /c/]
+  end
+end
+
+describe MatchFilter, "#===" do
+  before :each do
+    @filter = MatchFilter.new nil, 'a', 'b', 'c'
+  end
+
+  it "returns true if the argument matches any of the #initialize strings" do
+    @filter.===('aaa').should == true
+    @filter.===('bccb').should == true
+  end
+
+  it "returns false if the argument matches none of the #initialize strings" do
+    @filter.===('d').should == false
+  end
+end
+
+describe MatchFilter, "#register" do
+  it "registers itself with MSpec for the designated action list" do
+    filter = MatchFilter.new :include
+    MSpec.should_receive(:register).with(:include, filter)
+    filter.register
+  end
+end
+
+describe MatchFilter, "#unregister" do
+  it "unregisters itself with MSpec for the designated action list" do
+    filter = MatchFilter.new :exclude
+    MSpec.should_receive(:unregister).with(:exclude, filter)
+    filter.unregister
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/runner/filters/profile_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/runner/filters/profile_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/runner/filters/profile_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,117 @@
+require File.dirname(__FILE__) + '/../../spec_helper'
+require 'mspec/runner/mspec'
+require 'mspec/runner/filters/profile'
+
+describe ProfileFilter, "#find" do
+  before :each do
+    @filter = ProfileFilter.new nil
+    File.stub!(:exist?).and_return(false)
+    @file = "rails.yaml"
+  end
+
+  it "attempts to locate the file through the expanded path name" do
+    File.should_receive(:expand_path).with(@file).and_return(@file)
+    File.should_receive(:exist?).with(@file).and_return(true)
+    @filter.find(@file).should == @file
+  end
+
+  it "attemps to locate the file in 'spec/profiles'" do
+    path = File.join "spec/profiles", @file
+    File.should_receive(:exist?).with(path).and_return(true)
+    @filter.find(@file).should == path
+  end
+
+  it "attemps to locate the file in 'spec'" do
+    path = File.join "spec", @file
+    File.should_receive(:exist?).with(path).and_return(true)
+    @filter.find(@file).should == path
+  end
+
+  it "attemps to locate the file in 'profiles'" do
+    path = File.join "profiles", @file
+    File.should_receive(:exist?).with(path).and_return(true)
+    @filter.find(@file).should == path
+  end
+
+  it "attemps to locate the file in '.'" do
+    path = File.join ".", @file
+    File.should_receive(:exist?).with(path).and_return(true)
+    @filter.find(@file).should == path
+  end
+end
+
+describe ProfileFilter, "#parse" do
+  before :each do
+    @filter = ProfileFilter.new nil
+    @file = File.open(File.dirname(__FILE__) + "/b.yaml", "r")
+  end
+
+  after :each do
+    @file.close
+  end
+
+  it "creates a Hash of the contents of the YAML file" do
+    @filter.parse(@file).should == {
+      "B." => ["b", "bb"],
+      "B::C#" => ["b!", "b=", "b?", "-", "[]", "[]="]
+    }
+  end
+end
+
+describe ProfileFilter, "#load" do
+  before :each do
+    @filter = ProfileFilter.new nil
+    @files = [
+      File.dirname(__FILE__) + "/a.yaml",
+      File.dirname(__FILE__) + "/b.yaml"
+      ]
+  end
+
+  it "generates a composite hash from multiple YAML files" do
+    @filter.load(*@files).should == {
+      "A#"    => ["a", "aa"],
+      "B."    => ["b", "bb"],
+      "B::C#" => ["b!", "b=", "b?", "-", "[]", "[]="]
+    }
+  end
+end
+
+describe ProfileFilter, "#===" do
+  before :each do
+    @filter = ProfileFilter.new nil
+    @filter.stub!(:load).and_return({ "A#" => ["[]=", "a", "a!", "a?", "aa="]})
+    @filter.send :initialize, nil
+  end
+
+  it "returns true if the spec description is for a method in the profile" do
+    @filter.===("The A#[]= method").should == true
+    @filter.===("A#a returns").should == true
+    @filter.===("A#a! replaces").should == true
+    @filter.===("A#a? returns").should == true
+    @filter.===("A#aa= raises").should == true
+  end
+
+  it "returns false if the spec description is for a method not in the profile" do
+    @filter.===("The A#[] method").should == false
+    @filter.===("B#a returns").should == false
+    @filter.===("A.a! replaces").should == false
+    @filter.===("AA#a? returns").should == false
+    @filter.===("A#aa raises").should == false
+  end
+end
+
+describe ProfileFilter, "#register" do
+  it "registers itself with MSpec for the designated action list" do
+    filter = ProfileFilter.new :include
+    MSpec.should_receive(:register).with(:include, filter)
+    filter.register
+  end
+end
+
+describe ProfileFilter, "#unregister" do
+  it "unregisters itself with MSpec for the designated action list" do
+    filter = ProfileFilter.new :exclude
+    MSpec.should_receive(:unregister).with(:exclude, filter)
+    filter.unregister
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/runner/filters/regexp_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/runner/filters/regexp_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/runner/filters/regexp_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,13 @@
+require File.dirname(__FILE__) + '/../../spec_helper'
+require 'mspec/runner/mspec'
+require 'mspec/runner/filters/regexp'
+
+describe RegexpFilter, "#to_regexp" do
+  before :each do
+    @filter = RegexpFilter.new nil
+  end
+
+  it "converts its arguments to Regexp instances" do
+    @filter.to_regexp('a(b|c)', 'b[^ab]', 'cc?').should == [/a(b|c)/, /b[^ab]/, /cc?/]
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/runner/filters/tag_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/runner/filters/tag_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/runner/filters/tag_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,77 @@
+require File.dirname(__FILE__) + '/../../spec_helper'
+require 'mspec/runner/mspec'
+require 'mspec/runner/filters/match'
+require 'mspec/runner/filters/tag'
+
+describe TagFilter, "#load" do
+  before :each do
+    @match = mock("match filter", :null_object => true)
+    @filter = TagFilter.new :include, "tag", "key"
+    @tag = SpecTag.new "tag(comment):description"
+    MSpec.stub!(:read_tags).and_return([@tag])
+    MSpec.stub!(:register)
+  end
+
+  it "loads tags from the tag file" do
+    MSpec.should_receive(:read_tags).with(["tag", "key"]).and_return([])
+    @filter.load
+  end
+
+  it "creates a MatchFilter from the descriptions matching the tags" do
+    MatchFilter.should_receive(:new).with(:include, "description").and_return(@match)
+    @filter.load
+  end
+
+  it "creates an empty MatchFilter if no tags were found" do
+    MSpec.should_receive(:read_tags).and_return([])
+    MatchFilter.should_receive(:new).with(:include).and_return(@match)
+    @filter.load
+  end
+
+  it "registers the MatchFilter if there were tags found in the tag file" do
+    @match.should_receive(:register)
+    MatchFilter.should_receive(:new).with(:include, "description").and_return(@match)
+    @filter.load
+  end
+
+  it "registers the empty MatchFilter if no tags were found" do
+    @match.should_receive(:register)
+    MSpec.should_receive(:read_tags).and_return([])
+    MatchFilter.should_receive(:new).with(:include).and_return(@match)
+    @filter.load
+  end
+end
+
+describe TagFilter, "#unload" do
+  before :each do
+    @filter = TagFilter.new :include, "tag", "key"
+    @tag = SpecTag.new "tag(comment):description"
+    MSpec.stub!(:read_tags).and_return([@tag])
+  end
+
+  it "unregisters the MatchFilter if one was registered" do
+    match = mock("match filter", :null_object => true)
+    match.should_receive(:unregister)
+    MatchFilter.stub!(:new).with(:include, "description").and_return(match)
+    @filter.load
+    @filter.unload
+  end
+end
+
+describe TagFilter, "#register" do
+  it "registers itself with MSpec for the :load, :unload actions" do
+    filter = TagFilter.new(nil)
+    MSpec.should_receive(:register).with(:load, filter)
+    MSpec.should_receive(:register).with(:unload, filter)
+    filter.register
+  end
+end
+
+describe TagFilter, "#unregister" do
+  it "unregisters itself with MSpec for the :load, :unload actions" do
+    filter = TagFilter.new(nil)
+    MSpec.should_receive(:unregister).with(:load, filter)
+    MSpec.should_receive(:unregister).with(:unload, filter)
+    filter.unregister
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/runner/formatters/describe_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/runner/formatters/describe_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/runner/formatters/describe_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,67 @@
+require File.dirname(__FILE__) + '/../../spec_helper'
+require 'mspec/runner/formatters/describe'
+require 'mspec/runner/example'
+
+describe DescribeFormatter, "#finish" do
+  before :each do
+    MSpec.stub!(:register)
+    MSpec.stub!(:unregister)
+
+    @timer = mock("timer", :null_object => true)
+    TimerAction.stub!(:new).and_return(@timer)
+    @timer.stub!(:format).and_return("Finished in 2.0 seconds")
+
+    $stdout = @out = IOStub.new
+    context = ContextState.new "Class#method"
+    @state = ExampleState.new(context, "runs")
+
+    @formatter = DescribeFormatter.new
+    @formatter.register
+
+    @tally = @formatter.tally
+    @counter = @tally.counter
+
+    @counter.files!
+    @counter.examples!
+    @counter.expectations! 2
+  end
+
+  after :each do
+    $stdout = STDOUT
+  end
+
+  it "prints a summary of elapsed time" do
+    @formatter.finish
+    @out.should =~ /^Finished in 2.0 seconds$/
+  end
+
+  it "prints a tally of counts" do
+    @formatter.finish
+    @out.should =~ /^1 file, 1 example, 2 expectations, 0 failures, 0 errors$/
+  end
+
+  it "does not print exceptions" do
+    @formatter.finish
+    @out.should == %[
+
+Finished in 2.0 seconds
+
+1 file, 1 example, 2 expectations, 0 failures, 0 errors
+]
+  end
+
+  it "prints a summary of failures and errors for each describe block" do
+    exc = ExceptionState.new @state, nil, MSpecExampleError.new("broken")
+    exc.stub!(:backtrace).and_return("path/to/some/file.rb:35:in method")
+    @formatter.exception exc
+    @formatter.finish
+    @out.should == %[
+
+Class#method                             0 failures, 1 error
+
+Finished in 2.0 seconds
+
+1 file, 1 example, 2 expectations, 0 failures, 0 errors
+]
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/runner/formatters/dotted_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/runner/formatters/dotted_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/runner/formatters/dotted_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,290 @@
+require File.dirname(__FILE__) + '/../../spec_helper'
+require 'mspec/runner/formatters/dotted'
+require 'mspec/runner/mspec'
+require 'mspec/runner/example'
+
+describe DottedFormatter, "#initialize" do
+  it "permits zero arguments" do
+    DottedFormatter.new
+  end
+
+  it "accepts one argument" do
+    DottedFormatter.new nil
+  end
+end
+
+describe DottedFormatter, "#register" do
+  before :each do
+    @formatter = DottedFormatter.new
+  end
+
+  it "registers self with MSpec for appropriate actions" do
+    MSpec.stub!(:register)
+    MSpec.should_receive(:register).with(:exception, @formatter)
+    MSpec.should_receive(:register).with(:before, @formatter)
+    MSpec.should_receive(:register).with(:after, @formatter)
+    MSpec.should_receive(:register).with(:finish, @formatter)
+    @formatter.register
+  end
+
+  it "creates TimerAction and TallyAction" do
+    timer = mock("timer")
+    tally = mock("tally")
+    timer.should_receive(:register)
+    tally.should_receive(:register)
+    tally.should_receive(:counter)
+    TimerAction.should_receive(:new).and_return(timer)
+    TallyAction.should_receive(:new).and_return(tally)
+    @formatter.register
+  end
+end
+
+describe DottedFormatter, "#print" do
+  before :each do
+    $stdout = IOStub.new
+  end
+
+  after :each do
+    $stdout = STDOUT
+  end
+
+  it "writes to $stdout by default" do
+    formatter = DottedFormatter.new
+    formatter.print "begonias"
+    $stdout.should == "begonias"
+  end
+
+  it "writes to the file specified when the formatter was created" do
+    out = IOStub.new
+    File.should_receive(:open).with("some/file", "w").and_return(out)
+    formatter = DottedFormatter.new "some/file"
+    formatter.print "begonias"
+    out.should == "begonias"
+  end
+
+  it "flushes the IO output" do
+    $stdout.should_receive(:flush)
+    formatter = DottedFormatter.new
+    formatter.print "begonias"
+  end
+
+  it "rescues errors from flush" do
+    $stdout.should_receive(:flush).and_raise(RuntimeError.new)
+    formatter = DottedFormatter.new
+    formatter.print("begonias").should == nil
+  end
+end
+
+describe DottedFormatter, "#exception" do
+  before :each do
+    @formatter = DottedFormatter.new
+    @failure = ExceptionState.new nil, nil, ExpectationNotMetError.new("failed")
+    @error = ExceptionState.new nil, nil, MSpecExampleError.new("boom!")
+  end
+
+  it "sets the #failure? flag" do
+    @formatter.exception @failure
+    @formatter.failure?.should be_true
+    @formatter.exception @error
+    @formatter.failure?.should be_false
+  end
+
+  it "sets the #exception? flag" do
+    @formatter.exception @error
+    @formatter.exception?.should be_true
+    @formatter.exception @failure
+    @formatter.exception?.should be_true
+  end
+
+  it "addes the exception to the list of exceptions" do
+    @formatter.exceptions.should == []
+    @formatter.exception @error
+    @formatter.exception @failure
+    @formatter.exceptions.should == [@error, @failure]
+  end
+end
+
+describe DottedFormatter, "#exception?" do
+  before :each do
+    @formatter = DottedFormatter.new
+    @failure = ExceptionState.new nil, nil, ExpectationNotMetError.new("failed")
+    @error = ExceptionState.new nil, nil, MSpecExampleError.new("boom!")
+  end
+
+  it "returns false if there have been no exceptions" do
+    @formatter.exception?.should be_false
+  end
+
+  it "returns true if any exceptions are errors" do
+    @formatter.exception @failure
+    @formatter.exception @error
+    @formatter.exception?.should be_true
+  end
+
+  it "returns true if all exceptions are failures" do
+    @formatter.exception @failure
+    @formatter.exception @failure
+    @formatter.exception?.should be_true
+  end
+
+  it "returns true if all exceptions are errors" do
+    @formatter.exception @error
+    @formatter.exception @error
+    @formatter.exception?.should be_true
+  end
+end
+
+describe DottedFormatter, "#failure?" do
+  before :each do
+    @formatter = DottedFormatter.new
+    @failure = ExceptionState.new nil, nil, ExpectationNotMetError.new("failed")
+    @error = ExceptionState.new nil, nil, MSpecExampleError.new("boom!")
+  end
+
+  it "returns false if there have been no exceptions" do
+    @formatter.failure?.should be_false
+  end
+
+  it "returns false if any exceptions are errors" do
+    @formatter.exception @failure
+    @formatter.exception @error
+    @formatter.failure?.should be_false
+  end
+
+  it "returns true if all exceptions are failures" do
+    @formatter.exception @failure
+    @formatter.exception @failure
+    @formatter.failure?.should be_true
+  end
+end
+
+describe DottedFormatter, "#before" do
+  before :each do
+    @state = ExampleState.new ContextState.new("describe"), "it"
+    @formatter = DottedFormatter.new
+    @formatter.exception ExceptionState.new(nil, nil, ExpectationNotMetError.new("Failed!"))
+  end
+
+  it "resets the #failure? flag to false" do
+    @formatter.failure?.should be_true
+    @formatter.before @state
+    @formatter.failure?.should be_false
+  end
+
+  it "resets the #exception? flag to false" do
+    @formatter.exception?.should be_true
+    @formatter.before @state
+    @formatter.exception?.should be_false
+  end
+end
+
+describe DottedFormatter, "#after" do
+  before :each do
+    $stdout = @out = IOStub.new
+    @formatter = DottedFormatter.new
+    @state = ExampleState.new ContextState.new("describe"), "it"
+  end
+
+  after :each do
+    $stdout = STDOUT
+  end
+
+  it "prints a '.' if there was no exception raised" do
+    @formatter.after(@state)
+    @out.should == "."
+  end
+
+  it "prints an 'F' if there was an expectation failure" do
+    exc = ExpectationNotMetError.new "failed"
+    @formatter.exception ExceptionState.new(@state, nil, exc)
+    @formatter.after(@state)
+    @out.should == "F"
+  end
+
+  it "prints an 'E' if there was an exception other than expectation failure" do
+    exc = MSpecExampleError.new("boom!")
+    @formatter.exception ExceptionState.new(@state, nil, exc)
+    @formatter.after(@state)
+    @out.should == "E"
+  end
+
+  it "prints an 'E' if there are mixed exceptions and exepctation failures" do
+    exc = ExpectationNotMetError.new "failed"
+    @formatter.exception ExceptionState.new(@state, nil, exc)
+    exc = MSpecExampleError.new("boom!")
+    @formatter.exception ExceptionState.new(@state, nil, exc)
+    @formatter.after(@state)
+    @out.should == "E"
+  end
+end
+
+describe DottedFormatter, "#finish" do
+  before :each do
+    @tally = mock("tally", :null_object => true)
+    TallyAction.stub!(:new).and_return(@tally)
+    @timer = mock("timer", :null_object => true)
+    TimerAction.stub!(:new).and_return(@timer)
+
+    $stdout = @out = IOStub.new
+    context = ContextState.new "Class#method"
+    @state = ExampleState.new(context, "runs")
+    MSpec.stub!(:register)
+    @formatter = DottedFormatter.new
+    @formatter.register
+  end
+
+  after :each do
+    $stdout = STDOUT
+  end
+
+  it "prints a failure message for an exception" do
+    exc = ExceptionState.new @state, nil, MSpecExampleError.new("broken")
+    @formatter.exception exc
+    @formatter.after @state
+    @formatter.finish
+    @out.should =~ /^1\)\nClass#method runs ERROR$/
+  end
+
+  it "prints a backtrace for an exception" do
+    exc = ExceptionState.new @state, nil, MSpecExampleError.new("broken")
+    exc.stub!(:backtrace).and_return("path/to/some/file.rb:35:in method")
+    @formatter.exception exc
+    @formatter.after @state
+    @formatter.finish
+    @out.should =~ %r[path/to/some/file.rb:35:in method$]
+  end
+
+  it "prints a summary of elapsed time" do
+    @timer.should_receive(:format).and_return("Finished in 2.0 seconds")
+    @formatter.finish
+    @out.should =~ /^Finished in 2.0 seconds$/
+  end
+
+  it "prints a tally of counts" do
+    @tally.should_receive(:format).and_return("1 example, 0 failures")
+    @formatter.finish
+    @out.should =~ /^1 example, 0 failures$/
+  end
+
+  it "prints errors, backtraces, elapsed time, and tallies" do
+    exc = ExceptionState.new @state, nil, MSpecExampleError.new("broken")
+    exc.stub!(:backtrace).and_return("path/to/some/file.rb:35:in method")
+    @formatter.exception exc
+    @timer.should_receive(:format).and_return("Finished in 2.0 seconds")
+    @tally.should_receive(:format).and_return("1 example, 1 failure")
+    @formatter.after @state
+    @formatter.finish
+    @out.should ==
+%[E
+
+1)
+Class#method runs ERROR
+MSpecExampleError: broken
+path/to/some/file.rb:35:in method
+
+Finished in 2.0 seconds
+
+1 example, 1 failure
+]
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/runner/formatters/file_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/runner/formatters/file_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/runner/formatters/file_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,84 @@
+require File.dirname(__FILE__) + '/../../spec_helper'
+require 'mspec/runner/formatters/file'
+require 'mspec/runner/mspec'
+require 'mspec/runner/example'
+
+describe FileFormatter, "#register" do
+  before :each do
+    @formatter = FileFormatter.new
+  end
+
+  it "registers self with MSpec for :load, :unload actions" do
+    MSpec.stub!(:register)
+    MSpec.should_receive(:register).with(:load, @formatter)
+    MSpec.should_receive(:register).with(:unload, @formatter)
+    @formatter.register
+  end
+
+  it "unregisters self with MSpec for :before, :after actions" do
+    MSpec.stub!(:unregister)
+    MSpec.should_receive(:unregister).with(:before, @formatter)
+    MSpec.should_receive(:unregister).with(:after, @formatter)
+    @formatter.register
+  end
+end
+
+describe FileFormatter, "#load" do
+  before :each do
+    @state = ExampleState.new ContextState.new("describe"), "it"
+    @formatter = FileFormatter.new
+    @formatter.exception ExceptionState.new(nil, nil, ExpectationNotMetError.new("Failed!"))
+  end
+
+  it "resets the #failure? flag to false" do
+    @formatter.failure?.should be_true
+    @formatter.load @state
+    @formatter.failure?.should be_false
+  end
+
+  it "resets the #exception? flag to false" do
+    @formatter.exception?.should be_true
+    @formatter.load @state
+    @formatter.exception?.should be_false
+  end
+end
+
+describe FileFormatter, "#unload" do
+  before :each do
+    $stdout = @out = IOStub.new
+    @formatter = FileFormatter.new
+    @state = ExampleState.new ContextState.new("describe"), "it"
+  end
+
+  after :each do
+    $stdout = STDOUT
+  end
+
+  it "prints a '.' if there was no exception raised" do
+    @formatter.unload(@state)
+    @out.should == "."
+  end
+
+  it "prints an 'F' if there was an expectation failure" do
+    exc = ExpectationNotMetError.new "failed"
+    @formatter.exception ExceptionState.new(@state, nil, exc)
+    @formatter.unload(@state)
+    @out.should == "F"
+  end
+
+  it "prints an 'E' if there was an exception other than expectation failure" do
+    exc = MSpecExampleError.new("boom!")
+    @formatter.exception ExceptionState.new(@state, nil, exc)
+    @formatter.unload(@state)
+    @out.should == "E"
+  end
+
+  it "prints an 'E' if there are mixed exceptions and exepctation failures" do
+    exc = ExpectationNotMetError.new "failed"
+    @formatter.exception ExceptionState.new(@state, nil, exc)
+    exc = MSpecExampleError.new("boom!")
+    @formatter.exception ExceptionState.new(@state, nil, exc)
+    @formatter.unload(@state)
+    @out.should == "E"
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/runner/formatters/html_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/runner/formatters/html_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/runner/formatters/html_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,216 @@
+require File.dirname(__FILE__) + '/../../spec_helper'
+require 'mspec/utils/ruby_name'
+require 'mspec/guards/guard'
+require 'mspec/runner/formatters/html'
+require 'mspec/runner/mspec'
+require 'mspec/runner/example'
+
+describe HtmlFormatter do
+  before :each do
+    @formatter = HtmlFormatter.new
+  end
+
+  it "responds to #register by registering itself with MSpec for appropriate actions" do
+    MSpec.stub!(:register)
+    MSpec.should_receive(:register).with(:start, @formatter)
+    MSpec.should_receive(:register).with(:enter, @formatter)
+    MSpec.should_receive(:register).with(:leave, @formatter)
+    @formatter.register
+  end
+end
+
+describe HtmlFormatter, "#start" do
+  before :each do
+    $stdout = @out = IOStub.new
+    @formatter = HtmlFormatter.new
+  end
+
+  after :each do
+    $stdout = STDOUT
+  end
+
+  it "prints the HTML head" do
+    @formatter.start
+    ruby_name = RUBY_NAME
+    ruby_name.should =~ /^ruby/
+    @out.should ==
+%[<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
+    "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+<title>Spec Output For #{ruby_name} (#{RUBY_VERSION})</title>
+<style type="text/css">
+ul {
+  list-style: none;
+}
+.fail {
+  color: red;
+}
+.pass {
+  color: green;
+}
+#details :target {
+  background-color: #ffffe0;
+}
+</style>
+</head>
+<body>
+]
+  end
+end
+
+describe HtmlFormatter, "#enter" do
+  before :each do
+    $stdout = @out = IOStub.new
+    @formatter = HtmlFormatter.new
+  end
+
+  after :each do
+    $stdout = STDOUT
+  end
+
+  it "prints the #describe string" do
+    @formatter.enter "describe"
+    @out.should == "<div><p>describe</p>\n<ul>\n"
+  end
+end
+
+describe HtmlFormatter, "#leave" do
+  before :each do
+    $stdout = @out = IOStub.new
+    @formatter = HtmlFormatter.new
+  end
+
+  after :each do
+    $stdout = STDOUT
+  end
+
+  it "prints the closing tags for the #describe string" do
+    @formatter.leave
+    @out.should == "</ul>\n</div>\n"
+  end
+end
+
+describe HtmlFormatter, "#exception" do
+  before :each do
+    $stdout = @out = IOStub.new
+    @formatter = HtmlFormatter.new
+    @formatter.register
+    @state = ExampleState.new ContextState.new("describe"), "it"
+  end
+
+  after :each do
+    $stdout = STDOUT
+  end
+
+  it "prints the #it string once for each exception raised" do
+    exc = ExceptionState.new @state, nil, ExpectationNotMetError.new("disappointing")
+    @formatter.exception exc
+    exc = ExceptionState.new @state, nil, MSpecExampleError.new("painful")
+    @formatter.exception exc
+    @out.should == 
+%[<li class="fail">- it (<a href="#details-1">FAILED - 1</a>)</li>
+<li class="fail">- it (<a href="#details-2">ERROR - 2</a>)</li>
+]
+  end
+end
+
+describe HtmlFormatter, "#after" do
+  before :each do
+    $stdout = @out = IOStub.new
+    @formatter = HtmlFormatter.new
+    @formatter.register
+    @state = ExampleState.new ContextState.new("describe"), "it"
+  end
+
+  after :each do
+    $stdout = STDOUT
+  end
+
+  it "prints the #it once when there are no exceptions raised" do
+    @formatter.after @state
+    @out.should == %[<li class="pass">- it</li>\n]
+  end
+
+  it "does not print any output if an exception is raised" do
+    exc = ExceptionState.new @state, nil, ExpectationNotMetError.new("disappointing")
+    @formatter.exception exc
+    out = @out.dup
+    @formatter.after @state
+    @out.should == out
+  end
+end
+
+describe HtmlFormatter, "#finish" do
+  before :each do
+    @tally = mock("tally", :null_object => true)
+    TallyAction.stub!(:new).and_return(@tally)
+    @timer = mock("timer", :null_object => true)
+    TimerAction.stub!(:new).and_return(@timer)
+
+    $stdout = @out = IOStub.new
+    context = ContextState.new "describe"
+    @state = ExampleState.new(context, "it")
+    MSpec.stub!(:register)
+    @formatter = HtmlFormatter.new
+    @formatter.register
+    @exception = MSpecExampleError.new("broken")
+    @exception.stub!(:backtrace).and_return(["file.rb:1", "file.rb:2"])
+  end
+
+  after :each do
+    $stdout = STDOUT
+  end
+
+  it "prints a failure message for an exception" do
+    exc = ExceptionState.new @state, nil, @exception
+    @formatter.exception exc
+    @formatter.finish
+    @out.should =~ %r[<p>describe it ERROR</p>]
+  end
+
+  it "prints a backtrace for an exception" do
+    exc = ExceptionState.new @state, nil, @exception
+    exc.stub!(:backtrace).and_return("path/to/some/file.rb:35:in method")
+    @formatter.exception exc
+    @formatter.finish
+    @out.should =~ %r[<pre>.*path/to/some/file.rb:35:in method.*</pre>]m
+  end
+
+  it "prints a summary of elapsed time" do
+    @timer.should_receive(:format).and_return("Finished in 2.0 seconds")
+    @formatter.finish
+    @out.should =~ %r[<p>Finished in 2.0 seconds</p>\n]
+  end
+
+  it "prints a tally of counts" do
+    @tally.should_receive(:format).and_return("1 example, 0 failures")
+    @formatter.finish
+    @out.should =~ %r[<p class="pass">1 example, 0 failures</p>]
+  end
+
+  it "prints errors, backtraces, elapsed time, and tallies" do
+    exc = ExceptionState.new @state, nil, @exception
+    exc.stub!(:backtrace).and_return("path/to/some/file.rb:35:in method")
+    @formatter.exception exc
+    
+    @timer.should_receive(:format).and_return("Finished in 2.0 seconds")
+    @tally.should_receive(:format).and_return("1 example, 1 failures")
+    @formatter.finish
+    @out.should ==
+%[<li class=\"fail\">- it (<a href=\"#details-1\">ERROR - 1</a>)</li>
+<hr>
+<ol id="details">
+<li id="details-1"><p>describe it ERROR</p>
+<p>MSpecExampleError: broken</p>
+<pre>
+path/to/some/file.rb:35:in method</pre>
+</li>
+</ol>
+<p>Finished in 2.0 seconds</p>
+<p class="fail">1 example, 1 failures</p>
+</body>
+</html>
+]
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/runner/formatters/method_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/runner/formatters/method_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/runner/formatters/method_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,177 @@
+require File.dirname(__FILE__) + '/../../spec_helper'
+require 'mspec/runner/formatters/method'
+require 'mspec/runner/mspec'
+require 'mspec/runner/example'
+
+describe MethodFormatter, "#method_type" do
+  before :each do
+    @formatter = MethodFormatter.new
+  end
+
+  it "returns 'class' if the separator is '.' or '::'" do
+    @formatter.method_type('.').should == "class"
+    @formatter.method_type('::').should == "class"
+  end
+
+  it "returns 'instance' if the separator is '#'" do
+    @formatter.method_type('#').should == "instance"
+  end
+
+  it "returns 'unknown' for all other cases" do
+    @formatter.method_type(nil).should == "unknown"
+  end
+end
+
+describe MethodFormatter, "#before" do
+  before :each do
+    @formatter = MethodFormatter.new
+    MSpec.stub!(:register)
+    @formatter.register
+  end
+
+  it "resets the tally counters to 0" do
+    @formatter.tally.counter.examples = 3
+    @formatter.tally.counter.expectations = 4
+    @formatter.tally.counter.failures = 2
+    @formatter.tally.counter.errors = 1
+
+    state = ExampleState.new ContextState.new("describe"), "it"
+    @formatter.before state
+    @formatter.tally.counter.examples.should == 0
+    @formatter.tally.counter.expectations.should == 0
+    @formatter.tally.counter.failures.should == 0
+    @formatter.tally.counter.errors.should == 0
+  end
+
+  it "records the class, method if available" do
+    state = ExampleState.new ContextState.new("Some#method"), "it"
+    @formatter.before state
+    key = "Some#method"
+    @formatter.methods.keys.should include(key)
+    h = @formatter.methods[key]
+    h[:class].should == "Some"
+    h[:method].should == "method"
+    h[:description].should == "Some#method it"
+  end
+
+  it "does not record class, method unless both are available" do
+    state = ExampleState.new ContextState.new("Some method"), "it"
+    @formatter.before state
+    key = "Some method"
+    @formatter.methods.keys.should include(key)
+    h = @formatter.methods[key]
+    h[:class].should == ""
+    h[:method].should == ""
+    h[:description].should == "Some method it"
+  end
+
+  it "sets the method type to unknown if class and method are not available" do
+    state = ExampleState.new ContextState.new("Some method"), "it"
+    @formatter.before state
+    key = "Some method"
+    h = @formatter.methods[key]
+    h[:type].should == "unknown"
+  end
+
+  it "sets the method type based on the class, method separator" do
+    [["C#m", "instance"], ["C.m", "class"], ["C::m", "class"]].each do |k, t|
+      state = ExampleState.new ContextState.new(k), "it"
+      @formatter.before state
+      h = @formatter.methods[k]
+      h[:type].should == t
+    end
+  end
+
+  it "clears the list of exceptions" do
+    state = ExampleState.new ContextState.new("describe"), "it"
+    @formatter.exceptions << "stuff"
+    @formatter.before state
+    @formatter.exceptions.should be_empty
+  end
+end
+
+describe MethodFormatter, "#after" do
+  before :each do
+    @formatter = MethodFormatter.new
+    MSpec.stub!(:register)
+    @formatter.register
+  end
+
+  it "sets the tally counts" do
+    state = ExampleState.new ContextState.new("Some#method"), "it"
+    @formatter.before state
+
+    @formatter.tally.counter.examples = 3
+    @formatter.tally.counter.expectations = 4
+    @formatter.tally.counter.failures = 2
+    @formatter.tally.counter.errors = 1
+
+    @formatter.after state
+    h = @formatter.methods["Some#method"]
+    h[:examples].should == 3
+    h[:expectations].should == 4
+    h[:failures].should == 2
+    h[:errors].should == 1
+  end
+
+  it "renders the list of exceptions" do
+    state = ExampleState.new ContextState.new("Some#method"), "it"
+    @formatter.before state
+
+    exc = ExpectationNotMetError.new "failed"
+    @formatter.exception ExceptionState.new(@state, nil, exc)
+    @formatter.exception ExceptionState.new(@state, nil, exc)
+
+    @formatter.after state
+    h = @formatter.methods["Some#method"]
+    h[:exceptions].should == [
+      %[failed\n\n],
+      %[failed\n\n]
+    ]
+  end
+end
+
+describe MethodFormatter, "#after" do
+  before :each do
+    $stdout = IOStub.new
+    context = ContextState.new "Class#method"
+    @state = ExampleState.new(context, "runs")
+    @formatter = MethodFormatter.new
+    MSpec.stub!(:register)
+    @formatter.register
+  end
+
+  after :each do
+    $stdout = STDOUT
+  end
+
+  it "prints a summary of the results of an example in YAML format" do
+    @formatter.before @state
+    @formatter.tally.counter.examples = 3
+    @formatter.tally.counter.expectations = 4
+    @formatter.tally.counter.failures = 2
+    @formatter.tally.counter.errors = 1
+
+    exc = ExpectationNotMetError.new "failed"
+    @formatter.exception ExceptionState.new(@state, nil, exc)
+    @formatter.exception ExceptionState.new(@state, nil, exc)
+
+    @formatter.after @state
+    @formatter.finish
+    $stdout.should ==
+%[---
+"Class#method":
+  class: "Class"
+  method: "method"
+  type: instance
+  description: "Class#method runs"
+  examples: 3
+  expectations: 4
+  failures: 2
+  errors: 1
+  exceptions:
+  - "failed\\n\\n"
+  - "failed\\n\\n"
+]
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/runner/formatters/specdoc_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/runner/formatters/specdoc_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/runner/formatters/specdoc_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,106 @@
+require File.dirname(__FILE__) + '/../../spec_helper'
+require 'mspec/runner/formatters/specdoc'
+require 'mspec/runner/example'
+
+describe SpecdocFormatter do
+  before :each do
+    @formatter = SpecdocFormatter.new
+  end
+
+  it "responds to #register by registering itself with MSpec for appropriate actions" do
+    MSpec.stub!(:register)
+    MSpec.should_receive(:register).with(:enter, @formatter)
+    @formatter.register
+  end
+end
+
+describe SpecdocFormatter, "#enter" do
+  before :each do
+    $stdout = @out = IOStub.new
+    @formatter = SpecdocFormatter.new
+  end
+
+  after :each do
+    $stdout = STDOUT
+  end
+
+  it "prints the #describe string" do
+    @formatter.enter("describe")
+    @out.should == "\ndescribe\n"
+  end
+end
+
+describe SpecdocFormatter, "#before" do
+  before :each do
+    $stdout = @out = IOStub.new
+    @formatter = SpecdocFormatter.new
+    @state = ExampleState.new ContextState.new("describe"), "it"
+  end
+
+  after :each do
+    $stdout = STDOUT
+  end
+
+  it "prints the #it string" do
+    @formatter.before @state
+    @out.should == "- it"
+  end
+
+  it "resets the #exception? flag" do
+    exc = ExceptionState.new @state, nil, ExpectationNotMetError.new("disappointing")
+    @formatter.exception exc
+    @formatter.exception?.should be_true
+    @formatter.before @state
+    @formatter.exception?.should be_false
+  end
+end
+
+describe SpecdocFormatter, "#exception" do
+  before :each do
+    $stdout = @out = IOStub.new
+    @formatter = SpecdocFormatter.new
+    context = ContextState.new "describe"
+    @state = ExampleState.new context, "it"
+  end
+
+  after :each do
+    $stdout = STDOUT
+  end
+
+  it "prints 'ERROR' if an exception is not an ExpectationNotMetError" do
+    exc = ExceptionState.new @state, nil, MSpecExampleError.new("painful")
+    @formatter.exception exc
+    @out.should == " (ERROR - 1)"
+  end
+
+  it "prints 'FAILED' if an exception is an ExpectationNotMetError" do
+    exc = ExceptionState.new @state, nil, ExpectationNotMetError.new("disappointing")
+    @formatter.exception exc
+    @out.should == " (FAILED - 1)"
+  end
+
+  it "prints the #it string if an exception has already been raised" do
+    exc = ExceptionState.new @state, nil, ExpectationNotMetError.new("disappointing")
+    @formatter.exception exc
+    exc = ExceptionState.new @state, nil, MSpecExampleError.new("painful")
+    @formatter.exception exc
+    @out.should == " (FAILED - 1)\n- it (ERROR - 2)"
+  end
+end
+
+describe SpecdocFormatter, "#after" do
+  before :each do
+    $stdout = @out = IOStub.new
+    @formatter = SpecdocFormatter.new
+    @state = ExampleState.new "describe", "it"
+  end
+
+  after :each do
+    $stdout = STDOUT
+  end
+
+  it "prints a newline character" do
+    @formatter.after @state
+    @out.should == "\n"
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/runner/formatters/spinner_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/runner/formatters/spinner_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/runner/formatters/spinner_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,85 @@
+require File.dirname(__FILE__) + '/../../spec_helper'
+require 'mspec/runner/formatters/spinner'
+require 'mspec/runner/mspec'
+require 'mspec/runner/example'
+
+describe SpinnerFormatter, "#initialize" do
+  it "permits zero arguments" do
+    SpinnerFormatter.new
+  end
+
+  it "accepts one argument" do
+    SpinnerFormatter.new nil
+  end
+end
+
+describe SpinnerFormatter, "#register" do
+  before :each do
+    @formatter = SpinnerFormatter.new
+  end
+
+  it "registers self with MSpec for appropriate actions" do
+    MSpec.stub!(:register)
+    MSpec.should_receive(:register).with(:start, @formatter)
+    MSpec.should_receive(:register).with(:load, @formatter)
+    MSpec.should_receive(:register).with(:after, @formatter)
+    MSpec.should_receive(:register).with(:finish, @formatter)
+    @formatter.register
+  end
+
+  it "creates TimerAction and TallyAction" do
+    timer = mock("timer")
+    tally = mock("tally")
+    timer.should_receive(:register)
+    tally.should_receive(:register)
+    tally.should_receive(:counter)
+    TimerAction.should_receive(:new).and_return(timer)
+    TallyAction.should_receive(:new).and_return(tally)
+    @formatter.register
+  end
+end
+
+describe SpinnerFormatter, "#print" do
+  after :each do
+    $stdout = STDOUT
+  end
+
+  it "ignores the argument to #initialize and writes to $stdout" do
+    $stdout = IOStub.new
+    formatter = SpinnerFormatter.new "some/file"
+    formatter.print "begonias"
+    $stdout.should == "begonias"
+  end
+end
+
+describe SpinnerFormatter, "#after" do
+  before :each do
+    $stdout = IOStub.new
+    MSpec.stub!(:retrieve).and_return(["a", "b"])
+    @formatter = SpinnerFormatter.new
+    @formatter.register
+    @state = ExampleState.new("describe", "it")
+  end
+
+  after :each do
+    $stdout = STDOUT
+  end
+
+  it "updates the spinner" do
+    @formatter.start
+    @formatter.load
+    @formatter.after @state
+    @formatter.after @state
+
+    if ENV["TERM"] != "dumb"
+      $stdout.should == "\r[/ | ========          20%                    | 00:00:00] " \
+                        "\e[0;32m     0F \e[0;32m     0E\e[0m" \
+                        "\r[- | ========          20%                    | 00:00:00] " \
+                        "\e[0;32m     0F \e[0;32m     0E\e[0m" 
+    else
+      $stdout.should == "\r[/ | ========          20%                    | 00:00:00] " \
+                        "     0F      0E\r[- | ========          20%                 " \
+                        "   | 00:00:00]      0F      0E"
+    end
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/runner/formatters/summary_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/runner/formatters/summary_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/runner/formatters/summary_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,26 @@
+require File.dirname(__FILE__) + '/../../spec_helper'
+require 'mspec/runner/formatters/summary'
+require 'mspec/runner/example'
+
+describe SummaryFormatter, "#after" do
+  before :each do
+    $stdout = @out = IOStub.new
+    @formatter = SummaryFormatter.new
+    @formatter.register
+    context = ContextState.new "describe"
+    @state = ExampleState.new(context, "it")
+  end
+
+  after :each do
+    $stdout = STDOUT
+  end
+
+  it "does not print anything" do
+    exc = ExceptionState.new @state, nil, ExpectationNotMetError.new("disappointing")
+    @formatter.exception exc
+    exc = ExceptionState.new @state, nil, MSpecExampleError.new("painful")
+    @formatter.exception exc
+    @formatter.after(@state)
+    @out.should == ""
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/runner/formatters/unit_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/runner/formatters/unit_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/runner/formatters/unit_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,73 @@
+require File.dirname(__FILE__) + '/../../spec_helper'
+require 'mspec/runner/formatters/unit'
+require 'mspec/runner/example'
+
+describe UnitdiffFormatter, "#finish" do
+  before :each do
+    @tally = mock("tally", :null_object => true)
+    TallyAction.stub!(:new).and_return(@tally)
+    @timer = mock("timer", :null_object => true)
+    TimerAction.stub!(:new).and_return(@timer)
+
+    $stdout = @out = IOStub.new
+    context = ContextState.new "describe"
+    @state = ExampleState.new(context, "it")
+    MSpec.stub!(:register)
+    @formatter = UnitdiffFormatter.new
+    @formatter.register
+  end
+
+  after :each do
+    $stdout = STDOUT
+  end
+
+  it "prints a failure message for an exception" do
+    exc = ExceptionState.new @state, nil, MSpecExampleError.new("broken")
+    @formatter.exception exc
+    @formatter.after @state
+    @formatter.finish
+    @out.should =~ /^1\)\ndescribe it ERROR$/
+  end
+
+  it "prints a backtrace for an exception" do
+    exc = ExceptionState.new @state, nil, Exception.new("broken")
+    exc.stub!(:backtrace).and_return("path/to/some/file.rb:35:in method")
+    @formatter.exception exc
+    @formatter.finish
+    @out.should =~ %r[path/to/some/file.rb:35:in method$]
+  end
+
+  it "prints a summary of elapsed time" do
+    @timer.should_receive(:format).and_return("Finished in 2.0 seconds")
+    @formatter.finish
+    @out.should =~ /^Finished in 2.0 seconds$/
+  end
+
+  it "prints a tally of counts" do
+    @tally.should_receive(:format).and_return("1 example, 0 failures")
+    @formatter.finish
+    @out.should =~ /^1 example, 0 failures$/
+  end
+
+  it "prints errors, backtraces, elapsed time, and tallies" do
+    exc = ExceptionState.new @state, nil, Exception.new("broken")
+    exc.stub!(:backtrace).and_return("path/to/some/file.rb:35:in method")
+    @formatter.exception exc
+    @formatter.after @state
+    @timer.should_receive(:format).and_return("Finished in 2.0 seconds")
+    @tally.should_receive(:format).and_return("1 example, 0 failures")
+    @formatter.finish
+    @out.should ==
+%[E
+
+Finished in 2.0 seconds
+
+1)
+describe it ERROR
+Exception: broken: 
+path/to/some/file.rb:35:in method
+
+1 example, 0 failures
+]
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/runner/formatters/yaml_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/runner/formatters/yaml_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/runner/formatters/yaml_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,125 @@
+require File.dirname(__FILE__) + '/../../spec_helper'
+require 'mspec/runner/formatters/yaml'
+require 'mspec/runner/example'
+
+describe YamlFormatter, "#initialize" do
+  it "permits zero arguments" do
+    YamlFormatter.new
+  end
+
+  it "accepts one argument" do
+    YamlFormatter.new nil
+  end
+end
+
+describe YamlFormatter, "#print" do
+  before :each do
+    $stdout = IOStub.new
+    @out = IOStub.new
+    File.stub!(:open).and_return(@out)
+    @formatter = YamlFormatter.new "some/file"
+  end
+
+  after :each do
+    $stdout = STDOUT
+  end
+
+  it "writes to $stdout if #switch has not been called" do
+    @formatter.print "begonias"
+    $stdout.should == "begonias"
+    @out.should == ""
+  end
+
+  it "writes to the file passed to #initialize once #switch has been called" do
+    @formatter.switch
+    @formatter.print "begonias"
+    $stdout.should == ""
+    @out.should == "begonias"
+  end
+
+  it "writes to $stdout once #switch is called if no file was passed to #initialize" do
+    formatter = YamlFormatter.new
+    formatter.switch
+    formatter.print "begonias"
+    $stdout.should == "begonias"
+    @out.should == ""
+  end
+end
+
+describe YamlFormatter, "#finish" do
+  before :each do
+    @tally = mock("tally", :null_object => true)
+    @counter = mock("counter", :null_object => true)
+    @tally.stub!(:counter).and_return(@counter)
+    TallyAction.stub!(:new).and_return(@tally)
+
+    @timer = mock("timer", :null_object => true)
+    TimerAction.stub!(:new).and_return(@timer)
+
+    $stdout = IOStub.new
+    context = ContextState.new "describe"
+    @state = ExampleState.new(context, "it")
+
+    @formatter = YamlFormatter.new
+    @formatter.stub!(:backtrace).and_return("")
+    MSpec.stub!(:register)
+    @formatter.register
+
+    exc = ExceptionState.new @state, nil, MSpecExampleError.new("broken")
+    exc.stub!(:backtrace).and_return("path/to/some/file.rb:35:in method")
+    @formatter.exception exc
+    @formatter.after @state
+  end
+
+  after :each do
+    $stdout = STDOUT
+  end
+
+  it "calls #switch" do
+    @formatter.should_receive(:switch)
+    @formatter.finish
+  end
+
+  it "outputs a failure message and backtrace" do
+    @formatter.finish
+    $stdout.should =~ /describe it ERROR/
+    $stdout.should =~ /MSpecExampleError: broken\\n/
+    $stdout.should =~ %r[path/to/some/file.rb:35:in method]
+  end
+
+  it "outputs an elapsed time" do
+    @timer.should_receive(:elapsed).and_return(4.2)
+    @formatter.finish
+    $stdout.should =~ /time: 4.2/
+  end
+
+  it "outputs a file count" do
+    @counter.should_receive(:files).and_return(3)
+    @formatter.finish
+    $stdout.should =~ /files: 3/
+  end
+
+  it "outputs an example count" do
+    @counter.should_receive(:examples).and_return(3)
+    @formatter.finish
+    $stdout.should =~ /examples: 3/
+  end
+
+  it "outputs an expectation count" do
+    @counter.should_receive(:expectations).and_return(9)
+    @formatter.finish
+    $stdout.should =~ /expectations: 9/
+  end
+
+  it "outputs a failure count" do
+    @counter.should_receive(:failures).and_return(2)
+    @formatter.finish
+    $stdout.should =~ /failures: 2/
+  end
+
+  it "outputs an error count" do
+    @counter.should_receive(:errors).and_return(1)
+    @formatter.finish
+    $stdout.should =~ /errors: 1/
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/runner/mspec_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/runner/mspec_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/runner/mspec_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,589 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/helpers/tmp'
+require 'mspec/matchers/base'
+require 'mspec/runner/mspec'
+require 'mspec/runner/example'
+
+describe MSpec, ".register_files" do
+  it "records which spec files to run" do
+    MSpec.register_files [:one, :two, :three]
+    MSpec.retrieve(:files).should == [:one, :two, :three]
+  end
+end
+
+describe MSpec, ".register_mode" do
+  before :each do
+    MSpec.clear_modes
+  end
+
+  it "sets execution mode flags" do
+    MSpec.register_mode :verify
+    MSpec.retrieve(:modes).should == [:verify]
+  end
+end
+
+describe MSpec, ".register_tags_patterns" do
+  it "records the patterns for generating a tag file from a spec file" do
+    MSpec.register_tags_patterns [[/spec\/ruby/, "spec/tags"], [/frozen/, "ruby"]]
+    MSpec.retrieve(:tags_patterns).should == [[/spec\/ruby/, "spec/tags"], [/frozen/, "ruby"]]
+  end
+end
+
+describe MSpec, ".register_exit" do
+  before :each do
+    MSpec.store :exit, 0
+  end
+
+  it "records the exit code" do
+    MSpec.exit_code.should == 0
+    MSpec.register_exit 1
+    MSpec.exit_code.should == 1
+  end
+end
+
+describe MSpec, ".exit_code" do
+  it "retrieves the code set with .register_exit" do
+    MSpec.register_exit 99
+    MSpec.exit_code.should == 99
+  end
+end
+
+describe MSpec, ".store" do
+  it "records data for MSpec settings" do
+    MSpec.store :anything, :value
+    MSpec.retrieve(:anything).should == :value
+  end
+end
+
+describe MSpec, ".retrieve" do
+  it "accesses .store'd data" do
+    MSpec.register :action, :first
+    MSpec.retrieve(:action).should == [:first]
+  end
+end
+
+describe MSpec, ".randomize" do
+  it "sets the flag to randomize spec execution order" do
+    MSpec.randomize?.should == false
+    MSpec.randomize
+    MSpec.randomize?.should == true
+    MSpec.randomize false
+    MSpec.randomize?.should == false
+  end
+end
+
+describe MSpec, ".register" do
+  it "is the gateway behind the register(symbol, action) facility" do
+    MSpec.register :bonus, :first
+    MSpec.register :bonus, :second
+    MSpec.register :bonus, :second
+    MSpec.retrieve(:bonus).should == [:first, :second]
+  end
+end
+
+describe MSpec, ".unregister" do
+  it "is the gateway behind the unregister(symbol, actions) facility" do
+    MSpec.register :unregister, :first
+    MSpec.register :unregister, :second
+    MSpec.unregister :unregister, :second
+    MSpec.retrieve(:unregister).should == [:first]
+  end
+end
+
+describe MSpec, ".protect" do
+  before :each do
+    MSpec.clear_current
+    @cs = ContextState.new "C#m"
+    @cs.stub!(:state).and_return(@es)
+    @cs.parent = MSpec.current
+
+    @es = ExampleState.new @cs, "runs"
+    ScratchPad.record Exception.new("Sharp!")
+  end
+
+  it "returns true if no exception is raised" do
+    MSpec.protect("passed") { 1 }.should be_true
+  end
+
+  it "returns false if an exception is raised" do
+    MSpec.protect("testing") { raise ScratchPad.recorded }.should be_false
+  end
+
+  it "rescues any exceptions raised when evaluating the block argument" do
+    MSpec.protect("") { raise Exception, "Now you see me..." }
+  end
+
+  it "does not rescue SystemExit" do
+    begin
+      MSpec.protect("") { exit 1 }
+    rescue SystemExit
+      ScratchPad.record :system_exit
+    end
+    ScratchPad.recorded.should == :system_exit
+  end
+
+  it "calls all the exception actions" do
+    exc = ExceptionState.new @es, "testing", ScratchPad.recorded
+    ExceptionState.stub!(:new).and_return(exc)
+    action = mock("exception")
+    action.should_receive(:exception).with(exc)
+    MSpec.register :exception, action
+    MSpec.protect("testing") { raise ScratchPad.recorded }
+    MSpec.unregister :exception, action
+  end
+
+  it "registers a non-zero exit code when an exception is raised" do
+    MSpec.should_receive(:register_exit).with(1)
+    MSpec.protect("testing") { raise ScratchPad.recorded }
+  end
+end
+
+describe MSpec, ".register_current" do
+  before :each do
+    MSpec.clear_current
+  end
+
+  it "sets the value returned by MSpec.current" do
+    MSpec.current.should be_nil
+    MSpec.register_current :a
+    MSpec.current.should == :a
+  end
+end
+
+describe MSpec, ".clear_current" do
+  it "sets the value returned by MSpec.current to nil" do
+    MSpec.register_current :a
+    MSpec.current.should_not be_nil
+    MSpec.clear_current
+    MSpec.current.should be_nil
+  end
+end
+
+describe MSpec, ".current" do
+  before :each do
+    MSpec.clear_current
+  end
+
+  it "returns nil if no ContextState has been registered" do
+    MSpec.current.should be_nil
+  end
+
+  it "returns the most recently registered ContextState" do
+    first = ContextState.new ""
+    second = ContextState.new ""
+    MSpec.register_current first
+    MSpec.current.should == first
+    MSpec.register_current second
+    MSpec.current.should == second
+  end
+end
+
+describe MSpec, ".actions" do
+  before :each do
+    MSpec.store :start, []
+    ScratchPad.record []
+    start_one = mock("one")
+    start_one.stub!(:start).and_return { ScratchPad << :one }
+    start_two = mock("two")
+    start_two.stub!(:start).and_return { ScratchPad << :two }
+    MSpec.register :start, start_one
+    MSpec.register :start, start_two
+  end
+
+  it "does not attempt to run any actions if none have been registered" do
+    MSpec.store :finish, nil
+    lambda { MSpec.actions :finish }.should_not raise_error
+  end
+
+  it "runs each action registered as a start action" do
+    MSpec.actions :start
+    ScratchPad.recorded.should == [:one, :two]
+  end
+end
+
+describe MSpec, ".mode?" do
+  before :each do
+    MSpec.clear_modes
+  end
+
+  it "returns true if the mode has been set" do
+    MSpec.mode?(:verify).should == false
+    MSpec.register_mode :verify
+    MSpec.mode?(:verify).should == true
+  end
+end
+
+describe MSpec, ".clear_modes" do
+  it "clears all registered modes" do
+    MSpec.register_mode(:pretend)
+    MSpec.register_mode(:verify)
+
+    MSpec.mode?(:pretend).should == true
+    MSpec.mode?(:verify).should == true
+
+    MSpec.clear_modes
+
+    MSpec.mode?(:pretend).should == false
+    MSpec.mode?(:verify).should == false
+  end
+end
+
+describe MSpec, ".guarded?" do
+  before :each do
+    MSpec.instance_variable_set :@guarded, []
+  end
+
+  it "returns false if no guard has run" do
+    MSpec.guarded?.should == false
+  end
+
+  it "returns true if a single guard has run" do
+    MSpec.guard
+    MSpec.guarded?.should == true
+  end
+
+  it "returns true if more than one guard has run" do
+    MSpec.guard
+    MSpec.guard
+    MSpec.guarded?.should == true
+  end
+
+  it "returns true until all guards have finished" do
+    MSpec.guard
+    MSpec.guard
+    MSpec.guarded?.should == true
+    MSpec.unguard
+    MSpec.guarded?.should == true
+    MSpec.unguard
+    MSpec.guarded?.should == false
+  end
+end
+
+describe MSpec, ".describe" do
+  before :each do
+    MSpec.clear_current
+    @cs = ContextState.new ""
+    ContextState.stub!(:new).and_return(@cs)
+    MSpec.stub!(:current).and_return(nil)
+    MSpec.stub!(:register_current)
+  end
+
+  it "creates a new ContextState for the block" do
+    ContextState.should_receive(:new).and_return(@cs)
+    MSpec.describe(Object) { }
+  end
+
+  it "accepts an optional second argument" do
+    ContextState.should_receive(:new).and_return(@cs)
+    MSpec.describe(Object, "msg") { }
+  end
+
+  it "registers the newly created ContextState" do
+    MSpec.should_receive(:register_current).with(@cs).twice
+    MSpec.describe(Object) { }
+  end
+
+  it "invokes the ContextState#describe method" do
+    prc = lambda { }
+    @cs.should_receive(:describe).with(&prc)
+    MSpec.describe(Object, "msg", &prc)
+  end
+end
+
+describe MSpec, ".process" do
+  before :each do
+    MSpec.stub!(:files)
+    MSpec.store :start, []
+    MSpec.store :finish, []
+  end
+
+  it "calls all start actions" do
+    start = mock("start")
+    start.stub!(:start).and_return { ScratchPad.record :start }
+    MSpec.register :start, start
+    MSpec.process
+    ScratchPad.recorded.should == :start
+  end
+
+  it "calls all finish actions" do
+    finish = mock("finish")
+    finish.stub!(:finish).and_return { ScratchPad.record :finish }
+    MSpec.register :finish, finish
+    MSpec.process
+    ScratchPad.recorded.should == :finish
+  end
+
+  it "calls the files method" do
+    MSpec.should_receive(:files)
+    MSpec.process
+  end
+end
+
+describe MSpec, ".files" do
+  before :each do
+    MSpec.store :load, []
+    MSpec.store :unload, []
+    MSpec.register_files [:one, :two, :three]
+    Kernel.stub!(:load)
+  end
+
+  it "calls load actions before each file" do
+    load = mock("load")
+    load.stub!(:load).and_return { ScratchPad.record :load }
+    MSpec.register :load, load
+    MSpec.files
+    ScratchPad.recorded.should == :load
+  end
+
+  it "shuffles the file list if .randomize? is true" do
+    MSpec.randomize
+    MSpec.should_receive(:shuffle)
+    MSpec.files
+    MSpec.randomize false
+  end
+
+  it "registers the current file" do
+    MSpec.should_receive(:store).with(:file, :one)
+    MSpec.should_receive(:store).with(:file, :two)
+    MSpec.should_receive(:store).with(:file, :three)
+    MSpec.files
+  end
+end
+
+describe MSpec, ".shuffle" do
+  before :each do
+    @base = (0..100).to_a
+    @list = @base.clone
+    MSpec.shuffle @list
+  end
+
+  it "does not alter the elements in the list" do
+    @base.each do |elt|
+      @list.should include(elt)
+    end
+  end
+
+  it "changes the order of the list" do
+    # obviously, this spec has a certain probability
+    # of failing. If it fails, run it again.
+    @list.should_not == @base
+  end
+end
+
+describe MSpec, ".tags_file" do
+  before :each do
+    MSpec.store :file, "path/to/spec/something/some_spec.rb"
+    MSpec.store :tags_patterns, nil
+  end
+
+  it "returns the default tags file for the current spec file" do
+    MSpec.tags_file.should == "path/to/spec/tags/something/some_tags.txt"
+  end
+
+  it "returns the tags file for the current spec file with custom tags_patterns" do
+    MSpec.register_tags_patterns [[/^(.*)\/spec/, '\1/tags'], [/_spec.rb/, "_tags.txt"]]
+    MSpec.tags_file.should == "path/to/tags/something/some_tags.txt"
+  end
+
+  it "performs multiple substitutions" do
+    MSpec.register_tags_patterns [
+      [%r(/spec/something/), "/spec/other/"],
+      [%r(/spec/), "/spec/tags/"],
+      [/_spec.rb/, "_tags.txt"]
+    ]
+    MSpec.tags_file.should == "path/to/spec/tags/other/some_tags.txt"
+  end
+
+  it "handles cases where no substitution is performed" do
+    MSpec.register_tags_patterns [[/nothing/, "something"]]
+    MSpec.tags_file.should == "path/to/spec/something/some_spec.rb"
+  end
+end
+
+describe MSpec, ".read_tags" do
+  before :each do
+    MSpec.stub!(:tags_file).and_return(File.dirname(__FILE__) + '/tags.txt')
+  end
+
+  it "returns a list of tag instances for matching tag names found" do
+    one = SpecTag.new "fail(broken):Some#method? works"
+    MSpec.read_tags(["fail", "pass"]).should == [one]
+  end
+
+  it "returns [] if no tags names match" do
+    MSpec.read_tags("super").should == []
+  end
+end
+
+describe MSpec, ".read_tags" do
+  before :each do
+    @tag = SpecTag.new "fails:Some#method"
+    File.open(tmp("tags.txt"), "w") do |f|
+      f.puts ""
+      f.puts @tag
+      f.puts ""
+    end
+    MSpec.stub!(:tags_file).and_return(tmp("tags.txt"))
+  end
+
+  it "does not return a tag object for empty lines" do
+    MSpec.read_tags(["fails"]).should == [@tag]
+  end
+end
+
+describe MSpec, ".write_tags" do
+  before :each do
+    FileUtils.cp File.dirname(__FILE__) + "/tags.txt", tmp("tags.txt")
+    MSpec.stub!(:tags_file).and_return(tmp("tags.txt"))
+    @tag1 = SpecTag.new "check(broken):Tag#rewrite works"
+    @tag2 = SpecTag.new "broken:Tag#write_tags fails"
+  end
+
+  after :all do
+    File.delete tmp("tags.txt") rescue nil
+  end
+
+  it "overwrites the tags in the tag file" do
+    IO.read(tmp("tags.txt")).should == %[fail(broken):Some#method? works
+incomplete(20%):The#best method ever
+benchmark(0.01825):The#fastest method today
+extended():\"Multi-line\\ntext\\ntag\"
+]
+    MSpec.write_tags [@tag1, @tag2]
+    IO.read(tmp("tags.txt")).should == %[check(broken):Tag#rewrite works
+broken:Tag#write_tags fails
+]
+  end
+end
+
+describe MSpec, ".write_tag" do
+  before :each do
+    FileUtils.stub!(:mkdir_p)
+    MSpec.stub!(:tags_file).and_return(tmp("tags.txt"))
+    @tag = SpecTag.new "fail(broken):Some#method works"
+  end
+
+  after :all do
+    File.delete tmp("tags.txt") rescue nil
+  end
+
+  it "writes a tag to the tags file for the current spec file" do
+    MSpec.write_tag @tag
+    IO.read(tmp("tags.txt")).should == "fail(broken):Some#method works\n"
+  end
+
+  it "does not write a duplicate tag" do
+    File.open(tmp("tags.txt"), "w") { |f| f.puts @tag }
+    MSpec.write_tag @tag
+    IO.read(tmp("tags.txt")).should == "fail(broken):Some#method works\n"
+  end
+end
+
+describe MSpec, ".delete_tag" do
+  before :each do
+    FileUtils.cp File.dirname(__FILE__) + "/tags.txt", tmp("tags.txt")
+    MSpec.stub!(:tags_file).and_return(tmp("tags.txt"))
+    @tag = SpecTag.new "fail(Comments don't matter):Some#method? works"
+  end
+
+  after :each do
+    File.delete tmp("tags.txt") rescue nil
+  end
+
+  it "deletes the tag if it exists" do
+    MSpec.delete_tag(@tag).should == true
+    IO.read(tmp("tags.txt")).should == %[incomplete(20%):The#best method ever
+benchmark(0.01825):The#fastest method today
+extended():\"Multi-line\\ntext\\ntag\"
+]
+  end
+
+  it "deletes a tag with escaped newlines" do
+    MSpec.delete_tag(SpecTag.new('extended:"Multi-line\ntext\ntag"')).should == true
+    IO.read(tmp("tags.txt")).should == %[fail(broken):Some#method? works
+incomplete(20%):The#best method ever
+benchmark(0.01825):The#fastest method today
+]
+  end
+
+  it "does not change the tags file contents if the tag doesn't exist" do
+    @tag.tag = "failed"
+    MSpec.delete_tag(@tag).should == false
+    IO.read(tmp("tags.txt")).should == %[fail(broken):Some#method? works
+incomplete(20%):The#best method ever
+benchmark(0.01825):The#fastest method today
+extended():\"Multi-line\\ntext\\ntag\"
+]
+  end
+
+  it "deletes the tag file if it is empty" do
+    MSpec.delete_tag(@tag).should == true
+    MSpec.delete_tag(SpecTag.new("incomplete:The#best method ever")).should == true
+    MSpec.delete_tag(SpecTag.new("benchmark:The#fastest method today")).should == true
+    MSpec.delete_tag(SpecTag.new("extended:\"Multi-line\ntext\ntag\"")).should == true
+    File.exist?(tmp("tags.txt")).should == false
+  end
+end
+
+describe MSpec, ".delete_tags" do
+  before :each do
+    @tags = tmp("tags.txt")
+    FileUtils.cp File.dirname(__FILE__) + "/tags.txt", @tags
+    MSpec.stub!(:tags_file).and_return(@tags)
+  end
+
+  it "deletes the tag file" do
+    MSpec.delete_tags
+    File.exists?(@tags).should be_false
+  end
+end
+
+describe MSpec, ".expectation" do
+  it "sets the flag that an expectation has been reported" do
+    MSpec.clear_expectations
+    MSpec.expectation?.should be_false
+    MSpec.expectation
+    MSpec.expectation?.should be_true
+  end
+end
+
+describe MSpec, ".expectation?" do
+  it "returns true if an expectation has been reported" do
+    MSpec.expectation
+    MSpec.expectation?.should be_true
+  end
+
+  it "returns false if an expectation has not been reported" do
+    MSpec.clear_expectations
+    MSpec.expectation?.should be_false
+  end
+end
+
+describe MSpec, ".clear_expectations" do
+  it "clears the flag that an expectation has been reported" do
+    MSpec.expectation
+    MSpec.expectation?.should be_true
+    MSpec.clear_expectations
+    MSpec.expectation?.should be_false
+  end
+end
+
+describe MSpec, ".register_shared" do
+  it "stores a shared ContextState by description" do
+    parent = ContextState.new "container"
+    state = ContextState.new "shared"
+    state.parent = parent
+    prc = lambda { }
+    state.describe(&prc)
+    MSpec.register_shared(state)
+    MSpec.retrieve(:shared)["shared"].should == state
+  end
+end
+
+describe MSpec, ".retrieve_shared" do
+  it "retrieves the shared ContextState matching description" do
+    state = ContextState.new ""
+    MSpec.retrieve(:shared)["shared"] = state
+    MSpec.retrieve_shared(:shared).should == state
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/runner/shared_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/runner/shared_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/runner/shared_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,27 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/runner/shared'
+
+describe Object, "#it_behaves_like" do
+  before :each do
+    @recv = Object.new
+    def @recv.before(what)
+      yield
+    end
+    @recv.stub!(:it_should_behave_like)
+  end
+
+  it "creates @method set to the name of the aliased method" do
+    @recv.it_behaves_like "something", :some_method
+    @recv.instance_variable_get(:@method).should == :some_method
+  end
+
+  it "creates @object if the passed object is not nil" do
+    @recv.it_behaves_like "something", :some_method, :some_object
+    @recv.instance_variable_get(:@object).should == :some_object
+  end
+
+  it "sends :it_should_behave_like" do
+    @recv.should_receive(:it_should_behave_like)
+    @recv.it_behaves_like "something", :some_method
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/runner/tag_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/runner/tag_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/runner/tag_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,111 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/runner/tag'
+
+describe SpecTag do
+  it "accepts an optional string to parse into fields" do
+    tag = SpecTag.new "tag(comment):description"
+    tag.tag.should == "tag"
+    tag.comment.should == "comment"
+    tag.description.should == "description"
+  end
+end
+
+describe SpecTag, "#parse" do
+  before :each do
+    @tag = SpecTag.new
+  end
+
+  it "accepts 'tag(comment):description'" do
+    @tag.parse "tag(I'm real):Some#method returns a value"
+    @tag.tag.should == "tag"
+    @tag.comment.should == "I'm real"
+    @tag.description.should == "Some#method returns a value"
+  end
+
+  it "accepts 'tag:description'" do
+    @tag.parse "tag:Another#method"
+    @tag.tag.should == "tag"
+    @tag.comment.should == nil
+    @tag.description.should == "Another#method"
+  end
+
+  it "accepts 'tag():description'" do
+    @tag.parse "tag():Another#method"
+    @tag.tag.should == "tag"
+    @tag.comment.should == nil
+    @tag.description.should == "Another#method"
+  end
+
+  it "accepts 'tag:'" do
+    @tag.parse "tag:"
+    @tag.tag.should == "tag"
+    @tag.comment.should == nil
+    @tag.description.should == ""
+  end
+
+  it "accepts 'tag(bug:555):Another#method'" do
+    @tag.parse "tag(bug:555):Another#method"
+    @tag.tag.should == "tag"
+    @tag.comment.should == "bug:555"
+    @tag.description.should == "Another#method"
+  end
+
+  it "accepts 'tag(http://someplace.com/neato):Another#method'" do
+    @tag.parse "tag(http://someplace.com/neato):Another#method"
+    @tag.tag.should == "tag"
+    @tag.comment.should == "http://someplace.com/neato"
+    @tag.description.should == "Another#method"
+  end
+
+  it "accepts 'tag(comment):\"Multi-line\\ntext\"'" do
+    @tag.parse 'tag(comment):"Multi-line\ntext"'
+    @tag.tag.should == "tag"
+    @tag.comment.should == "comment"
+    @tag.description.should == "Multi-line\ntext"
+  end
+
+  it "ignores '#anything'" do
+    @tag.parse "# this could be a comment"
+    @tag.tag.should == nil
+    @tag.comment.should == nil
+    @tag.description.should == nil
+  end
+end
+
+describe SpecTag, "#to_s" do
+  it "formats itself as 'tag(comment):description'" do
+    txt = "tag(comment):description"
+    tag = SpecTag.new txt
+    tag.tag.should == "tag"
+    tag.comment.should == "comment"
+    tag.description.should == "description"
+    tag.to_s.should == txt
+  end
+
+  it "formats itself as 'tag:description" do
+    txt = "tag:description"
+    tag = SpecTag.new txt
+    tag.tag.should == "tag"
+    tag.comment.should == nil
+    tag.description.should == "description"
+    tag.to_s.should == txt
+  end
+
+  it "formats itself as 'tag(comment):\"multi-line\\ntext\\ntag\"'" do
+    txt = 'tag(comment):"multi-line\ntext\ntag"'
+    tag = SpecTag.new txt
+    tag.tag.should == "tag"
+    tag.comment.should == "comment"
+    tag.description.should == "multi-line\ntext\ntag"
+    tag.to_s.should == txt
+  end
+end
+
+describe SpecTag, "#==" do
+  it "returns true if the tags have the same fields" do
+    one = SpecTag.new "tag(this):unicorn"
+    two = SpecTag.new "tag(this):unicorn"
+    one.==(two).should == true
+    [one].==([two]).should == true
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/runner/tags.txt
===================================================================
--- MacRuby/branches/experimental/mspec/spec/runner/tags.txt	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/runner/tags.txt	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,4 @@
+fail(broken):Some#method? works
+incomplete(20%):The#best method ever
+benchmark(0.01825):The#fastest method today
+extended():"Multi-line\ntext\ntag"

Added: MacRuby/branches/experimental/mspec/spec/spec_helper.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/spec_helper.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/spec_helper.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,43 @@
+dir_path = File.dirname(__FILE__)
+lib_path = File.expand_path(dir_path + '/../lib')
+bin_path = File.expand_path(dir_path + '/..')
+$:.unshift lib_path unless $:.include? lib_path
+$:.unshift bin_path unless $:.include? bin_path
+
+require 'pp'
+require 'mspec/helpers/io'
+require 'mspec/helpers/scratch'
+
+# Remove this when MRI has intelligent warnings
+$VERBOSE = nil unless $VERBOSE
+
+class MOSConfig < Hash
+  def initialize
+    self[:includes]  = []
+    self[:requires]  = []
+    self[:flags]     = []
+    self[:options]   = []
+    self[:includes]  = []
+    self[:excludes]  = []
+    self[:patterns]  = []
+    self[:xpatterns] = []
+    self[:tags]      = []
+    self[:xtags]     = []
+    self[:atags]     = []
+    self[:astrings]  = []
+    self[:target]    = 'ruby'
+    self[:command]   = nil
+    self[:ltags]     = []
+    self[:files]     = []
+  end
+end
+
+def new_option
+  config = MOSConfig.new
+  return MSpecOptions.new("spec", 20, config), config
+end
+
+# Just to have an exception name output not be "Exception"
+class MSpecExampleError < Exception
+end
+

Added: MacRuby/branches/experimental/mspec/spec/utils/name_map_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/utils/name_map_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/utils/name_map_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,178 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/utils/name_map'
+
+module NameMapSpecs
+  class A
+    A = self
+
+    def self.a; end
+    def a; end
+    def c; end
+
+    class B
+      def b; end
+    end
+  end
+
+  class Error
+  end
+
+  class Fixnum
+    def f; end
+  end
+
+  def self.n; end
+  def n; end
+end
+
+describe NameMap, "#exception?" do
+  before :each do
+    @map = NameMap.new
+  end
+
+  it "returns true if the constant is Errno" do
+    @map.exception?("Errno").should == true
+  end
+
+  it "returns true if the constant is a kind of Exception" do
+    @map.exception?("Errno::EBADF").should == true
+    @map.exception?("LoadError").should == true
+    @map.exception?("SystemExit").should == true
+  end
+
+  it "returns false if the constant is not a kind of Exception" do
+    @map.exception?("NameMapSpecs::Error").should == false
+    @map.exception?("NameMapSpecs").should == false
+  end
+
+  it "returns false if the constant does not exist" do
+    @map.exception?("Nonexistent").should == false
+  end
+end
+
+describe NameMap, "#class_or_module" do
+  before :each do
+    @map = NameMap.new true
+  end
+
+  it "returns the constant specified by the string" do
+    @map.class_or_module("NameMapSpecs").should == NameMapSpecs
+  end
+
+  it "returns the constant specified by the 'A::B' string" do
+    @map.class_or_module("NameMapSpecs::A").should == NameMapSpecs::A
+  end
+
+  it "returns nil if the constant is not a class or module" do
+    @map.class_or_module("Float::MAX").should == nil
+  end
+
+  it "returns nil if the constant is in the set of excluded constants" do
+    excluded = %w[
+      MSpecScript
+      MkSpec
+      DTracer
+      NameMap
+      OptionParser
+      YAML
+    ]
+
+    excluded.each do |const|
+      @map.class_or_module(const).should == nil
+    end
+  end
+
+  it "returns nil if the constant does not exist" do
+    @map.class_or_module("Heaven").should == nil
+    @map.class_or_module("Hell").should == nil
+    @map.class_or_module("Bush::Brain").should == nil
+  end
+end
+
+describe NameMap, "#dir_name" do
+  before :each do
+    @map = NameMap.new
+  end
+
+  it "returns a directory name from the base name and constant" do
+    @map.dir_name("NameMapSpecs", 'spec/core').should == 'spec/core/namemapspecs'
+  end
+
+  it "returns a directory name from the components in the constants name" do
+    @map.dir_name("NameMapSpecs::A", 'spec').should == 'spec/namemapspecs/a'
+    @map.dir_name("NameMapSpecs::A::B", 'spec').should == 'spec/namemapspecs/a/b'
+  end
+
+  it "returns a directory name without 'class' for constants like TrueClass" do
+    @map.dir_name("TrueClass", 'spec').should == 'spec/true'
+    @map.dir_name("FalseClass", 'spec').should == 'spec/false'
+  end
+
+  it "returns 'exception' for the directory name of any Exception subclass" do
+    @map.dir_name("SystemExit", 'spec').should == 'spec/exception'
+    @map.dir_name("Errno::EBADF", 'spec').should == 'spec/exception'
+  end
+
+  it "returns 'class' for Class" do
+    @map.dir_name("Class", 'spec').should == 'spec/class'
+  end
+end
+
+# These specs do not cover all the mappings, but only describe how the
+# name is derived when the hash item maps to a single value, a hash with
+# a specific item, or a hash with a :default item.
+describe NameMap, "#file_name" do
+  before :each do
+    @map = NameMap.new
+  end
+
+  it "returns the name of the spec file based on the constant and method" do
+    @map.file_name("[]=", "Array").should == "element_set_spec.rb"
+  end
+
+  it "returns the name of the spec file based on the special entry for the method" do
+    @map.file_name("~", "Regexp").should == "match_spec.rb"
+    @map.file_name("~", "Fixnum").should == "complement_spec.rb"
+  end
+
+  it "returns the name of the spec file based on the default entry for the method" do
+    @map.file_name("<<", "NameMapSpecs").should == "append_spec.rb"
+  end
+
+  it "uses the last component of the constant to look up the method name" do
+    @map.file_name("^", "NameMapSpecs::Fixnum").should == "bit_xor_spec.rb"
+  end
+end
+
+describe NameMap, "#namespace" do
+  before :each do
+    @map = NameMap.new
+  end
+
+  it "prepends the module to the constant name" do
+    @map.namespace("SubModule", Fixnum).should == "SubModule::Fixnum"
+  end
+
+  it "does not prepend Object, Class, or Module to the constant name" do
+    @map.namespace("Object", Fixnum).should == "Fixnum"
+    @map.namespace("Module", Integer).should == "Integer"
+    @map.namespace("Class", Float).should == "Float"
+  end
+end
+
+describe NameMap, "#map" do
+  before :each do
+    @map = NameMap.new
+  end
+
+  it "flattens an object hierarchy into a single Hash" do
+    @map.map({}, [NameMapSpecs]).should == {
+      "NameMapSpecs."         => ["n"],
+      "NameMapSpecs#"         => ["n"],
+      "NameMapSpecs::A."      => ["a"],
+      "NameMapSpecs::A#"      => ["a", "c"],
+      "NameMapSpecs::A::B#"   => ["b"],
+      "NameMapSpecs::Fixnum#" => ["f"]
+    }
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/utils/options_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/utils/options_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/utils/options_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,1291 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/utils/options'
+require 'mspec/version'
+require 'mspec/guards/guard'
+require 'mspec/runner/mspec'
+require 'mspec/runner/formatters'
+
+describe MSpecOption, ".new" do
+  before :each do
+    @opt = MSpecOption.new("-a", "--bdc", "ARG", "desc", :block)
+  end
+
+  it "sets the short attribute" do
+    @opt.short.should == "-a"
+  end
+
+  it "sets the long attribute" do
+    @opt.long.should == "--bdc"
+  end
+
+  it "sets the arg attribute" do
+    @opt.arg.should == "ARG"
+  end
+
+  it "sets the description attribute" do
+    @opt.description.should == "desc"
+  end
+
+  it "sets the block attribute" do
+    @opt.block.should == :block
+  end
+end
+
+describe MSpecOption, "#arg?" do
+  it "returns true if arg attribute is not nil" do
+    MSpecOption.new(nil, nil, "ARG", nil, nil).arg?.should be_true
+  end
+
+  it "returns false if arg attribute is nil" do
+    MSpecOption.new(nil, nil, nil, nil, nil).arg?.should be_false
+  end
+end
+
+describe MSpecOption, "#match?" do
+  before :each do
+    @opt = MSpecOption.new("-a", "--bdc", "ARG", "desc", :block)
+  end
+
+  it "returns true if the argument matches the short option" do
+    @opt.match?("-a").should be_true
+  end
+
+  it "returns true if the argument matches the long option" do
+    @opt.match?("--bdc").should be_true
+  end
+
+  it "returns false if the argument matches neither the short nor long option" do
+    @opt.match?("-b").should be_false
+    @opt.match?("-abdc").should be_false
+  end
+end
+
+describe MSpecOptions, ".new" do
+  before :each do
+    @opt = MSpecOptions.new("cmd", 20, :config)
+  end
+
+  it "sets the banner attribute" do
+    @opt.banner.should == "cmd"
+  end
+
+  it "sets the config attribute" do
+    @opt.config.should == :config
+  end
+
+  it "sets the width attribute" do
+    @opt.width.should == 20
+  end
+
+  it "sets the default width attribute" do
+    MSpecOptions.new.width.should == 30
+  end
+end
+
+describe MSpecOptions, "#on" do
+  before :each do
+    @opt = MSpecOptions.new
+  end
+
+  it "adds a short option" do
+    @opt.should_receive(:add).with("-a", nil, nil, "desc", nil)
+    @opt.on("-a", "desc")
+  end
+
+  it "adds a short option taking an argument" do
+    @opt.should_receive(:add).with("-a", nil, "ARG", "desc", nil)
+    @opt.on("-a", "ARG", "desc")
+  end
+
+  it "adds a long option" do
+    @opt.should_receive(:add).with("-a", nil, nil, "desc", nil)
+    @opt.on("-a", "desc")
+  end
+
+  it "adds a long option taking an argument" do
+    @opt.should_receive(:add).with("-a", nil, nil, "desc", nil)
+    @opt.on("-a", "desc")
+  end
+
+  it "adds a short and long option" do
+    @opt.should_receive(:add).with("-a", nil, nil, "desc", nil)
+    @opt.on("-a", "desc")
+  end
+
+  it "adds a short and long option taking an argument" do
+    @opt.should_receive(:add).with("-a", nil, nil, "desc", nil)
+    @opt.on("-a", "desc")
+  end
+
+  it "raises MSpecOptions::OptionError if pass less than 2 arguments" do
+    lambda { @opt.on    }.should raise_error(MSpecOptions::OptionError)
+    lambda { @opt.on "" }.should raise_error(MSpecOptions::OptionError)
+  end
+end
+
+describe MSpecOptions, "#add" do
+  before :each do
+    @opt = MSpecOptions.new "cmd", 20
+    @prc = lambda { }
+  end
+
+  it "adds documentation for an option" do
+    @opt.should_receive(:doc).with("   -t, --typo ARG   Correct typo ARG")
+    @opt.add("-t", "--typo", "ARG", "Correct typo ARG", @prc)
+  end
+
+  it "leaves spaces in the documentation for a missing short option" do
+    @opt.should_receive(:doc).with("       --typo ARG   Correct typo ARG")
+    @opt.add(nil, "--typo", "ARG", "Correct typo ARG", @prc)
+  end
+
+  it "handles a short option with argument but no long argument" do
+    @opt.should_receive(:doc).with("   -t ARG           Correct typo ARG")
+    @opt.add("-t", nil, "ARG", "Correct typo ARG", @prc)
+  end
+
+  it "registers an option" do
+    option = MSpecOption.new "-t", "--typo", "ARG", "Correct typo ARG", @prc
+    MSpecOption.should_receive(:new).with(
+        "-t", "--typo", "ARG", "Correct typo ARG", @prc).and_return(option)
+    @opt.add("-t", "--typo", "ARG", "Correct typo ARG", @prc)
+    @opt.options.should == [option]
+  end
+end
+
+describe MSpecOptions, "#match?" do
+  before :each do
+    @opt = MSpecOptions.new
+  end
+
+  it "returns the MSpecOption instance matching the argument" do
+    @opt.on "-a", "--abdc", "desc"
+    option = @opt.match? "-a"
+    @opt.match?("--abdc").should be(option)
+    option.should be_kind_of(MSpecOption)
+    option.short.should == "-a"
+    option.long.should == "--abdc"
+    option.description.should == "desc"
+  end
+end
+
+describe MSpecOptions, "#process" do
+  before :each do
+    @opt = MSpecOptions.new
+    ScratchPad.clear
+  end
+
+  it "calls the on_extra block if the argument does not match any option" do
+    @opt.on_extra { ScratchPad.record :extra }
+    @opt.process ["-a"], "-a", "-a", nil
+    ScratchPad.recorded.should == :extra
+  end
+
+  it "returns the matching option" do
+    @opt.on "-a", "ARG", "desc"
+    option = @opt.process [], "-a", "-a", "ARG"
+    option.should be_kind_of(MSpecOption)
+    option.short.should == "-a"
+    option.arg.should == "ARG"
+    option.description.should == "desc"
+  end
+
+  it "raises an MSpecOptions::ParseError if arg is nil and there are no more entries in argv" do
+    @opt.on "-a", "ARG", "desc"
+    lambda { @opt.process [], "-a", "-a", nil }.should raise_error(MSpecOptions::ParseError)
+  end
+
+  it "fetches the argument for the option from argv if arg is nil" do
+    @opt.on("-a", "ARG", "desc") { |o| ScratchPad.record o }
+    @opt.process ["ARG"], "-a", "-a", nil
+    ScratchPad.recorded.should == "ARG"
+  end
+
+  it "calls the option's block" do
+    @opt.on("-a", "ARG", "desc") { ScratchPad.record :option }
+    @opt.process [], "-a", "-a", "ARG"
+    ScratchPad.recorded.should == :option
+  end
+
+  it "does not call the option's block if it is nil" do
+    @opt.on "-a", "ARG", "desc"
+    lambda { @opt.process [], "-a", "-a", "ARG" }.should_not raise_error
+  end
+end
+
+describe MSpecOptions, "#split" do
+  before :each do
+    @opt = MSpecOptions.new
+  end
+
+  it "breaks a string at the nth character" do
+    opt, arg, rest = @opt.split "-bdc", 2
+    opt.should == "-b"
+    arg.should == "dc"
+    rest.should == "dc"
+  end
+
+  it "returns nil for arg if there are no characters left" do
+    opt, arg, rest = @opt.split "-b", 2
+    opt.should == "-b"
+    arg.should == nil
+    rest.should == ""
+  end
+end
+
+describe MSpecOptions, "#parse" do
+  before :each do
+    @opt = MSpecOptions.new
+    @prc = lambda { |o| ScratchPad.record [:parsed, o] }
+    ScratchPad.clear
+  end
+
+  it "parses a short option" do
+    @opt.on "-a", "desc", &@prc
+    @opt.parse ["-a"]
+    ScratchPad.recorded.should == [:parsed, nil]
+  end
+
+  it "parse a long option" do
+    @opt.on "--abdc", "desc", &@prc
+    @opt.parse ["--abdc"]
+    ScratchPad.recorded.should == [:parsed, nil]
+  end
+
+  it "parses a short option group" do
+    @opt.on "-a", "ARG", "desc", &@prc
+    @opt.parse ["-a", "ARG"]
+    ScratchPad.recorded.should == [:parsed, "ARG"]
+  end
+
+  it "parses a short option with an argument" do
+    @opt.on "-a", "ARG", "desc", &@prc
+    @opt.parse ["-a", "ARG"]
+    ScratchPad.recorded.should == [:parsed, "ARG"]
+  end
+
+  it "parses a short option with connected argument" do
+    @opt.on "-a", "ARG", "desc", &@prc
+    @opt.parse ["-aARG"]
+    ScratchPad.recorded.should == [:parsed, "ARG"]
+  end
+
+  it "parses a long option with an argument" do
+    @opt.on "--abdc", "ARG", "desc", &@prc
+    @opt.parse ["--abdc", "ARG"]
+    ScratchPad.recorded.should == [:parsed, "ARG"]
+  end
+
+  it "parses a long option with an '=' argument" do
+    @opt.on "--abdc", "ARG", "desc", &@prc
+    @opt.parse ["--abdc=ARG"]
+    ScratchPad.recorded.should == [:parsed, "ARG"]
+  end
+
+  it "parses a short option group with the final option taking an argument" do
+    ScratchPad.record []
+    @opt.on("-a", "desc") { |o| ScratchPad << :a }
+    @opt.on("-b", "ARG", "desc") { |o| ScratchPad << [:b, o] }
+    @opt.parse ["-ab", "ARG"]
+    ScratchPad.recorded.should == [:a, [:b, "ARG"]]
+  end
+
+  it "parses a short option group with a connected argument" do
+    ScratchPad.record []
+    @opt.on("-a", "desc") { |o| ScratchPad << :a }
+    @opt.on("-b", "ARG", "desc") { |o| ScratchPad << [:b, o] }
+    @opt.on("-c", "desc") { |o| ScratchPad << :c }
+    @opt.parse ["-acbARG"]
+    ScratchPad.recorded.should == [:a, :c, [:b, "ARG"]]
+  end
+
+  it "returns the unprocessed entries" do
+    @opt.on "-a", "ARG", "desc", &@prc
+    @opt.parse(["abdc", "-a", "ilny"]).should == ["abdc"]
+  end
+
+  it "calls the on_extra handler with unrecognized options" do
+    ScratchPad.record []
+    @opt.on_extra { |o| ScratchPad << o }
+    @opt.on "-a", "desc"
+    @opt.parse ["-a", "-b"]
+    ScratchPad.recorded.should == ["-b"]
+  end
+
+  it "does not attempt to call the block if it is nil" do
+    @opt.on "-a", "ARG", "desc"
+    @opt.parse(["-a", "ARG"]).should == []
+  end
+
+  it "raises MSpecOptions::ParseError if passed an unrecognized option" do
+    @opt.should_receive(:raise).with(MSpecOptions::ParseError, an_instance_of(String))
+    @opt.stub!(:puts)
+    @opt.stub!(:exit)
+    @opt.parse "-u"
+  end
+end
+
+describe MSpecOptions, "#banner=" do
+  before :each do
+    @opt = MSpecOptions.new
+  end
+
+  it "sets the banner attribute" do
+    @opt.banner.should == ""
+    @opt.banner = "banner"
+    @opt.banner.should == "banner"
+  end
+end
+
+describe MSpecOptions, "#width=" do
+  before :each do
+    @opt = MSpecOptions.new
+  end
+
+  it "sets the width attribute" do
+    @opt.width.should == 30
+    @opt.width = 20
+    @opt.width.should == 20
+  end
+end
+
+describe MSpecOptions, "#config=" do
+  before :each do
+    @opt = MSpecOptions.new
+  end
+
+  it "sets the config attribute" do
+    @opt.config.should be_nil
+    @opt.config = :config
+    @opt.config.should == :config
+  end
+end
+
+describe MSpecOptions, "#doc" do
+  before :each do
+    @opt = MSpecOptions.new "command"
+  end
+
+  it "adds text to be displayed with #to_s" do
+    @opt.doc "Some message"
+    @opt.doc "Another message"
+    @opt.to_s.should == <<-EOD
+command
+
+Some message
+Another message
+EOD
+  end
+end
+
+describe MSpecOptions, "#version" do
+  before :each do
+    @opt = MSpecOptions.new
+    ScratchPad.clear
+  end
+
+  it "installs a basic -v, --version option" do
+    @opt.should_receive(:puts)
+    @opt.should_receive(:exit)
+    @opt.version "1.0.0"
+    @opt.parse "-v"
+  end
+
+  it "accepts a block instead of using the default block" do
+    @opt.version("1.0.0") { |o| ScratchPad.record :version }
+    @opt.parse "-v"
+    ScratchPad.recorded.should == :version
+  end
+end
+
+describe MSpecOptions, "#help" do
+  before :each do
+    @opt = MSpecOptions.new
+    ScratchPad.clear
+  end
+
+  it "installs a basic -h, --help option" do
+    @opt.should_receive(:puts)
+    @opt.should_receive(:exit).with(1)
+    @opt.help
+    @opt.parse "-h"
+  end
+
+  it "accepts a block instead of using the default block" do
+    @opt.help { |o| ScratchPad.record :help }
+    @opt.parse "-h"
+    ScratchPad.recorded.should == :help
+  end
+end
+
+describe MSpecOptions, "#on_extra" do
+  before :each do
+    @opt = MSpecOptions.new
+    ScratchPad.clear
+  end
+
+  it "registers a block to be called when an option is not recognized" do
+    @opt.on_extra { ScratchPad.record :extra }
+    @opt.parse "-g"
+    ScratchPad.recorded.should == :extra
+  end
+end
+
+describe MSpecOptions, "#to_s" do
+  before :each do
+    @opt = MSpecOptions.new "command"
+  end
+
+  it "returns the banner and descriptive strings for all registered options" do
+    @opt.on "-t", "--this ARG", "Adds this ARG to the list"
+    @opt.to_s.should == <<-EOD
+command
+
+   -t, --this ARG             Adds this ARG to the list
+EOD
+  end
+end
+
+describe "The -B, --config FILE option" do
+  before :each do
+    @options, @config = new_option
+  end
+
+  it "is enabled with #configure { }" do
+    @options.should_receive(:on).with("-B", "--config", "FILE",
+        an_instance_of(String))
+    @options.configure {}
+  end
+
+  it "calls the passed block" do
+    ["-B", "--config"].each do |opt|
+      ScratchPad.clear
+
+      @options.configure { |x| ScratchPad.record x }
+      @options.parse [opt, "file"]
+      ScratchPad.recorded.should == "file"
+    end
+  end
+end
+
+describe "The -C, --chdir DIR option" do
+  before :each do
+    @options, @config = new_option
+    @options.chdir
+  end
+
+  it "is enabled with #chdir" do
+    @options.should_receive(:on).with("-C", "--chdir", "DIR",
+        an_instance_of(String))
+    @options.chdir
+  end
+
+  it "changes the working directory to DIR" do
+    Dir.should_receive(:chdir).with("dir").twice
+    ["-C", "--chdir"].each do |opt|
+      @options.parse [opt, "dir"]
+    end
+  end
+end
+
+describe "The --prefix STR option" do
+  before :each do
+    @options, @config = new_option
+  end
+
+  it "is enabled with #prefix" do
+    @options.should_receive(:on).with("--prefix", "STR",
+       an_instance_of(String))
+    @options.prefix
+  end
+
+  it "sets the prefix config value" do
+    @options.prefix
+    @options.parse ["--prefix", "some/dir"]
+    @config[:prefix].should == "some/dir"
+  end
+end
+
+describe "The -n, --name RUBY_NAME option" do
+  before :each do
+    @verbose, $VERBOSE = $VERBOSE, nil
+    @options, @config = new_option
+  end
+
+  after :each do
+    $VERBOSE = @verbose
+  end
+
+  it "is enabled with #name" do
+    @options.should_receive(:on).with("-n", "--name", "RUBY_NAME",
+        an_instance_of(String))
+    @options.name
+  end
+
+  it "sets RUBY_NAME when invoked" do
+    Object.should_receive(:const_set).with(:RUBY_NAME, "name").twice
+    @options.name
+    @options.parse ["-n", "name"]
+    @options.parse ["--name", "name"]
+  end
+end
+
+describe "The -t, --target TARGET option" do
+  before :each do
+    @options, @config = new_option
+    @options.targets
+  end
+
+  it "is enabled with #targets" do
+    @options.stub!(:on)
+    @options.should_receive(:on).with("-t", "--target", "TARGET",
+        an_instance_of(String))
+    @options.targets
+  end
+
+  it "sets the target to 'ruby' and flags to verbose with TARGET 'ruby'" do
+    ["-t", "--target"].each do |opt|
+      @options.parse [opt, "ruby"]
+      @config[:target].should == "ruby"
+    end
+  end
+
+  it "sets the target to 'ruby1.9' with TARGET 'r19', 'ruby19' or 'ruby1.9'" do
+    ["-t", "--target"].each do |opt|
+      ["r19", "ruby19", "ruby1.9"].each do |t|
+        @options.parse [opt, t]
+        @config[:target].should == "ruby1.9"
+      end
+    end
+  end
+
+  it "sets the target to 'jruby' with TARGET 'j' or 'jruby'" do
+    ["-t", "--target"].each do |opt|
+      ["j", "jruby"].each do |t|
+        @options.parse [opt, t]
+        @config[:target].should == "jruby"
+      end
+    end
+  end
+
+  it "sets the target to 'shotgun/rubinius' with TARGET 'x' or 'rubinius'" do
+    ["-t", "--target"].each do |opt|
+      ["x", "rubinius"].each do |t|
+        @options.parse [opt, t]
+        @config[:target].should == "./bin/rbx"
+      end
+    end
+  end
+
+  it "set the target to 'rbx' with TARGET 'rbx'" do
+    ["-t", "--target"].each do |opt|
+      ["X", "rbx"].each do |t|
+        @options.parse [opt, t]
+        @config[:target].should == "rbx"
+      end
+    end
+  end
+
+  it "sets the target to TARGET" do
+    ["-t", "--target"].each do |opt|
+      @options.parse [opt, "whateva"]
+      @config[:target].should == "whateva"
+    end
+  end
+end
+
+describe "The -T, --target-opt OPT option" do
+  before :each do
+    @options, @config = new_option
+    @options.targets
+  end
+
+  it "is enabled with #targets" do
+    @options.stub!(:on)
+    @options.should_receive(:on).with("-T", "--target-opt", "OPT",
+        an_instance_of(String))
+    @options.targets
+  end
+
+  it "adds OPT to flags" do
+    ["-T", "--target-opt"].each do |opt|
+      @config[:flags].delete "--whateva"
+      @options.parse [opt, "--whateva"]
+      @config[:flags].should include("--whateva")
+    end
+  end
+end
+
+describe "The -I, --include DIR option" do
+  before :each do
+    @options, @config = new_option
+    @options.targets
+  end
+
+  it "is enabled with #targets" do
+    @options.stub!(:on)
+    @options.should_receive(:on).with("-I", "--include", "DIR",
+        an_instance_of(String))
+    @options.targets
+  end
+
+  it "add DIR to the includes list" do
+    ["-I", "--include"].each do |opt|
+      @config[:includes].delete "-Ipackage"
+      @options.parse [opt, "package"]
+      @config[:includes].should include("-Ipackage")
+    end
+  end
+end
+
+describe "The -r, --require LIBRARY option" do
+  before :each do
+    @options, @config = new_option
+    @options.targets
+  end
+
+  it "is enabled with #targets" do
+    @options.stub!(:on)
+    @options.should_receive(:on).with("-r", "--require", "LIBRARY",
+        an_instance_of(String))
+    @options.targets
+  end
+
+  it "adds LIBRARY to the requires list" do
+    ["-r", "--require"].each do |opt|
+      @config[:requires].delete "-rlibrick"
+      @options.parse [opt, "librick"]
+      @config[:requires].should include("-rlibrick")
+    end
+  end
+end
+
+describe "The -f, --format FORMAT option" do
+  before :each do
+    @options, @config = new_option
+    @options.formatters
+  end
+
+  it "is enabled with #formatters" do
+    @options.stub!(:on)
+    @options.should_receive(:on).with("-f", "--format", "FORMAT",
+        an_instance_of(String))
+    @options.formatters
+  end
+
+  it "sets the SpecdocFormatter with FORMAT 's' or 'specdoc'" do
+    ["-f", "--format"].each do |opt|
+      ["s", "specdoc"].each do |f|
+        @config[:formatter] = nil
+        @options.parse [opt, f]
+        @config[:formatter].should == SpecdocFormatter
+      end
+    end
+  end
+
+  it "sets the HtmlFormatter with FORMAT 'h' or 'html'" do
+    ["-f", "--format"].each do |opt|
+      ["h", "html"].each do |f|
+        @config[:formatter] = nil
+        @options.parse [opt, f]
+        @config[:formatter].should == HtmlFormatter
+      end
+    end
+  end
+
+  it "sets the DottedFormatter with FORMAT 'd', 'dot' or 'dotted'" do
+    ["-f", "--format"].each do |opt|
+      ["d", "dot", "dotted"].each do |f|
+        @config[:formatter] = nil
+        @options.parse [opt, f]
+        @config[:formatter].should == DottedFormatter
+      end
+    end
+  end
+
+  it "sets the DescribeFormatter with FORMAT 'b' or 'describe'" do
+    ["-f", "--format"].each do |opt|
+      ["b", "describe"].each do |f|
+        @config[:formatter] = nil
+        @options.parse [opt, f]
+        @config[:formatter].should == DescribeFormatter
+      end
+    end
+  end
+
+  it "sets the FileFormatter with FORMAT 'f', 'file'" do
+    ["-f", "--format"].each do |opt|
+      ["f", "file"].each do |f|
+        @config[:formatter] = nil
+        @options.parse [opt, f]
+        @config[:formatter].should == FileFormatter
+      end
+    end
+  end
+
+  it "sets the UnitdiffFormatter with FORMAT 'u', 'unit', or 'unitdiff'" do
+    ["-f", "--format"].each do |opt|
+      ["u", "unit", "unitdiff"].each do |f|
+        @config[:formatter] = nil
+        @options.parse [opt, f]
+        @config[:formatter].should == UnitdiffFormatter
+      end
+    end
+  end
+
+  it "sets the SummaryFormatter with FORMAT 'm' or 'summary'" do
+    ["-f", "--format"].each do |opt|
+      ["m", "summary"].each do |f|
+        @config[:formatter] = nil
+        @options.parse [opt, f]
+        @config[:formatter].should == SummaryFormatter
+      end
+    end
+  end
+
+  it "sets the SpinnerFormatter with FORMAT 'a', '*', or 'spin'" do
+    ["-f", "--format"].each do |opt|
+      ["a", "*", "spin"].each do |f|
+        @config[:formatter] = nil
+        @options.parse [opt, f]
+        @config[:formatter].should == SpinnerFormatter
+      end
+    end
+  end
+
+  it "sets the MethodFormatter with FORMAT 't' or 'method'" do
+    ["-f", "--format"].each do |opt|
+      ["t", "method"].each do |f|
+        @config[:formatter] = nil
+        @options.parse [opt, f]
+        @config[:formatter].should == MethodFormatter
+      end
+    end
+  end
+
+  it "sets the YamlFormatter with FORMAT 'y' or 'yaml'" do
+    ["-f", "--format"].each do |opt|
+      ["y", "yaml"].each do |f|
+        @config[:formatter] = nil
+        @options.parse [opt, f]
+        @config[:formatter].should == YamlFormatter
+      end
+    end
+  end
+end
+
+describe "The -o, --output FILE option" do
+  before :each do
+    @options, @config = new_option
+    @options.formatters
+  end
+
+  it "is enabled with #formatters" do
+    @options.stub!(:on)
+    @options.should_receive(:on).with("-o", "--output", "FILE",
+        an_instance_of(String))
+    @options.formatters
+  end
+
+  it "sets the output to FILE" do
+    ["-o", "--output"].each do |opt|
+      @config[:output] = nil
+      @options.parse [opt, "some/file"]
+      @config[:output].should == "some/file"
+    end
+  end
+end
+
+describe "The -e, --example STR" do
+  before :each do
+    @options, @config = new_option
+    @options.filters
+  end
+
+  it "is enabled with #filters" do
+    @options.stub!(:on)
+    @options.should_receive(:on).with("-e", "--example", "STR",
+        an_instance_of(String))
+    @options.filters
+  end
+
+  it "adds STR to the includes list" do
+    ["-e", "--example"].each do |opt|
+      @config[:includes] = []
+      @options.parse [opt, "this spec"]
+      @config[:includes].should include("this spec")
+    end
+  end
+end
+
+describe "The -E, --exclude STR" do
+  before :each do
+    @options, @config = new_option
+    @options.filters
+  end
+
+  it "is enabled with #filters" do
+    @options.stub!(:on)
+    @options.should_receive(:on).with("-E", "--exclude", "STR",
+        an_instance_of(String))
+    @options.filters
+  end
+
+  it "adds STR to the excludes list" do
+    ["-E", "--exclude"].each do |opt|
+      @config[:excludes] = []
+      @options.parse [opt, "this spec"]
+      @config[:excludes].should include("this spec")
+    end
+  end
+end
+
+describe "The -p, --pattern PATTERN" do
+  before :each do
+    @options, @config = new_option
+    @options.filters
+  end
+
+  it "is enabled with #filters" do
+    @options.stub!(:on)
+    @options.should_receive(:on).with("-p", "--pattern", "PATTERN",
+        an_instance_of(String))
+    @options.filters
+  end
+
+  it "adds PATTERN to the included patterns list" do
+    ["-p", "--pattern"].each do |opt|
+      @config[:patterns] = []
+      @options.parse [opt, "this spec"]
+      @config[:patterns].should include(/this spec/)
+    end
+  end
+end
+
+describe "The -P, --excl-pattern PATTERN" do
+  before :each do
+    @options, @config = new_option
+    @options.filters
+  end
+
+  it "is enabled with #filters" do
+    @options.stub!(:on)
+    @options.should_receive(:on).with("-P", "--excl-pattern", "PATTERN",
+        an_instance_of(String))
+    @options.filters
+  end
+
+  it "adds PATTERN to the excluded patterns list" do
+    ["-P", "--excl-pattern"].each do |opt|
+      @config[:xpatterns] = []
+      @options.parse [opt, "this spec"]
+      @config[:xpatterns].should include(/this spec/)
+    end
+  end
+end
+
+describe "The -g, --tag TAG" do
+  before :each do
+    @options, @config = new_option
+    @options.filters
+  end
+
+  it "is enabled with #filters" do
+    @options.stub!(:on)
+    @options.should_receive(:on).with("-g", "--tag", "TAG",
+        an_instance_of(String))
+    @options.filters
+  end
+
+  it "adds TAG to the included tags list" do
+    ["-g", "--tag"].each do |opt|
+      @config[:tags] = []
+      @options.parse [opt, "this spec"]
+      @config[:tags].should include("this spec")
+    end
+  end
+end
+
+describe "The -G, --excl-tag TAG" do
+  before :each do
+    @options, @config = new_option
+    @options.filters
+  end
+
+  it "is enabled with #filters" do
+    @options.stub!(:on)
+    @options.should_receive(:on).with("-G", "--excl-tag", "TAG",
+        an_instance_of(String))
+    @options.filters
+  end
+
+  it "adds TAG to the excluded tags list" do
+    ["-G", "--excl-tag"].each do |opt|
+      @config[:xtags] = []
+      @options.parse [opt, "this spec"]
+      @config[:xtags].should include("this spec")
+    end
+  end
+end
+
+describe "The -w, --profile FILE option" do
+  before :each do
+    @options, @config = new_option
+    @options.filters
+  end
+
+  it "is enabled with #filters" do
+    @options.stub!(:on)
+    @options.should_receive(:on).with("-w", "--profile", "FILE",
+        an_instance_of(String))
+    @options.filters
+  end
+
+  it "adds FILE to the included profiles list" do
+    ["-w", "--profile"].each do |opt|
+      @config[:profiles] = []
+      @options.parse [opt, "spec/profiles/rails.yaml"]
+      @config[:profiles].should include("spec/profiles/rails.yaml")
+    end
+  end
+end
+
+describe "The -W, --excl-profile FILE option" do
+  before :each do
+    @options, @config = new_option
+    @options.filters
+  end
+
+  it "is enabled with #filters" do
+    @options.stub!(:on)
+    @options.should_receive(:on).with("-W", "--excl-profile", "FILE",
+        an_instance_of(String))
+    @options.filters
+  end
+
+  it "adds FILE to the excluded profiles list" do
+    ["-W", "--excl-profile"].each do |opt|
+      @config[:xprofiles] = []
+      @options.parse [opt, "spec/profiles/rails.yaml"]
+      @config[:xprofiles].should include("spec/profiles/rails.yaml")
+    end
+  end
+end
+
+describe "The -Z, --dry-run option" do
+  before :each do
+    @options, @config = new_option
+    @options.pretend
+  end
+
+  it "is enabled with #pretend" do
+    @options.should_receive(:on).with("-Z", "--dry-run", an_instance_of(String))
+    @options.pretend
+  end
+
+  it "registers the MSpec pretend mode" do
+    MSpec.should_receive(:register_mode).with(:pretend).twice
+    ["-Z", "--dry-run"].each do |opt|
+      @options.parse opt
+    end
+  end
+end
+
+describe "The --background option" do
+  before :each do
+    @options, @config = new_option
+  end
+
+  it "is enabled with #background" do
+    @options.should_receive(:on).with("--background", an_instance_of(String))
+    @options.background
+  end
+
+  it "registers the MSpec background mode" do
+    MSpec.should_receive(:register_mode).with(:background)
+    @options.background
+    @options.parse "--background"
+  end
+end
+
+describe "The --unguarded option" do
+  before :each do
+    @options, @config = new_option
+    @options.unguarded
+  end
+
+  it "is enabled with #unguarded" do
+    @options.stub!(:on)
+    @options.should_receive(:on).with("--unguarded", an_instance_of(String))
+    @options.unguarded
+  end
+
+  it "registers the MSpec unguarded mode" do
+    MSpec.should_receive(:register_mode).with(:unguarded)
+    @options.parse "--unguarded"
+  end
+end
+
+describe "The --no-ruby_guard option" do
+  before :each do
+    @options, @config = new_option
+    @options.unguarded
+  end
+
+  it "is enabled with #unguarded" do
+    @options.stub!(:on)
+    @options.should_receive(:on).with("--no-ruby_bug", an_instance_of(String))
+    @options.unguarded
+  end
+
+  it "registers the MSpec no_ruby_bug mode" do
+    MSpec.should_receive(:register_mode).with(:no_ruby_bug)
+    @options.parse "--no-ruby_bug"
+  end
+end
+
+describe "The -H, --random option" do
+  before :each do
+    @options, @config = new_option
+    @options.randomize
+  end
+
+  it "is enabled with #randomize" do
+    @options.should_receive(:on).with("-H", "--random", an_instance_of(String))
+    @options.randomize
+  end
+
+  it "registers the MSpec randomize mode" do
+    MSpec.should_receive(:randomize).twice
+    ["-H", "--random"].each do |opt|
+      @options.parse opt
+    end
+  end
+end
+
+describe "The -V, --verbose option" do
+  before :each do
+    @options, @config = new_option
+    @options.verbose
+  end
+
+  it "is enabled with #verbose" do
+    @options.stub!(:on)
+    @options.should_receive(:on).with("-V", "--verbose", an_instance_of(String))
+    @options.verbose
+  end
+
+  it "registers a verbose output object with MSpec" do
+    MSpec.should_receive(:register).with(:start, anything()).twice
+    MSpec.should_receive(:register).with(:load, anything()).twice
+    ["-V", "--verbose"].each do |opt|
+      @options.parse opt
+    end
+  end
+end
+
+describe "The -m, --marker MARKER option" do
+  before :each do
+    @options, @config = new_option
+    @options.verbose
+  end
+
+  it "is enabled with #verbose" do
+    @options.stub!(:on)
+    @options.should_receive(:on).with("-m", "--marker", "MARKER",
+        an_instance_of(String))
+    @options.verbose
+  end
+
+  it "registers a marker output object with MSpec" do
+    MSpec.should_receive(:register).with(:load, anything()).twice
+    ["-m", "--marker"].each do |opt|
+      @options.parse [opt, ","]
+    end
+  end
+end
+
+describe "The --int-spec option" do
+  before :each do
+    @options, @config = new_option
+    @options.interrupt
+  end
+
+  it "is enabled with #interrupt" do
+    @options.should_receive(:on).with("--int-spec", an_instance_of(String))
+    @options.interrupt
+  end
+
+  it "sets the abort config option to false to only abort the running spec with ^C" do
+    @config[:abort] = true
+    @options.parse "--int-spec"
+    @config[:abort].should == false
+  end
+end
+
+describe "The -Y, --verify option" do
+  before :each do
+    @options, @config = new_option
+    @options.verify
+  end
+
+  it "is enabled with #interrupt" do
+    @options.stub!(:on)
+    @options.should_receive(:on).with("-Y", "--verify", an_instance_of(String))
+    @options.verify
+  end
+
+  it "sets the MSpec mode to :verify" do
+    MSpec.should_receive(:register_mode).with(:verify).twice
+    ["-Y", "--verify"].each do |m|
+      @options.parse m
+    end
+  end
+end
+
+describe "The -O, --report option" do
+  before :each do
+    @options, @config = new_option
+    @options.verify
+  end
+
+  it "is enabled with #interrupt" do
+    @options.stub!(:on)
+    @options.should_receive(:on).with("-O", "--report", an_instance_of(String))
+    @options.verify
+  end
+
+  it "sets the MSpec mode to :report" do
+    MSpec.should_receive(:register_mode).with(:report).twice
+    ["-O", "--report"].each do |m|
+      @options.parse m
+    end
+  end
+end
+
+describe "The --report-on GUARD option" do
+  before :all do
+    MSpec.stub!(:register_mode)
+  end
+
+  before :each do
+    @options, @config = new_option
+    @options.verify
+
+    SpecGuard.clear_guards
+  end
+
+  after :each do
+    SpecGuard.clear_guards
+  end
+
+  it "is enabled with #interrupt" do
+    @options.stub!(:on)
+    @options.should_receive(:on).with("--report-on", "GUARD", an_instance_of(String))
+    @options.verify
+  end
+
+  it "sets the MSpec mode to :report_on" do
+    MSpec.should_receive(:register_mode).with(:report_on)
+    @options.parse ["--report-on", "ruby_bug"]
+  end
+
+  it "converts the guard name to a symbol" do
+    name = mock("ruby_bug")
+    name.should_receive(:to_sym)
+    @options.parse ["--report-on", name]
+  end
+
+  it "saves the name of the guard" do
+    @options.parse ["--report-on", "ruby_bug"]
+    SpecGuard.guards.should == [:ruby_bug]
+  end
+end
+
+describe "The -K, --action-tag TAG option" do
+  before :each do
+    @options, @config = new_option
+    @options.action_filters
+  end
+
+  it "is enabled with #action_filters" do
+    @options.stub!(:on)
+    @options.should_receive(:on).with("-K", "--action-tag", "TAG",
+        an_instance_of(String))
+    @options.action_filters
+  end
+
+  it "adds TAG to the list of tags that trigger actions" do
+    ["-K", "--action-tag"].each do |opt|
+      @config[:atags] = []
+      @options.parse [opt, "action-tag"]
+      @config[:atags].should include("action-tag")
+    end
+  end
+end
+
+describe "The -S, --action-string STR option" do
+  before :each do
+    @options, @config = new_option
+    @options.action_filters
+  end
+
+  it "is enabled with #action_filters" do
+    @options.stub!(:on)
+    @options.should_receive(:on).with("-S", "--action-string", "STR",
+        an_instance_of(String))
+    @options.action_filters
+  end
+
+  it "adds STR to the list of spec descriptions that trigger actions" do
+    ["-S", "--action-string"].each do |opt|
+      @config[:astrings] = []
+      @options.parse [opt, "action-str"]
+      @config[:astrings].should include("action-str")
+    end
+  end
+end
+
+describe "The --spec-debug option" do
+  before :each do
+    @options, @config = new_option
+    @options.actions
+  end
+
+  it "is enabled with #actions" do
+    @options.stub!(:on)
+    @options.should_receive(:on).with("--spec-debug", an_instance_of(String))
+    @options.actions
+  end
+
+  it "enables the triggering the ruby debugger" do
+    @options.action_filters
+    @options.parse ["-S", "some spec"]
+
+    @config[:debugger] = nil
+    @options.parse "--spec-debug"
+    @config[:debugger].should == true
+  end
+end
+
+describe "The --spec-gdb option" do
+  before :each do
+    @options, @config = new_option
+    @options.actions
+  end
+
+  it "is enabled with #actions" do
+    @options.stub!(:on)
+    @options.should_receive(:on).with("--spec-gdb", an_instance_of(String))
+    @options.actions
+  end
+
+  it "enables triggering the gdb debugger" do
+    @options.action_filters
+    @options.parse ["-S", "some spec"]
+
+    @config[:gdb] = nil
+    @options.parse "--spec-gdb"
+    @config[:gdb].should == true
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/utils/script_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/utils/script_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/utils/script_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,443 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/utils/script'
+require 'mspec/runner/mspec'
+require 'mspec/runner/filters'
+require 'mspec/runner/actions/filter'
+require 'mspec/runner/actions/debug'
+require 'mspec/runner/actions/gdb'
+
+describe MSpecScript, ".config" do
+  it "returns a Hash" do
+    MSpecScript.config.should be_kind_of(Hash)
+  end
+end
+
+describe MSpecScript, ".set" do
+  it "sets the config hash key, value" do
+    MSpecScript.set :a, 10
+    MSpecScript.config[:a].should == 10
+  end
+end
+
+describe MSpecScript, ".get" do
+  it "gets the config hash value for a key" do
+    MSpecScript.set :a, 10
+    MSpecScript.get(:a).should == 10
+  end
+end
+
+describe MSpecScript, "#config" do
+  it "returns the MSpecScript config hash" do
+    MSpecScript.set :b, 5
+    MSpecScript.new.config[:b].should == 5
+  end
+
+  it "returns the MSpecScript config hash from subclasses" do
+    class MSSClass < MSpecScript; end
+    MSpecScript.set :b, 5
+    MSSClass.new.config[:b].should == 5
+  end
+end
+
+describe MSpecScript, "#load_default" do
+  before :all do
+    @verbose = $VERBOSE
+    $VERBOSE = nil
+  end
+
+  after :all do
+    $VERBOSE = @verbose
+  end
+
+  before :each do
+    @version = RUBY_VERSION
+    if Object.const_defined? :RUBY_ENGINE
+      @engine = Object.const_get :RUBY_ENGINE
+    end
+    @script = MSpecScript.new
+    MSpecScript.stub!(:new).and_return(@script)
+  end
+
+  after :each do
+    Object.const_set :RUBY_VERSION, @version
+    Object.const_set :RUBY_ENGINE, @engine if @engine
+  end
+
+  it "attempts to load 'default.mspec'" do
+    @script.should_receive(:load).with('default.mspec').and_return(true)
+    @script.load_default
+  end
+
+  it "attempts to load a config file based on RUBY_ENGINE and RUBY_VERSION" do
+    Object.const_set :RUBY_ENGINE, "ybur"
+    Object.const_set :RUBY_VERSION, "1.8.9"
+    default = "ybur.1.8.mspec"
+    @script.should_receive(:load).with('default.mspec').and_return(false)
+    @script.should_receive(:load).with(default)
+    @script.load_default
+  end
+end
+
+describe MSpecScript, ".main" do
+  before :each do
+    @script = mock("MSpecScript", :null_object => true)
+    MSpecScript.stub!(:new).and_return(@script)
+  end
+
+  it "creates an instance of MSpecScript" do
+    MSpecScript.should_receive(:new).and_return(@script)
+    MSpecScript.main
+  end
+
+  it "attempts to load the default config" do
+    @script.should_receive(:load_default)
+    MSpecScript.main
+  end
+
+  it "attempts to load the '~/.mspecrc' script" do
+    @script.should_receive(:load).with('~/.mspecrc')
+    MSpecScript.main
+  end
+
+  it "calls the #options method on the script" do
+    @script.should_receive(:options)
+    MSpecScript.main
+  end
+
+  it "calls the #signals method on the script" do
+    @script.should_receive(:signals)
+    MSpecScript.main
+  end
+
+  it "calls the #register method on the script" do
+    @script.should_receive(:register)
+    MSpecScript.main
+  end
+
+  it "calls the #run method on the script" do
+    @script.should_receive(:run)
+    MSpecScript.main
+  end
+end
+
+describe MSpecScript, "#initialize" do
+  before :each do
+    @config = MSpecScript.new.config
+  end
+
+  it "sets the default config values" do
+    @config[:formatter].should  == nil
+    @config[:includes].should   == []
+    @config[:excludes].should   == []
+    @config[:patterns].should   == []
+    @config[:xpatterns].should  == []
+    @config[:tags].should       == []
+    @config[:xtags].should      == []
+    @config[:atags].should      == []
+    @config[:astrings].should   == []
+    @config[:abort].should      == true
+    @config[:config_ext].should == '.mspec'
+  end
+end
+
+describe MSpecScript, "#load" do
+  before :each do
+    File.stub!(:exist?).and_return(false)
+    @script = MSpecScript.new
+    @file = "default.mspec"
+    @base = "default"
+  end
+
+  it "attempts to locate the file through the expanded path name" do
+    File.should_receive(:expand_path).with(@file).and_return(@file)
+    File.should_receive(:exist?).with(@file).and_return(true)
+    Kernel.should_receive(:load).with(@file).and_return(:loaded)
+    @script.load(@file).should == :loaded
+  end
+
+  it "appends config[:config_ext] to the name and attempts to locate the file through the expanded path name" do
+    File.should_receive(:expand_path).with(@base).and_return(@base)
+    File.should_receive(:expand_path).with(@file).and_return(@file)
+    File.should_receive(:exist?).with(@base).and_return(false)
+    File.should_receive(:exist?).with(@file).and_return(true)
+    Kernel.should_receive(:load).with(@file).and_return(:loaded)
+    @script.load(@base).should == :loaded
+  end
+
+  it "attemps to locate the file in '.'" do
+    path = File.join ".", @file
+    File.should_receive(:exist?).with(path).and_return(true)
+    Kernel.should_receive(:load).with(path).and_return(:loaded)
+    @script.load(@file).should == :loaded
+  end
+
+  it "appends config[:config_ext] to the name and attempts to locate the file in '.'" do
+    path = File.join ".", @file
+    File.should_receive(:exist?).with(path).and_return(true)
+    Kernel.should_receive(:load).with(path).and_return(:loaded)
+    @script.load(@base).should == :loaded
+  end
+
+  it "attemps to locate the file in 'spec'" do
+    path = File.join "spec", @file
+    File.should_receive(:exist?).with(path).and_return(true)
+    Kernel.should_receive(:load).with(path).and_return(:loaded)
+    @script.load(@file).should == :loaded
+  end
+
+  it "appends config[:config_ext] to the name and attempts to locate the file in 'spec'" do
+    path = File.join "spec", @file
+    File.should_receive(:exist?).with(path).and_return(true)
+    Kernel.should_receive(:load).with(path).and_return(:loaded)
+    @script.load(@base).should == :loaded
+  end
+end
+
+describe MSpecScript, "#custom_options" do
+  before :each do
+    @script = MSpecScript.new
+  end
+
+  after :each do
+  end
+
+  it "prints 'None'" do
+    options = mock("options")
+    options.should_receive(:doc).with("   No custom options registered")
+    @script.custom_options options
+  end
+end
+
+describe MSpecScript, "#register" do
+  before :each do
+    @script = MSpecScript.new
+
+    @formatter = mock("formatter", :null_object => true)
+    @script.config[:formatter] = @formatter
+  end
+
+  it "creates and registers the formatter" do
+    @formatter.should_receive(:new).and_return(@formatter)
+    @formatter.should_receive(:register)
+    @script.register
+  end
+
+  it "does not register the formatter if config[:formatter] is false" do
+    @script.config[:formatter] = false
+    @script.register
+  end
+
+  it "calls #custom_register" do
+    @script.should_receive(:custom_register)
+    @script.register
+  end
+
+  it "registers :formatter with the formatter instance" do
+    @formatter.stub!(:new).and_return(@formatter)
+    MSpec.should_receive(:store).with(:formatter, @formatter)
+    @script.register
+  end
+
+  it "does not register :formatter if config[:formatter] is false" do
+    @script.config[:formatter] = false
+    MSpec.should_not_receive(:store)
+    @script.register
+  end
+end
+
+describe MSpecScript, "#register" do
+  before :each do
+    @script = MSpecScript.new
+
+    @formatter = mock("formatter", :null_object => true)
+    @script.config[:formatter] = @formatter
+
+    @filter = mock("filter")
+    @filter.should_receive(:register)
+
+    @ary = ["some", "spec"]
+  end
+
+  it "creates and registers a MatchFilter for include specs" do
+    MatchFilter.should_receive(:new).with(:include, *@ary).and_return(@filter)
+    @script.config[:includes] = @ary
+    @script.register
+  end
+
+  it "creates and registers a MatchFilter for excluded specs" do
+    MatchFilter.should_receive(:new).with(:exclude, *@ary).and_return(@filter)
+    @script.config[:excludes] = @ary
+    @script.register
+  end
+
+  it "creates and registers a RegexpFilter for include specs" do
+    RegexpFilter.should_receive(:new).with(:include, *@ary).and_return(@filter)
+    @script.config[:patterns] = @ary
+    @script.register
+  end
+
+  it "creates and registers a RegexpFilter for excluded specs" do
+    RegexpFilter.should_receive(:new).with(:exclude, *@ary).and_return(@filter)
+    @script.config[:xpatterns] = @ary
+    @script.register
+  end
+
+  it "creates and registers a TagFilter for include specs" do
+    TagFilter.should_receive(:new).with(:include, *@ary).and_return(@filter)
+    @script.config[:tags] = @ary
+    @script.register
+  end
+
+  it "creates and registers a TagFilter for excluded specs" do
+    TagFilter.should_receive(:new).with(:exclude, *@ary).and_return(@filter)
+    @script.config[:xtags] = @ary
+    @script.register
+  end
+
+  it "creates and registers a ProfileFilter for include specs" do
+    ProfileFilter.should_receive(:new).with(:include, *@ary).and_return(@filter)
+    @script.config[:profiles] = @ary
+    @script.register
+  end
+
+  it "creates and registers a ProfileFilter for excluded specs" do
+    ProfileFilter.should_receive(:new).with(:exclude, *@ary).and_return(@filter)
+    @script.config[:xprofiles] = @ary
+    @script.register
+  end
+
+  it "creates and registers a DebugAction for excluded specs" do
+    @script.config[:atags] = ["some"]
+    @script.config[:astrings] = ["string"]
+    DebugAction.should_receive(:new).with(["some"], ["string"]).and_return(@filter)
+    @script.config[:debugger] = true
+    @script.register
+  end
+
+  it "creates and registers a GdbAction for excluded specs" do
+    @script.config[:atags] = ["some"]
+    @script.config[:astrings] = ["string"]
+    GdbAction.should_receive(:new).with(["some"], ["string"]).and_return(@filter)
+    @script.config[:gdb] = true
+    @script.register
+  end
+end
+
+describe MSpecScript, "#signals" do
+  before :each do
+    @script = MSpecScript.new
+    @abort = @script.config[:abort]
+  end
+
+  after :each do
+    @script.config[:abort] = @abort
+  end
+
+  it "traps the INT signal if config[:abort] is true" do
+    Signal.should_receive(:trap).with("INT")
+    @script.config[:abort] = true
+    @script.signals
+  end
+
+  it "does not trap the INT signal if config[:abort] is not true" do
+    Signal.should_not_receive(:trap).with("INT")
+    @script.config[:abort] = false
+    @script.signals
+  end
+end
+
+describe MSpecScript, "#entries" do
+  before :each do
+    @script = MSpecScript.new
+
+    File.stub!(:expand_path).and_return("name")
+    File.stub!(:file?).and_return(false)
+    File.stub!(:directory?).and_return(false)
+  end
+
+  it "returns the pattern in an array if it is a file" do
+    File.should_receive(:expand_path).with("file").and_return("file")
+    File.should_receive(:file?).with("file").and_return(true)
+    @script.entries("file").should == ["file"]
+  end
+
+  it "returns Dir['pattern/**/*_spec.rb'] if pattern is a directory" do
+    File.should_receive(:directory?).with("name").and_return(true)
+    File.stub!(:expand_path).and_return("name","name/**/*_spec.rb")
+    Dir.should_receive(:[]).with("name/**/*_spec.rb").and_return(["dir1", "dir2"])
+    @script.entries("name").should == ["dir1", "dir2"]
+  end
+
+  it "returns Dir[pattern] if pattern is neither a file nor a directory" do
+    Dir.should_receive(:[]).with("pattern").and_return(["file1", "file2"])
+    @script.entries("pattern").should == ["file1", "file2"]
+  end
+
+  describe "with config[:prefix] set" do
+    before :each do
+      prefix = "prefix/dir"
+      @script.config[:prefix] = prefix
+      @name = prefix + "/name"
+    end
+
+    it "returns the pattern in an array if it is a file" do
+      File.should_receive(:expand_path).with(@name).and_return(@name)
+      File.should_receive(:file?).with(@name).and_return(true)
+      @script.entries("name").should == [@name]
+    end
+
+    it "returns Dir['pattern/**/*_spec.rb'] if pattern is a directory" do
+      File.stub!(:expand_path).and_return(@name, @name+"/**/*_spec.rb")
+      File.should_receive(:directory?).with(@name).and_return(true)
+      Dir.should_receive(:[]).with(@name + "/**/*_spec.rb").and_return(["dir1", "dir2"])
+      @script.entries("name").should == ["dir1", "dir2"]
+    end
+
+    it "returns Dir[pattern] if pattern is neither a file nor a directory" do
+      Dir.should_receive(:[]).with("pattern").and_return(["file1", "file2"])
+      @script.entries("pattern").should == ["file1", "file2"]
+    end
+  end
+end
+
+describe MSpecScript, "#files" do
+  before :each do
+    @script = MSpecScript.new
+  end
+
+  it "accumlates the values returned by #entries" do
+    @script.should_receive(:entries).and_return(["file1"], ["file2"])
+    @script.files(["a", "b"]).should == ["file1", "file2"]
+  end
+
+  it "strips a leading '^' and removes the values returned by #entries" do
+    @script.should_receive(:entries).and_return(["file1"], ["file2"], ["file1"])
+    @script.files(["a", "b", "^a"]).should == ["file2"]
+  end
+
+  it "processes the array elements in order" do
+    @script.should_receive(:entries).and_return(["file1"], ["file1"], ["file2"])
+    @script.files(["^a", "a", "b"]).should == ["file1", "file2"]
+  end
+end
+
+describe MSpecScript, "#files" do
+  before :each do
+    MSpecScript.set :files, ["file1", "file2"]
+
+    @script = MSpecScript.new
+  end
+
+  after :each do
+    MSpecScript.config.delete :files
+  end
+
+  it "looks up items with leading ':' in the config object" do
+    @script.should_receive(:entries).and_return(["file1"], ["file2"])
+    @script.files(":files").should == ["file1", "file2"]
+  end
+
+  it "returns an empty list if the config key is not set" do
+    @script.files(":all_files").should == []
+  end
+end

Added: MacRuby/branches/experimental/mspec/spec/utils/version_spec.rb
===================================================================
--- MacRuby/branches/experimental/mspec/spec/utils/version_spec.rb	                        (rev 0)
+++ MacRuby/branches/experimental/mspec/spec/utils/version_spec.rb	2009-03-11 23:10:17 UTC (rev 876)
@@ -0,0 +1,47 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/utils/version'
+
+describe SpecVersion, "#to_s" do
+  it "returns the string with which it was initialized" do
+    SpecVersion.new("1.8").to_s.should == "1.8"
+    SpecVersion.new("2.118.9.2").to_s.should == "2.118.9.2"
+  end
+end
+
+describe SpecVersion, "#to_str" do
+  it "returns the same string as #to_s" do
+    version = SpecVersion.new("2.118.9.2")
+    version.to_str.should == version.to_s
+  end
+end
+
+describe SpecVersion, "#to_i with ceil = false" do
+  it "returns an integer representation of the version string" do
+    SpecVersion.new("1.8.6.22").to_i.should == 10108060022
+  end
+
+  it "replaces missing version parts with zeros" do
+    SpecVersion.new("1.8").to_i.should == 10108000000
+    SpecVersion.new("1.8.6").to_i.should == 10108060000
+    SpecVersion.new("1.8.7.333").to_i.should == 10108070333
+  end
+end
+
+describe SpecVersion, "#to_i with ceil = true" do
+  it "returns an integer representation of the version string" do
+    SpecVersion.new("1.8.6.22", true).to_i.should == 10108060022
+  end
+
+  it "fills in 9s for missing tiny and patchlevel values" do
+    SpecVersion.new("1.8", true).to_i.should == 10108999999
+    SpecVersion.new("1.8.6", true).to_i.should == 10108069999
+    SpecVersion.new("1.8.7.333", true).to_i.should == 10108070333
+  end
+end
+
+describe SpecVersion, "#to_int" do
+  it "returns the same value as #to_i" do
+    version = SpecVersion.new("4.16.87.333")
+    version.to_int.should == version.to_i
+  end
+end
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/macruby-changes/attachments/20090311/90519fec/attachment-0001.html>


More information about the macruby-changes mailing list