[macruby-changes] [3870] MacRuby/trunk/mspec

source_changes at macosforge.org source_changes at macosforge.org
Sun Mar 28 10:00:15 PDT 2010


Revision: 3870
          http://trac.macosforge.org/projects/ruby/changeset/3870
Author:   eloy.de.enige at gmail.com
Date:     2010-03-28 10:00:14 -0700 (Sun, 28 Mar 2010)
Log Message:
-----------
Update MSpec to 88d85da83cbf2e595ec956651520ce119d429e8f

Modified Paths:
--------------
    MacRuby/trunk/mspec/Rakefile
    MacRuby/trunk/mspec/lib/mspec/commands/mspec-ci.rb
    MacRuby/trunk/mspec/lib/mspec/guards/conflict.rb
    MacRuby/trunk/mspec/lib/mspec/guards/guard.rb
    MacRuby/trunk/mspec/lib/mspec/guards/platform.rb
    MacRuby/trunk/mspec/lib/mspec/guards.rb
    MacRuby/trunk/mspec/lib/mspec/helpers/environment.rb
    MacRuby/trunk/mspec/lib/mspec/helpers/fixture.rb
    MacRuby/trunk/mspec/lib/mspec/helpers/ruby_exe.rb
    MacRuby/trunk/mspec/lib/mspec/helpers.rb
    MacRuby/trunk/mspec/lib/mspec/matchers/equal_utf16.rb
    MacRuby/trunk/mspec/lib/mspec/matchers.rb
    MacRuby/trunk/mspec/lib/mspec/mocks/mock.rb
    MacRuby/trunk/mspec/lib/mspec/runner/context.rb
    MacRuby/trunk/mspec/lib/mspec/runner/mspec.rb
    MacRuby/trunk/mspec/lib/mspec/version.rb
    MacRuby/trunk/mspec/spec/commands/mspec_ci_spec.rb
    MacRuby/trunk/mspec/spec/guards/conflict_spec.rb
    MacRuby/trunk/mspec/spec/guards/guard_spec.rb
    MacRuby/trunk/mspec/spec/helpers/environment_spec.rb
    MacRuby/trunk/mspec/spec/helpers/fixture_spec.rb
    MacRuby/trunk/mspec/spec/helpers/ruby_exe_spec.rb
    MacRuby/trunk/mspec/spec/matchers/equal_utf16_spec.rb
    MacRuby/trunk/mspec/spec/mocks/mock_spec.rb
    MacRuby/trunk/mspec/spec/runner/context_spec.rb
    MacRuby/trunk/mspec/spec/runner/example_spec.rb
    MacRuby/trunk/mspec/spec/runner/formatters/dotted_spec.rb
    MacRuby/trunk/mspec/spec/runner/formatters/file_spec.rb
    MacRuby/trunk/mspec/spec/runner/formatters/html_spec.rb
    MacRuby/trunk/mspec/spec/runner/formatters/method_spec.rb
    MacRuby/trunk/mspec/spec/runner/formatters/spinner_spec.rb
    MacRuby/trunk/mspec/spec/runner/formatters/unit_spec.rb
    MacRuby/trunk/mspec/spec/runner/shared_spec.rb
    MacRuby/trunk/mspec/upstream

Added Paths:
-----------
    MacRuby/trunk/mspec/lib/mspec/guards/feature.rb
    MacRuby/trunk/mspec/lib/mspec/guards/specified.rb
    MacRuby/trunk/mspec/lib/mspec/helpers/encode.rb
    MacRuby/trunk/mspec/lib/mspec/helpers/fmode.rb
    MacRuby/trunk/mspec/lib/mspec/matchers/have_data.rb
    MacRuby/trunk/mspec/spec/guards/feature_spec.rb
    MacRuby/trunk/mspec/spec/guards/specified_spec.rb
    MacRuby/trunk/mspec/spec/helpers/encode_spec.rb
    MacRuby/trunk/mspec/spec/helpers/fmode_spec.rb
    MacRuby/trunk/mspec/spec/matchers/have_data_spec.rb

Modified: MacRuby/trunk/mspec/Rakefile
===================================================================
--- MacRuby/trunk/mspec/Rakefile	2010-03-28 04:26:06 UTC (rev 3869)
+++ MacRuby/trunk/mspec/Rakefile	2010-03-28 17:00:14 UTC (rev 3870)
@@ -18,7 +18,7 @@
 
   s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
   s.authors                   = ["Brian Ford"]
-  s.date                      = %q{2010-01-05}
+  s.date                      = %q{2010-02-08}
   s.email                     = %q{bford at engineyard.com}
   s.has_rdoc                  = true
   s.extra_rdoc_files          = %w[ README LICENSE ]

Modified: MacRuby/trunk/mspec/lib/mspec/commands/mspec-ci.rb
===================================================================
--- MacRuby/trunk/mspec/lib/mspec/commands/mspec-ci.rb	2010-03-28 04:26:06 UTC (rev 3869)
+++ MacRuby/trunk/mspec/lib/mspec/commands/mspec-ci.rb	2010-03-28 17:00:14 UTC (rev 3870)
@@ -66,8 +66,11 @@
   def run
     MSpec.register_tags_patterns config[:tags_patterns]
     MSpec.register_files @files
-    filter = TagFilter.new(:exclude,
-        "fails", "critical", "unstable", "incomplete", "unsupported")
+
+    tags = ["fails", "critical", "unstable", "incomplete", "unsupported"]
+    tags += Array(config[:ci_xtags])
+
+    filter = TagFilter.new(:exclude, *tags)
     filter.register
 
     MSpec.process

Modified: MacRuby/trunk/mspec/lib/mspec/guards/conflict.rb
===================================================================
--- MacRuby/trunk/mspec/lib/mspec/guards/conflict.rb	2010-03-28 04:26:06 UTC (rev 3869)
+++ MacRuby/trunk/mspec/lib/mspec/guards/conflict.rb	2010-03-28 17:00:14 UTC (rev 3870)
@@ -2,12 +2,17 @@
 
 class ConflictsGuard < SpecGuard
   def match?
-    constants = Object.constants
-    @args.any? { |mod| constants.include? mod.to_s }
+    # Always convert constants to symbols regardless of version.
+    constants = Object.constants.map { |x| x.to_sym }
+    @args.any? { |mod| constants.include? mod }
   end
 end
 
 class Object
+  # In some cases, libraries will modify another Ruby method's
+  # behavior. The specs for the method's behavior will then fail
+  # if that library is loaded. This guard will not run if any of
+  # the specified constants exist in Object.constants.
   def conflicts_with(*modules)
     g = ConflictsGuard.new(*modules)
     g.name = :conflicts_with

Added: MacRuby/trunk/mspec/lib/mspec/guards/feature.rb
===================================================================
--- MacRuby/trunk/mspec/lib/mspec/guards/feature.rb	                        (rev 0)
+++ MacRuby/trunk/mspec/lib/mspec/guards/feature.rb	2010-03-28 17:00:14 UTC (rev 3870)
@@ -0,0 +1,47 @@
+require 'mspec/guards/guard'
+
+class FeatureGuard < SpecGuard
+  def self.enabled?(*features)
+    new(*features).match?
+  end
+
+  def match?
+    @parameters.all? { |f| MSpec.feature_enabled? f }
+  end
+end
+
+class Object
+  # Provides better documentation in the specs by
+  # naming sets of features that work together as
+  # a whole. Examples include :encoding, :fiber,
+  # :continuation, :fork.
+  #
+  # Usage example:
+  #
+  #   with_feature :encoding do
+  #     # specs for a method that provides aspects
+  #     # of the encoding feature
+  #   end
+  #
+  # Multiple features must all be enabled for the
+  # guard to run:
+  #
+  #   with_feature :one, :two do
+  #     # these specs will run if features :one AND
+  #     # :two are enabled.
+  #   end
+  #
+  # The implementation must explicitly enable a feature
+  # by adding code like the following to the .mspec
+  # configuration file:
+  #
+  #   MSpec.enable_feature :encoding
+  #
+  def with_feature(*features)
+    g = FeatureGuard.new(*features)
+    g.name = :with_feature
+    yield if g.yield?
+  ensure
+    g.unregister
+  end
+end

Modified: MacRuby/trunk/mspec/lib/mspec/guards/guard.rb
===================================================================
--- MacRuby/trunk/mspec/lib/mspec/guards/guard.rb	2010-03-28 04:26:06 UTC (rev 3869)
+++ MacRuby/trunk/mspec/lib/mspec/guards/guard.rb	2010-03-28 17:00:14 UTC (rev 3870)
@@ -1,6 +1,8 @@
 require 'mspec/runner/mspec'
 require 'mspec/runner/actions/tally'
 
+require 'rbconfig'
+
 class SpecGuard
   def self.report
     @report ||= Hash.new { |h,k| h[k] = [] }
@@ -56,10 +58,6 @@
     version.split('.')[0,n].join('.')
   end
 
-  def self.windows?(key = RUBY_PLATFORM)
-    !!key.match(/(mswin|mingw)/)
-  end
-
   attr_accessor :name, :parameters
 
   def initialize(*args)
@@ -136,7 +134,7 @@
   end
 
   def windows?(sym, key)
-    sym == :windows && SpecGuard.windows?(key)
+    sym == :windows && !key.match(/(mswin|mingw)/).nil?
   end
 
   def platform?(*args)
@@ -154,7 +152,6 @@
   end
 
   def os?(*oses)
-    require 'rbconfig'
     oses.any? do |os|
       host_os = Config::CONFIG['host_os'] || RUBY_PLATFORM
       host_os.downcase!

Modified: MacRuby/trunk/mspec/lib/mspec/guards/platform.rb
===================================================================
--- MacRuby/trunk/mspec/lib/mspec/guards/platform.rb	2010-03-28 04:26:06 UTC (rev 3869)
+++ MacRuby/trunk/mspec/lib/mspec/guards/platform.rb	2010-03-28 17:00:14 UTC (rev 3870)
@@ -1,6 +1,10 @@
 require 'mspec/guards/guard'
 
 class PlatformGuard < SpecGuard
+  def self.windows?
+    PlatformGuard.new(:os => :windows).match?
+  end
+
   def initialize(*args)
     if args.last.is_a?(Hash)
       @options, @platforms = args.last, args[0..-2]

Added: MacRuby/trunk/mspec/lib/mspec/guards/specified.rb
===================================================================
--- MacRuby/trunk/mspec/lib/mspec/guards/specified.rb	                        (rev 0)
+++ MacRuby/trunk/mspec/lib/mspec/guards/specified.rb	2010-03-28 17:00:14 UTC (rev 3870)
@@ -0,0 +1,66 @@
+require 'mspec/guards/guard'
+
+class UnspecifiedGuard < SpecGuard
+  def match?
+    not standard?
+  end
+end
+
+class SpecifiedOnGuard < SpecGuard
+  def match?
+    if @args.include? :ruby
+      raise Exception, "improper use of specified_on guard"
+    end
+    not standard? and implementation?(*@args)
+  end
+end
+
+class Object
+  # This guard wraps one or more #specified_on guards to group them and
+  # document the specs. The purpose of the guard is for situations where MRI
+  # either does not specify Ruby behavior or where MRI's behavior is all but
+  # impossible to spec, for example due to relying on platform-specific
+  # behavior that is not easily testable from Ruby code. In such cases, it
+  # may be desirable for implementations to explore a specified set of
+  # behaviors that are explicitly documented in the specs.
+  #
+  #   unspecified do
+  #     specified_on :rubinius, :ironruby do
+  #       it "returns true when passed :foo" do
+  #         # ...
+  #       end
+  #
+  #       it "returns false when passed :bar" do
+  #         # ...
+  #       end
+  #     end
+  #
+  #     specified_on :jruby do
+  #       it "returns true when passed :bar" do
+  #         # ...
+  #       end
+  #     end
+  #   end
+  #
+  # Note that these guards do not change the policy of the #compliant_on,
+  # #not_compliant_on, #deviates_on, #extended_on, and #not_supported_on
+  # guards.
+  #
+  def unspecified
+    g = UnspecifiedGuard.new
+    g.name = :unspecified
+    yield if g.yield?
+  ensure
+    g.unregister
+  end
+
+  # This guard wraps specs for one or more particular implementations. See the
+  # #unspecified guard for further documentation.
+  def specified_on(*args)
+    g = SpecifiedOnGuard.new(*args)
+    g.name = :specified_on
+    yield if g.yield?
+  ensure
+    g.unregister
+  end
+end

Modified: MacRuby/trunk/mspec/lib/mspec/guards.rb
===================================================================
--- MacRuby/trunk/mspec/lib/mspec/guards.rb	2010-03-28 04:26:06 UTC (rev 3869)
+++ MacRuby/trunk/mspec/lib/mspec/guards.rb	2010-03-28 17:00:14 UTC (rev 3870)
@@ -5,11 +5,13 @@
 require 'mspec/guards/conflict'
 require 'mspec/guards/endian'
 require 'mspec/guards/extensions'
+require 'mspec/guards/feature'
 require 'mspec/guards/guard'
 require 'mspec/guards/noncompliance'
 require 'mspec/guards/platform'
 require 'mspec/guards/quarantine'
 require 'mspec/guards/runner'
+require 'mspec/guards/specified'
 require 'mspec/guards/support'
 require 'mspec/guards/superuser'
 require 'mspec/guards/tty'

Added: MacRuby/trunk/mspec/lib/mspec/helpers/encode.rb
===================================================================
--- MacRuby/trunk/mspec/lib/mspec/helpers/encode.rb	                        (rev 0)
+++ MacRuby/trunk/mspec/lib/mspec/helpers/encode.rb	2010-03-28 17:00:14 UTC (rev 3870)
@@ -0,0 +1,21 @@
+require 'mspec/guards/feature'
+
+class Object
+  # Helper to handle String encodings. The +str+ and +encoding+ parameters
+  # must be Strings and an ArgumentError will be raised if not. This ensures
+  # that the encode() helper can be used regardless of whether Encoding exits.
+  # The helper is a no-op (i.e. passes through +str+ unmodified) if the
+  # :encoding feature is not enabled (see with_feature guard).  If the
+  # :encoding feature is enabled, +str+.force_encoding(+encoding+) is called.
+  def encode(str, encoding)
+    unless str.is_a? String and encoding.is_a? String
+      raise ArgumentError, "encoding name must be a String"
+    end
+
+    if FeatureGuard.enabled? :encoding
+      str.force_encoding encoding
+    end
+
+    str
+  end
+end

Modified: MacRuby/trunk/mspec/lib/mspec/helpers/environment.rb
===================================================================
--- MacRuby/trunk/mspec/lib/mspec/helpers/environment.rb	2010-03-28 04:26:06 UTC (rev 3869)
+++ MacRuby/trunk/mspec/lib/mspec/helpers/environment.rb	2010-03-28 17:00:14 UTC (rev 3870)
@@ -3,7 +3,7 @@
 class Object
   def env
     env = ""
-    if SpecGuard.windows?
+    if PlatformGuard.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]
@@ -17,7 +17,7 @@
 
   def username
     user = ""
-    if SpecGuard.windows?
+    if PlatformGuard.windows?
       user = windows_env_echo('USERNAME')
     else
       user = `whoami`.strip
@@ -26,7 +26,7 @@
   end
 
   def home_directory
-    return ENV['HOME'] unless SpecGuard.windows?
+    return ENV['HOME'] unless PlatformGuard.windows?
     windows_env_echo('HOMEDRIVE') + windows_env_echo('HOMEPATH')
   end  
 end

Modified: MacRuby/trunk/mspec/lib/mspec/helpers/fixture.rb
===================================================================
--- MacRuby/trunk/mspec/lib/mspec/helpers/fixture.rb	2010-03-28 04:26:06 UTC (rev 3869)
+++ MacRuby/trunk/mspec/lib/mspec/helpers/fixture.rb	2010-03-28 17:00:14 UTC (rev 3870)
@@ -15,6 +15,7 @@
   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))
+    dir = path[-9..-1] == "/fixtures" ? "" : "fixtures"
+    File.expand_path(File.join(path, dir, args))
   end
 end

Added: MacRuby/trunk/mspec/lib/mspec/helpers/fmode.rb
===================================================================
--- MacRuby/trunk/mspec/lib/mspec/helpers/fmode.rb	                        (rev 0)
+++ MacRuby/trunk/mspec/lib/mspec/helpers/fmode.rb	2010-03-28 17:00:14 UTC (rev 3870)
@@ -0,0 +1,15 @@
+require 'mspec/guards/feature'
+
+class Object
+  # This helper simplifies passing file access modes regardless of
+  # whether the :encoding feature is enabled. Only the access specifier
+  # itself will be returned if :encoding is not enabled. Otherwise,
+  # the full mode string will be returned (i.e. the helper is a no-op).
+  def fmode(mode)
+    if FeatureGuard.enabled? :encoding
+      mode
+    else
+      mode.split(':').first
+    end
+  end
+end

Modified: MacRuby/trunk/mspec/lib/mspec/helpers/ruby_exe.rb
===================================================================
--- MacRuby/trunk/mspec/lib/mspec/helpers/ruby_exe.rb	2010-03-28 04:26:06 UTC (rev 3869)
+++ MacRuby/trunk/mspec/lib/mspec/helpers/ruby_exe.rb	2010-03-28 17:00:14 UTC (rev 3870)
@@ -1,5 +1,5 @@
 require 'mspec/utils/ruby_name'
-require 'mspec/guards/guard'
+require 'mspec/guards/platform'
 
 # The ruby_exe helper provides a wrapper for invoking the
 # same Ruby interpreter as the one running the specs and
@@ -99,7 +99,7 @@
       # 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))
+      if File.exists?(exe) and (PlatformGuard.windows? or File.executable?(exe))
         return cmd
       end
     end

Modified: MacRuby/trunk/mspec/lib/mspec/helpers.rb
===================================================================
--- MacRuby/trunk/mspec/lib/mspec/helpers.rb	2010-03-28 04:26:06 UTC (rev 3869)
+++ MacRuby/trunk/mspec/lib/mspec/helpers.rb	2010-03-28 17:00:14 UTC (rev 3870)
@@ -2,10 +2,12 @@
 require 'mspec/helpers/bignum'
 require 'mspec/helpers/const_lookup'
 require 'mspec/helpers/ducktype'
+require 'mspec/helpers/encode'
 require 'mspec/helpers/enumerator_class'
 require 'mspec/helpers/environment'
 require 'mspec/helpers/fixture'
 require 'mspec/helpers/flunk'
+require 'mspec/helpers/fmode'
 require 'mspec/helpers/fs'
 require 'mspec/helpers/hash'
 require 'mspec/helpers/infinity'

Modified: MacRuby/trunk/mspec/lib/mspec/matchers/equal_utf16.rb
===================================================================
--- MacRuby/trunk/mspec/lib/mspec/matchers/equal_utf16.rb	2010-03-28 04:26:06 UTC (rev 3869)
+++ MacRuby/trunk/mspec/lib/mspec/matchers/equal_utf16.rb	2010-03-28 17:00:14 UTC (rev 3870)
@@ -1,19 +1,17 @@
+require 'mspec/helpers/encode'
+
 class EqualUtf16Matcher
   def initialize(expected)
-    @expected = expected
+    @expected = Array(expected).map { |x| encode x, "binary" }
   end
 
   def matches?(actual)
-    @actual = actual
+    @actual = Array(actual).map { |x| encode x, "binary" }
     @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
+    @expected_swapped ||= @expected.map { |x| x.to_str.gsub(/(.)(.)/, '\2\1') }
   end
 
   def failure_message

Added: MacRuby/trunk/mspec/lib/mspec/matchers/have_data.rb
===================================================================
--- MacRuby/trunk/mspec/lib/mspec/matchers/have_data.rb	                        (rev 0)
+++ MacRuby/trunk/mspec/lib/mspec/matchers/have_data.rb	2010-03-28 17:00:14 UTC (rev 3870)
@@ -0,0 +1,48 @@
+require 'mspec/guards/feature'
+require 'mspec/helpers/fmode'
+
+class HaveDataMatcher
+  def initialize(data)
+    @data = data
+  end
+
+  def matches?(name)
+    @name = name
+
+    if FeatureGuard.enabled? :encoding
+      size = @data.bytesize
+    else
+      size = @data.size
+    end
+
+    File.open @name, fmode("rb:binary") do |f|
+      return f.read(size) == @data
+    end
+  end
+
+  def failure_message
+    ["Expected #{@name}",
+     "to have data #{@data.pretty_inspect}"]
+  end
+
+  def negative_failure_message
+    ["Expected #{@name}",
+     "not to have data #{@data.pretty_inspect}"]
+  end
+end
+
+class Object
+  # Opens a file specified by the string the matcher is called on
+  # and compares the +data+ passed to the matcher with the contents
+  # of the file. Expects to match the first N bytes of the file
+  # with +data+. For example, suppose @name is the name of a file:
+  #
+  #   @name.should have_data("123")
+  #
+  # passes if the file @name has "123" as the first 3 bytes. The
+  # file can contain more bytes than +data+. The extra bytes do not
+  # affect the result.
+  def have_data(data)
+    HaveDataMatcher.new(data)
+  end
+end

Modified: MacRuby/trunk/mspec/lib/mspec/matchers.rb
===================================================================
--- MacRuby/trunk/mspec/lib/mspec/matchers.rb	2010-03-28 04:26:06 UTC (rev 3869)
+++ MacRuby/trunk/mspec/lib/mspec/matchers.rb	2010-03-28 17:00:14 UTC (rev 3870)
@@ -14,6 +14,7 @@
 require 'mspec/matchers/equal_utf16'
 require 'mspec/matchers/have_constant'
 require 'mspec/matchers/have_class_variable'
+require 'mspec/matchers/have_data'
 require 'mspec/matchers/have_instance_method'
 require 'mspec/matchers/have_instance_variable'
 require 'mspec/matchers/have_method'

Modified: MacRuby/trunk/mspec/lib/mspec/mocks/mock.rb
===================================================================
--- MacRuby/trunk/mspec/lib/mspec/mocks/mock.rb	2010-03-28 04:26:06 UTC (rev 3869)
+++ MacRuby/trunk/mspec/lib/mspec/mocks/mock.rb	2010-03-28 17:00:14 UTC (rev 3870)
@@ -38,6 +38,11 @@
     has_key?(mocks.keys, sym) or has_key?(stubs.keys, sym)
   end
 
+  def self.clear_replaced(key)
+    mocks.delete key
+    stubs.delete key
+  end
+
   def self.mock_respond_to?(obj, sym)
     name = replaced_name(obj, :respond_to?)
     if replaced? name
@@ -176,7 +181,10 @@
       else
         meta.__send__ :remove_method, sym
       end
+
+      clear_replaced key
     end
+  ensure
     reset
   end
 end

Modified: MacRuby/trunk/mspec/lib/mspec/runner/context.rb
===================================================================
--- MacRuby/trunk/mspec/lib/mspec/runner/context.rb	2010-03-28 04:26:06 UTC (rev 3869)
+++ MacRuby/trunk/mspec/lib/mspec/runner/context.rb	2010-03-28 17:00:14 UTC (rev 3870)
@@ -37,6 +37,12 @@
     @expectation_missing = Proc.new { raise SpecExpectationNotFoundError }
   end
 
+  # Remove caching when a ContextState is dup'd for shared specs.
+  def initialize_copy(other)
+    @pre  = {}
+    @post = {}
+  end
+
   # Returns true if this is a shared +ContextState+. Essentially, when
   # created with: describe "Something", :shared => true { ... }
   def shared?
@@ -47,28 +53,51 @@
   # 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
+    if shared?
+      @parent = nil
+    else
+      @parent = parent
+      parent.child self if parent
+
+      @parents = [self]
+      state = parent
+      while state
+        @parents.unshift state
+        state = state.parent
+      end
     end
   end
 
-  def replace_parent(parent)
-    @parents[0] = parent
-
-    children.each { |child| child.replace_parent parent }
-  end
-
   # Add the ContextState instance +child+ to the list of nested
   # describe blocks.
   def child(child)
     @children << child
   end
 
+  # Adds a nested ContextState in a shared ContextState to a containing
+  # ContextState.
+  #
+  # Normal adoption is from the parent's perspective. But adopt is a good
+  # verb and it's reasonable for the child to adopt the parent as well. In
+  # this case, manipulating state from inside the child avoids needlessly
+  # exposing the state to manipulate it externally in the dup. (See
+  # #it_should_behave_like)
+  def adopt(parent)
+    self.parent = parent
+
+    @examples = @examples.map do |example|
+      example = example.dup
+      example.context = self
+      example
+    end
+
+    children = @children
+    @children = []
+
+    children.each { |child| child.dup.adopt self }
+  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)) }
@@ -113,7 +142,7 @@
 
   # Returns a description string generated from self and all parents
   def description
-    @description ||= parents.map { |p| p.to_s }.join(" ")
+    @description ||= parents.map { |p| p.to_s }.compact.join(" ")
   end
 
   # Injects the before/after blocks and examples from the shared
@@ -125,18 +154,19 @@
       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 }
 
-    # There is a potential race here if mspec ever implements concurrency
-    # in process. Right now, the only way to run specs concurrently is
-    # with multiple processes, so we ignore this for the sake of simplicity.
+    state.examples.each do |example|
+      example = example.dup
+      example.context = self
+      @examples << example
+    end
+
     state.children.each do |child|
-      child.replace_parent self
-      @children << child
+      child.dup.adopt self
     end
   end
 

Modified: MacRuby/trunk/mspec/lib/mspec/runner/mspec.rb
===================================================================
--- MacRuby/trunk/mspec/lib/mspec/runner/mspec.rb	2010-03-28 04:26:06 UTC (rev 3869)
+++ MacRuby/trunk/mspec/lib/mspec/runner/mspec.rb	2010-03-28 17:00:14 UTC (rev 3870)
@@ -21,6 +21,7 @@
   @modes   = []
   @shared  = {}
   @guarded = []
+  @features     = {}
   @exception    = nil
   @randomize    = nil
   @expectation  = nil
@@ -162,6 +163,18 @@
     retrieve(:modes).include? mode
   end
 
+  def self.enable_feature(feature)
+    retrieve(:features)[feature] = true
+  end
+
+  def self.disable_feature(feature)
+    retrieve(:features)[feature] = false
+  end
+
+  def self.feature_enabled?(feature)
+    retrieve(:features)[feature] || false
+  end
+
   def self.retrieve(symbol)
     instance_variable_get :"@#{symbol}"
   end

Modified: MacRuby/trunk/mspec/lib/mspec/version.rb
===================================================================
--- MacRuby/trunk/mspec/lib/mspec/version.rb	2010-03-28 04:26:06 UTC (rev 3869)
+++ MacRuby/trunk/mspec/lib/mspec/version.rb	2010-03-28 17:00:14 UTC (rev 3870)
@@ -1,5 +1,5 @@
 require 'mspec/utils/version'
 
 module MSpec
-  VERSION = SpecVersion.new "1.5.14"
+  VERSION = SpecVersion.new "1.5.16"
 end

Modified: MacRuby/trunk/mspec/spec/commands/mspec_ci_spec.rb
===================================================================
--- MacRuby/trunk/mspec/spec/commands/mspec_ci_spec.rb	2010-03-28 04:26:06 UTC (rev 3869)
+++ MacRuby/trunk/mspec/spec/commands/mspec_ci_spec.rb	2010-03-28 17:00:14 UTC (rev 3870)
@@ -107,6 +107,8 @@
     TagFilter.stub!(:new).and_return(@filter)
     @filter.stub!(:register)
 
+    @tags = ["fails", "critical", "unstable", "incomplete", "unsupported"]
+
     @config = { :ci_files => ["one", "two"] }
     @script = MSpecCI.new
     @script.stub!(:exit)
@@ -128,8 +130,24 @@
 
   it "registers a tag filter for 'fails', 'unstable', 'incomplete', 'critical', 'unsupported'" do
     filter = mock("fails filter")
+    TagFilter.should_receive(:new).with(:exclude, *@tags).and_return(filter)
+    filter.should_receive(:register)
+    @script.run
+  end
+
+  it "registers an additional exclude tag specified by :ci_xtags" do
+    @config[:ci_xtags] = "windows"
+    filter = mock("fails filter")
+    TagFilter.should_receive(:new).with(:exclude, *(@tags + ["windows"])).and_return(filter)
+    filter.should_receive(:register)
+    @script.run
+  end
+
+  it "registers additional exclude tags specified by a :ci_xtags array" do
+    @config[:ci_xtags] = ["windows", "windoze"]
+    filter = mock("fails filter")
     TagFilter.should_receive(:new).with(:exclude,
-        "fails", "critical", "unstable", "incomplete", "unsupported").and_return(filter)
+        *(@tags + ["windows", "windoze"])).and_return(filter)
     filter.should_receive(:register)
     @script.run
   end

Modified: MacRuby/trunk/mspec/spec/guards/conflict_spec.rb
===================================================================
--- MacRuby/trunk/mspec/spec/guards/conflict_spec.rb	2010-03-28 04:26:06 UTC (rev 3869)
+++ MacRuby/trunk/mspec/spec/guards/conflict_spec.rb	2010-03-28 17:00:14 UTC (rev 3870)
@@ -12,11 +12,23 @@
     ScratchPad.recorded.should_not == :yield
   end
 
+  it "does not yield if Object.constants (as Symbols) 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
+
+  it "yields if Object.constants (as Symbols) 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

Added: MacRuby/trunk/mspec/spec/guards/feature_spec.rb
===================================================================
--- MacRuby/trunk/mspec/spec/guards/feature_spec.rb	                        (rev 0)
+++ MacRuby/trunk/mspec/spec/guards/feature_spec.rb	2010-03-28 17:00:14 UTC (rev 3870)
@@ -0,0 +1,80 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/guards/feature'
+
+describe FeatureGuard, ".enabled?" do
+  it "returns true if the feature is enabled" do
+    MSpec.should_receive(:feature_enabled?).with(:encoding).and_return(true)
+    FeatureGuard.enabled?(:encoding).should be_true
+  end
+
+  it "returns false if the feature is not enabled" do
+    MSpec.should_receive(:feature_enabled?).with(:encoding).and_return(false)
+    FeatureGuard.enabled?(:encoding).should be_false
+  end
+
+  it "returns true if all the features are enabled" do
+    MSpec.should_receive(:feature_enabled?).with(:one).and_return(true)
+    MSpec.should_receive(:feature_enabled?).with(:two).and_return(true)
+    FeatureGuard.enabled?(:one, :two).should be_true
+  end
+
+  it "returns false if any of the features are not enabled" do
+    MSpec.should_receive(:feature_enabled?).with(:one).and_return(true)
+    MSpec.should_receive(:feature_enabled?).with(:two).and_return(false)
+    FeatureGuard.enabled?(:one, :two).should be_false
+  end
+end
+
+describe Object, "#with_feature" do
+  before :each do
+    ScratchPad.clear
+
+    @guard = FeatureGuard.new :encoding
+    FeatureGuard.stub!(:new).and_return(@guard)
+  end
+
+  it "sets the name of the guard to :with_feature" do
+    with_feature(:encoding) { }
+    @guard.name.should == :with_feature
+  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_feature { raise Exception }
+    end.should raise_error(Exception)
+  end
+end
+
+describe Object, "#with_feature" do
+  before :each do
+    ScratchPad.clear
+  end
+
+  it "yields if the feature is enabled" do
+    MSpec.should_receive(:feature_enabled?).with(:encoding).and_return(true)
+    with_feature(:encoding) { ScratchPad.record :yield }
+    ScratchPad.recorded.should == :yield
+  end
+
+  it "yields if all the features are enabled" do
+    MSpec.should_receive(:feature_enabled?).with(:one).and_return(true)
+    MSpec.should_receive(:feature_enabled?).with(:two).and_return(true)
+    with_feature(:one, :two) { ScratchPad.record :yield }
+    ScratchPad.recorded.should == :yield
+  end
+
+  it "does not yield if the feature is not enabled" do
+    MSpec.should_receive(:feature_enabled?).with(:encoding).and_return(false)
+    with_feature(:encoding) { ScratchPad.record :yield }
+    ScratchPad.recorded.should be_nil
+  end
+
+  it "does not yield if any of the features are not enabled" do
+    MSpec.should_receive(:feature_enabled?).with(:one).and_return(true)
+    MSpec.should_receive(:feature_enabled?).with(:two).and_return(false)
+    with_feature(:one, :two) { ScratchPad.record :yield }
+    ScratchPad.recorded.should be_nil
+  end
+end

Modified: MacRuby/trunk/mspec/spec/guards/guard_spec.rb
===================================================================
--- MacRuby/trunk/mspec/spec/guards/guard_spec.rb	2010-03-28 04:26:06 UTC (rev 3869)
+++ MacRuby/trunk/mspec/spec/guards/guard_spec.rb	2010-03-28 17:00:14 UTC (rev 3870)
@@ -47,33 +47,6 @@
   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
@@ -367,7 +340,7 @@
   end
 end
 
-describe SpecGuard, "windows?" do
+describe SpecGuard, "#windows?" do
   before :each do
     @guard = SpecGuard.new
   end

Added: MacRuby/trunk/mspec/spec/guards/specified_spec.rb
===================================================================
--- MacRuby/trunk/mspec/spec/guards/specified_spec.rb	                        (rev 0)
+++ MacRuby/trunk/mspec/spec/guards/specified_spec.rb	2010-03-28 17:00:14 UTC (rev 3870)
@@ -0,0 +1,102 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/guards/specified'
+
+describe Object, "#unspecified" do
+  before :each do
+    ScratchPad.clear
+
+    @guard = UnspecifiedGuard.new
+    UnspecifiedGuard.stub!(:new).and_return(@guard)
+  end
+
+  it "does not yield if #standard? returns true" do
+    @guard.should_receive(:standard?).and_return(true)
+    unspecified { ScratchPad.record :yield }
+    ScratchPad.recorded.should be_nil
+  end
+
+  it "yields if #standard? returns false" do
+    @guard.should_receive(:standard?).and_return(false)
+    unspecified { ScratchPad.record :yield }
+    ScratchPad.recorded.should == :yield
+  end
+
+  it "sets the name of the guard to :unspecified" do
+    @guard.should_receive(:standard?).and_return(true)
+    unspecified { }
+    @guard.name.should == :unspecified
+  end
+
+  it "calls #unregister even when an exception is raised in the guard block" do
+    guard = UnspecifiedGuard.new :rubinius
+    UnspecifiedGuard.stub!(:new).and_return(guard)
+    guard.should_receive(:match?).and_return(true)
+    guard.should_receive(:unregister)
+
+    lambda do
+      unspecified { raise Exception }
+    end.should raise_error(Exception)
+  end
+end
+
+describe Object, "#specified_on" do
+  before :each do
+    ScratchPad.clear
+  end
+
+  it "raises an Exception when passed :ruby" do
+    lambda {
+      specifed_on(:ruby) { ScratchPad.record :yield }
+    }.should raise_error(Exception)
+    ScratchPad.recorded.should_not == :yield
+  end
+
+  it "does not yield when #standard? returns true" do
+    guard = SpecifiedOnGuard.new
+    SpecifiedOnGuard.stub!(:new).and_return(guard)
+    guard.should_receive(:standard?).and_return(true)
+
+    specified_on(:rubinius) { ScratchPad.record :yield }
+    ScratchPad.recorded.should be_nil
+  end
+
+  it "does not yield when #standard? returns false and #implementation? returns false" do
+    guard = SpecifiedOnGuard.new :rubinius
+    SpecifiedOnGuard.stub!(:new).and_return(guard)
+    guard.should_receive(:standard?).and_return(false)
+    guard.should_receive(:implementation?).with(:rubinius).and_return(false)
+
+    specified_on(:rubinius) { ScratchPad.record :yield }
+    ScratchPad.recorded.should be_nil
+  end
+
+  it "yields when #standard? returns false and #implementation? returns true" do
+    guard = SpecifiedOnGuard.new :rubinius
+    SpecifiedOnGuard.stub!(:new).and_return(guard)
+    guard.should_receive(:standard?).and_return(false)
+    guard.should_receive(:implementation?).with(:rubinius).and_return(true)
+
+    specified_on(:rubinius) { ScratchPad.record :yield }
+    ScratchPad.recorded.should == :yield
+  end
+
+  it "sets the name of the guard to :specified_on" do
+    guard = SpecifiedOnGuard.new :rubinius
+    SpecifiedOnGuard.stub!(:new).and_return(guard)
+    guard.should_receive(:match?).and_return(false)
+
+    specified_on(:rubinius) { }
+    guard.name.should == :specified_on
+  end
+
+  it "calls #unregister even when an exception is raised in the guard block" do
+    guard = SpecifiedOnGuard.new :rubinius
+    SpecifiedOnGuard.stub!(:new).and_return(guard)
+    guard.should_receive(:match?).and_return(true)
+    guard.should_receive(:unregister)
+
+    lambda do
+      specified_on(:rubinius) { raise Exception }
+    end.should raise_error(Exception)
+  end
+end

Added: MacRuby/trunk/mspec/spec/helpers/encode_spec.rb
===================================================================
--- MacRuby/trunk/mspec/spec/helpers/encode_spec.rb	                        (rev 0)
+++ MacRuby/trunk/mspec/spec/helpers/encode_spec.rb	2010-03-28 17:00:14 UTC (rev 3870)
@@ -0,0 +1,26 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/helpers/encode'
+
+describe Object, "#encode" do
+  it "raises an ArgumentError if the str parameter is not a String" do
+    lambda { encode(Object.new, "utf-8") }.should raise_error(ArgumentError)
+  end
+
+  it "raises an ArgumentError if the encoding parameter is not a String" do
+    lambda { encode("some str", Object.new) }.should raise_error(ArgumentError)
+  end
+
+  it "calls #force_encoding if the :encoding feature is enabled" do
+    FeatureGuard.should_receive(:enabled?).with(:encoding).and_return(true)
+    str = "some text"
+    str.should_receive(:force_encoding).with("utf-8")
+    encode(str, "utf-8")
+  end
+
+  it "does not call #force_encoding if the :encoding feature is not enabled" do
+    FeatureGuard.should_receive(:enabled?).with(:encoding).and_return(false)
+    str = "some text"
+    str.should_not_receive(:force_encoding)
+    encode(str, "utf-8")
+  end
+end

Modified: MacRuby/trunk/mspec/spec/helpers/environment_spec.rb
===================================================================
--- MacRuby/trunk/mspec/spec/helpers/environment_spec.rb	2010-03-28 04:26:06 UTC (rev 3869)
+++ MacRuby/trunk/mspec/spec/helpers/environment_spec.rb	2010-03-28 17:00:14 UTC (rev 3870)
@@ -1,43 +1,30 @@
 require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/guards/platform'
 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"
+    PlatformGuard.stub!(:windows?).and_return(false)
     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"
+  it "calls `cmd.exe /C set` on Windows" do
+    PlatformGuard.stub!(:windows?).and_return(true)
     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"
+    PlatformGuard.stub!(:windows?).and_return(false)
     should_receive(:`).with("env").and_return("one=two\nthree=four")
     env.should == {"one" => "two", "three" => "four"}
 
-    Object.const_set :RUBY_PLATFORM, "mswin32"
+    PlatformGuard.stub!(:windows?).and_return(true)
     should_receive(:`).with("cmd.exe /C set").and_return("five=six\nseven=eight")
     env.should == {"five" => "six", "seven" => "eight"}
   end
@@ -52,30 +39,24 @@
     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"
+  it "calls `cmd.exe /C ECHO %USERNAME%` on Windows" do
+    PlatformGuard.stub!(:windows?).and_return(true)
     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"
+    PlatformGuard.stub!(:windows?).and_return(false)
     should_receive(:`).with("whoami").and_return("john")
     username
   end
 
   it "returns the user's username" do
-    Object.const_set :RUBY_PLATFORM, "mswin32"
+    PlatformGuard.stub!(:windows?).and_return(true)
     should_receive(:`).with("cmd.exe /C ECHO %USERNAME%").and_return("johnonwin")
     username.should == "johnonwin"
 
-    Object.const_set :RUBY_PLATFORM, "notwindows"
+    PlatformGuard.stub!(:windows?).and_return(false)
     should_receive(:`).with("whoami").and_return("john")
     username.should == "john"
   end

Modified: MacRuby/trunk/mspec/spec/helpers/fixture_spec.rb
===================================================================
--- MacRuby/trunk/mspec/spec/helpers/fixture_spec.rb	2010-03-28 04:26:06 UTC (rev 3869)
+++ MacRuby/trunk/mspec/spec/helpers/fixture_spec.rb	2010-03-28 17:00:14 UTC (rev 3870)
@@ -15,4 +15,9 @@
     name = fixture("some/path/shared/file.rb", "dir", "file.txt")
     name.should == "#{@dir}/some/path/fixtures/dir/file.txt"
   end
+
+  it "does not append '/fixtures' if it is the suffix of the directory string" do
+    name = fixture("some/path/fixtures/file.rb", "dir", "file.txt")
+    name.should == "#{@dir}/some/path/fixtures/dir/file.txt"
+  end
 end

Added: MacRuby/trunk/mspec/spec/helpers/fmode_spec.rb
===================================================================
--- MacRuby/trunk/mspec/spec/helpers/fmode_spec.rb	                        (rev 0)
+++ MacRuby/trunk/mspec/spec/helpers/fmode_spec.rb	2010-03-28 17:00:14 UTC (rev 3870)
@@ -0,0 +1,14 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/helpers/fmode'
+
+describe Object, "#fmode" do
+  it "returns the argument unmodified if :encoding feature is enabled" do
+    FeatureGuard.should_receive(:enabled?).with(:encoding).and_return(true)
+    fmode("rb:binary:utf-8").should == "rb:binary:utf-8"
+  end
+
+  it "returns only the file access mode if :encoding feature is not enabled" do
+    FeatureGuard.should_receive(:enabled?).with(:encoding).and_return(false)
+    fmode("rb:binary:utf-8").should == "rb"
+  end
+end

Modified: MacRuby/trunk/mspec/spec/helpers/ruby_exe_spec.rb
===================================================================
--- MacRuby/trunk/mspec/spec/helpers/ruby_exe_spec.rb	2010-03-28 04:26:06 UTC (rev 3869)
+++ MacRuby/trunk/mspec/spec/helpers/ruby_exe_spec.rb	2010-03-28 17:00:14 UTC (rev 3870)
@@ -73,21 +73,15 @@
   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"
+    PlatformGuard.stub!(:windows?).and_return(false)
     @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)
@@ -95,7 +89,7 @@
   end
 
   it "returns the value returned by #ruby_exe_options if it exists on Windows platforms" do
-    Object.const_set :RUBY_PLATFORM, "mswin"
+    PlatformGuard.stub!(:windows?).and_return(true)
     @script.should_receive(:ruby_exe_options).and_return(@name)
     File.should_receive(:exists?).with(@name).and_return(true)
     File.should_not_receive(:executable?)

Modified: MacRuby/trunk/mspec/spec/matchers/equal_utf16_spec.rb
===================================================================
--- MacRuby/trunk/mspec/spec/matchers/equal_utf16_spec.rb	2010-03-28 04:26:06 UTC (rev 3869)
+++ MacRuby/trunk/mspec/spec/matchers/equal_utf16_spec.rb	2010-03-28 17:00:14 UTC (rev 3870)
@@ -43,15 +43,15 @@
     matcher = EqualUtf16Matcher.new("a\0b\0")
     matcher.matches?("a\0b\0c\0")
     matcher.failure_message.should == [
-      "Expected \"a#{@null}b#{@null}c#{@null}\"\n",
-      "to equal \"a#{@null}b#{@null}\"\n or \"#{@null}a#{@null}b\"\n"]
+      "Expected [\"a#{@null}b#{@null}c#{@null}\"]\n",
+      "to equal [\"a#{@null}b#{@null}\"]\n or [\"#{@null}a#{@null}b\"]\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 \"#{@null}a#{@null}b\"\n",
-      "not to equal \"a#{@null}b#{@null}\"\n nor \"#{@null}a#{@null}b\"\n"]
+      "Expected [\"#{@null}a#{@null}b\"]\n",
+      "not to equal [\"a#{@null}b#{@null}\"]\n nor [\"#{@null}a#{@null}b\"]\n"]
   end
 end

Added: MacRuby/trunk/mspec/spec/matchers/have_data_spec.rb
===================================================================
--- MacRuby/trunk/mspec/spec/matchers/have_data_spec.rb	                        (rev 0)
+++ MacRuby/trunk/mspec/spec/matchers/have_data_spec.rb	2010-03-28 17:00:14 UTC (rev 3870)
@@ -0,0 +1,51 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+require 'mspec/expectations/expectations'
+require 'mspec/matchers/have_data'
+require 'mspec/helpers/tmp'
+require 'mspec/helpers/fs'
+
+describe HaveDataMatcher do
+  before :each do
+    @name = tmp "have_data_matcher"
+    touch(@name) { |f| f.puts "123abc" }
+  end
+
+  after :each do
+    rm_r @name
+  end
+
+  it "raises an IOError if the named file does not exist" do
+    lambda do
+      HaveDataMatcher.new("123").matches?("no_file.txt")
+    end.should raise_error(Errno::ENOENT)
+  end
+
+  it "matches when the named file begins with the same bytes as data" do
+    HaveDataMatcher.new("123a").matches?(@name).should be_true
+  end
+
+  it "does not match when the named file begins with fewer bytes than data" do
+    HaveDataMatcher.new("123abcPQR").matches?(@name).should be_false
+
+  end
+
+  it "does not match when the named file begins with different bytes than data" do
+    HaveDataMatcher.new("abc1").matches?(@name).should be_false
+  end
+
+  it "provides a useful failure message" do
+    matcher = HaveDataMatcher.new("abc1")
+    matcher.matches?(@name)
+    matcher.failure_message.should == [
+      "Expected #{@name}", "to have data \"abc1\"\n"
+    ]
+  end
+
+  it "provides a useful negative failure message" do
+    matcher = HaveDataMatcher.new("123abc")
+    matcher.matches?(@name)
+    matcher.negative_failure_message.should == [
+      "Expected #{@name}", "not to have data \"123abc\"\n"
+    ]
+  end
+end

Modified: MacRuby/trunk/mspec/spec/mocks/mock_spec.rb
===================================================================
--- MacRuby/trunk/mspec/spec/mocks/mock_spec.rb	2010-03-28 04:26:06 UTC (rev 3869)
+++ MacRuby/trunk/mspec/spec/mocks/mock_spec.rb	2010-03-28 17:00:14 UTC (rev 3870)
@@ -446,4 +446,15 @@
     Mock.cleanup
     Mock.stubs.should == {}
   end
+
+  it "removes the replaced name for mocks" do
+    replaced_key = Mock.replaced_key(@mock, :method_call)
+    Mock.should_receive(:clear_replaced).with(replaced_key)
+
+    replaced_name = Mock.replaced_name(@mock, :method_call)
+    Mock.replaced?(replaced_name).should be_true
+
+    Mock.cleanup
+    Mock.replaced?(replaced_name).should be_false
+  end
 end

Modified: MacRuby/trunk/mspec/spec/runner/context_spec.rb
===================================================================
--- MacRuby/trunk/mspec/spec/runner/context_spec.rb	2010-03-28 04:26:06 UTC (rev 3869)
+++ MacRuby/trunk/mspec/spec/runner/context_spec.rb	2010-03-28 17:00:14 UTC (rev 3870)
@@ -262,6 +262,12 @@
     state.parent = @parent
   end
 
+  it "does not set parents if shared" do
+    state = ContextState.new "", :shared => true
+    state.parent = @parent
+    state.parents.should == [state]
+  end
+
   it "sets self as a child of parent" do
     @parent.should_receive(:child).with(@state)
     @state.parent = @parent
@@ -910,11 +916,11 @@
 
 describe ContextState, "#it_should_behave_like" do
   before :each do
-    @shared_desc = "shared context"
+    @shared_desc = :shared_context
     @shared = ContextState.new(@shared_desc, :shared => true)
     MSpec.stub!(:retrieve_shared).and_return(@shared)
 
-    @state = ContextState.new ""
+    @state = ContextState.new "Top level"
     @a = lambda { }
     @b = lambda { }
   end
@@ -926,36 +932,57 @@
 
   describe "for nested ContextState instances" do
     before :each do
-      @nested = ContextState.new ""
+      @nested = ContextState.new "nested context"
       @nested.parents.unshift @shared
+
       @shared.children << @nested
+
+      @nested_dup = @nested.dup
+      @nested.stub!(:dup).and_return(@nested_dup)
     end
 
-    it "adds nested describe blocks to the invoking ContextState" do
+    it "duplicates the nested ContextState" do
       @state.it_should_behave_like @shared_desc
-      @shared.children.should_not be_empty
-      @state.children.should include(*@shared.children)
+      @state.children.first.should equal(@nested_dup)
     end
 
-    it "changes the parent ContextState" do
-      @shared.children.first.parents.first.should equal(@shared)
+    it "sets the parent of the nested ContextState to the containing ContextState" do
       @state.it_should_behave_like @shared_desc
-      @shared.children.first.parents.first.should equal(@state)
+      @nested_dup.parent.should equal(@state)
     end
+
+    it "sets the context for nested examples to the nested ContextState's dup" do
+      @shared.it "an example", &@a
+      @shared.it "another example", &@b
+      @state.it_should_behave_like @shared_desc
+      @nested_dup.examples.each { |x| x.context.should equal(@nested_dup) }
+    end
+
+    it "omits the shored ContextState's description" do
+      @nested.it "an example", &@a
+      @nested.it "another example", &@b
+      @state.it_should_behave_like @shared_desc
+
+      @nested_dup.description.should == "Top level nested context"
+      @nested_dup.examples.first.description.should == "Top level nested context an example"
+      @nested_dup.examples.last.description.should == "Top level nested context another example"
+    end
   end
 
-  it "adds examples from the shared ContextState" do
-    @shared.it "some", &@a
-    @shared.it "thing", &@b
+  it "adds duped examples from the shared ContextState" do
+    @shared.it "some method", &@a
+    ex_dup = @shared.examples.first.dup
+    @shared.examples.first.stub!(:dup).and_return(ex_dup)
+
     @state.it_should_behave_like @shared_desc
-    @state.examples.should include(*@shared.examples)
+    @state.examples.should == [ex_dup]
   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) }
+  it "sets the context for examples to the containing ContextState" do
+    @shared.it "an example", &@a
+    @shared.it "another example", &@b
     @state.it_should_behave_like @shared_desc
+    @state.examples.each { |x| x.context.should equal(@state) }
   end
 
   it "adds before(:all) blocks from the shared ContextState" do

Modified: MacRuby/trunk/mspec/spec/runner/example_spec.rb
===================================================================
--- MacRuby/trunk/mspec/spec/runner/example_spec.rb	2010-03-28 04:26:06 UTC (rev 3869)
+++ MacRuby/trunk/mspec/spec/runner/example_spec.rb	2010-03-28 17:00:14 UTC (rev 3870)
@@ -71,6 +71,11 @@
     @filter = mock("filter")
   end
 
+  after :each do
+    MSpec.store :include, nil
+    MSpec.store :exclude, nil
+  end
+
   it "returns false if MSpec include filters list is empty" do
     @state.filtered?.should == false
   end

Modified: MacRuby/trunk/mspec/spec/runner/formatters/dotted_spec.rb
===================================================================
--- MacRuby/trunk/mspec/spec/runner/formatters/dotted_spec.rb	2010-03-28 04:26:06 UTC (rev 3869)
+++ MacRuby/trunk/mspec/spec/runner/formatters/dotted_spec.rb	2010-03-28 17:00:14 UTC (rev 3870)
@@ -2,6 +2,7 @@
 require 'mspec/runner/formatters/dotted'
 require 'mspec/runner/mspec'
 require 'mspec/runner/example'
+require 'mspec/utils/script'
 
 describe DottedFormatter, "#initialize" do
   it "permits zero arguments" do
@@ -16,10 +17,10 @@
 describe DottedFormatter, "#register" do
   before :each do
     @formatter = DottedFormatter.new
+    MSpec.stub!(:register)
   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)

Modified: MacRuby/trunk/mspec/spec/runner/formatters/file_spec.rb
===================================================================
--- MacRuby/trunk/mspec/spec/runner/formatters/file_spec.rb	2010-03-28 04:26:06 UTC (rev 3869)
+++ MacRuby/trunk/mspec/spec/runner/formatters/file_spec.rb	2010-03-28 17:00:14 UTC (rev 3870)
@@ -6,17 +6,17 @@
 describe FileFormatter, "#register" do
   before :each do
     @formatter = FileFormatter.new
+    MSpec.stub!(:register)
+    MSpec.stub!(:unregister)
   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

Modified: MacRuby/trunk/mspec/spec/runner/formatters/html_spec.rb
===================================================================
--- MacRuby/trunk/mspec/spec/runner/formatters/html_spec.rb	2010-03-28 04:26:06 UTC (rev 3869)
+++ MacRuby/trunk/mspec/spec/runner/formatters/html_spec.rb	2010-03-28 17:00:14 UTC (rev 3870)
@@ -4,6 +4,7 @@
 require 'mspec/runner/formatters/html'
 require 'mspec/runner/mspec'
 require 'mspec/runner/example'
+require 'mspec/utils/script'
 
 describe HtmlFormatter do
   before :each do

Modified: MacRuby/trunk/mspec/spec/runner/formatters/method_spec.rb
===================================================================
--- MacRuby/trunk/mspec/spec/runner/formatters/method_spec.rb	2010-03-28 04:26:06 UTC (rev 3869)
+++ MacRuby/trunk/mspec/spec/runner/formatters/method_spec.rb	2010-03-28 17:00:14 UTC (rev 3870)
@@ -2,6 +2,7 @@
 require 'mspec/runner/formatters/method'
 require 'mspec/runner/mspec'
 require 'mspec/runner/example'
+require 'mspec/utils/script'
 
 describe MethodFormatter, "#method_type" do
   before :each do

Modified: MacRuby/trunk/mspec/spec/runner/formatters/spinner_spec.rb
===================================================================
--- MacRuby/trunk/mspec/spec/runner/formatters/spinner_spec.rb	2010-03-28 04:26:06 UTC (rev 3869)
+++ MacRuby/trunk/mspec/spec/runner/formatters/spinner_spec.rb	2010-03-28 17:00:14 UTC (rev 3870)
@@ -16,10 +16,10 @@
 describe SpinnerFormatter, "#register" do
   before :each do
     @formatter = SpinnerFormatter.new
+    MSpec.stub!(:register)
   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)

Modified: MacRuby/trunk/mspec/spec/runner/formatters/unit_spec.rb
===================================================================
--- MacRuby/trunk/mspec/spec/runner/formatters/unit_spec.rb	2010-03-28 04:26:06 UTC (rev 3869)
+++ MacRuby/trunk/mspec/spec/runner/formatters/unit_spec.rb	2010-03-28 17:00:14 UTC (rev 3870)
@@ -1,6 +1,7 @@
 require File.dirname(__FILE__) + '/../../spec_helper'
 require 'mspec/runner/formatters/unit'
 require 'mspec/runner/example'
+require 'mspec/utils/script'
 
 describe UnitdiffFormatter, "#finish" do
   before :each do

Modified: MacRuby/trunk/mspec/spec/runner/shared_spec.rb
===================================================================
--- MacRuby/trunk/mspec/spec/runner/shared_spec.rb	2010-03-28 04:26:06 UTC (rev 3869)
+++ MacRuby/trunk/mspec/spec/runner/shared_spec.rb	2010-03-28 17:00:14 UTC (rev 3870)
@@ -1,27 +1,78 @@
 require File.dirname(__FILE__) + '/../spec_helper'
 require 'mspec/runner/shared'
+require 'mspec/runner/context'
+require 'mspec/runner/example'
 
 describe Object, "#it_behaves_like" do
   before :each do
-    @recv = Object.new
-    def @recv.before(what)
-      yield
-    end
-    @recv.stub!(:it_should_behave_like)
+    ScratchPad.clear
+
+    @state = ContextState.new "Top level"
+    @state.instance_variable_set :@parsed, true
+
+    @shared = ContextState.new :shared_spec, :shared => true
+    MSpec.stub!(:retrieve_shared).and_return(@shared)
   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
+    @shared.it("an example") { ScratchPad.record @method }
+    @state.it_behaves_like :shared_spec, :some_method
+    @state.process
+    ScratchPad.recorded.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
+  it "creates @object if the passed object" do
+    object = Object.new
+    @shared.it("an example") { ScratchPad.record @object }
+    @state.it_behaves_like :shared_spec, :some_method, object
+    @state.process
+    ScratchPad.recorded.should == object
   end
 
   it "sends :it_should_behave_like" do
-    @recv.should_receive(:it_should_behave_like)
-    @recv.it_behaves_like "something", :some_method
+    @state.should_receive(:it_should_behave_like)
+    @state.it_behaves_like :shared_spec, :some_method
   end
+
+  describe "with multiple shared contexts" do
+    before :each do
+      @obj = Object.new
+      @obj2 = Object.new
+
+      @state2 = ContextState.new "Second top level"
+      @state2.instance_variable_set :@parsed, true
+    end
+
+    it "ensures the shared spec state is distinct" do
+      @shared.it("an example") { ScratchPad.record [@method, @object] }
+
+      @state.it_behaves_like :shared_spec, :some_method, @obj
+
+      @state.process
+      ScratchPad.recorded.should == [:some_method, @obj]
+
+      @state2.it_behaves_like :shared_spec, :another_method, @obj2
+
+      @state2.process
+      ScratchPad.recorded.should == [:another_method, @obj2]
+    end
+
+    it "ensures the shared spec state is distinct for nested shared specs" do
+      nested = ContextState.new "nested context"
+      nested.instance_variable_set :@parsed, true
+      nested.parent = @shared
+
+      nested.it("another example") { ScratchPad.record [:shared, @method, @object] }
+
+      @state.it_behaves_like :shared_spec, :some_method, @obj
+
+      @state.process
+      ScratchPad.recorded.should == [:shared, :some_method, @obj]
+
+      @state2.it_behaves_like :shared_spec, :another_method, @obj2
+
+      @state2.process
+      ScratchPad.recorded.should == [:shared, :another_method, @obj2]
+    end
+  end
 end

Modified: MacRuby/trunk/mspec/upstream
===================================================================
--- MacRuby/trunk/mspec/upstream	2010-03-28 04:26:06 UTC (rev 3869)
+++ MacRuby/trunk/mspec/upstream	2010-03-28 17:00:14 UTC (rev 3870)
@@ -1 +1 @@
-ddf4bd5a4c29e36caad2504749a7bfb5dc8eadf5
\ No newline at end of file
+88d85da83cbf2e595ec956651520ce119d429e8f
\ No newline at end of file
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/macruby-changes/attachments/20100328/ca8eac9b/attachment-0001.html>


More information about the macruby-changes mailing list