summaryrefslogtreecommitdiffstats
path: root/test/lib/spec
diff options
context:
space:
mode:
authorluke <luke@980ebf18-57e1-0310-9a29-db15c13687c0>2007-03-17 02:48:41 +0000
committerluke <luke@980ebf18-57e1-0310-9a29-db15c13687c0>2007-03-17 02:48:41 +0000
commitba23a5ac276e59fdda8186750c6d0fd2cfecdeac (patch)
tree1e14b25ade74ea52d8da2788ede9b12b507867e8 /test/lib/spec
parent8ea6adaeb1e3d0aa6348c2a2c3a385d185372d06 (diff)
downloadpuppet-ba23a5ac276e59fdda8186750c6d0fd2cfecdeac.tar.gz
puppet-ba23a5ac276e59fdda8186750c6d0fd2cfecdeac.tar.xz
puppet-ba23a5ac276e59fdda8186750c6d0fd2cfecdeac.zip
Adding spec libs, so we can use them some day
git-svn-id: https://reductivelabs.com/svn/puppet/trunk@2283 980ebf18-57e1-0310-9a29-db15c13687c0
Diffstat (limited to 'test/lib/spec')
-rw-r--r--test/lib/spec/callback.rb11
-rw-r--r--test/lib/spec/callback/callback_container.rb60
-rw-r--r--test/lib/spec/callback/extensions/module.rb24
-rw-r--r--test/lib/spec/callback/extensions/object.rb37
-rw-r--r--test/lib/spec/deprecated.rb3
-rw-r--r--test/lib/spec/expectations.rb59
-rw-r--r--test/lib/spec/expectations/differs/default.rb62
-rw-r--r--test/lib/spec/expectations/errors.rb6
-rw-r--r--test/lib/spec/expectations/extensions.rb3
-rw-r--r--test/lib/spec/expectations/extensions/object.rb109
-rw-r--r--test/lib/spec/expectations/extensions/proc.rb57
-rw-r--r--test/lib/spec/expectations/extensions/string_and_symbol.rb17
-rw-r--r--test/lib/spec/expectations/handler.rb47
-rw-r--r--test/lib/spec/expectations/should.rb5
-rwxr-xr-xtest/lib/spec/expectations/should/base.rb64
-rw-r--r--test/lib/spec/expectations/should/change.rb69
-rw-r--r--test/lib/spec/expectations/should/have.rb128
-rwxr-xr-xtest/lib/spec/expectations/should/not.rb74
-rwxr-xr-xtest/lib/spec/expectations/should/should.rb81
-rw-r--r--test/lib/spec/expectations/sugar.rb47
-rw-r--r--test/lib/spec/matchers.rb160
-rw-r--r--test/lib/spec/matchers/be.rb161
-rw-r--r--test/lib/spec/matchers/be_close.rb37
-rw-r--r--test/lib/spec/matchers/change.rb120
-rw-r--r--test/lib/spec/matchers/eql.rb43
-rw-r--r--test/lib/spec/matchers/equal.rb43
-rw-r--r--test/lib/spec/matchers/has.rb44
-rw-r--r--test/lib/spec/matchers/have.rb140
-rw-r--r--test/lib/spec/matchers/include.rb50
-rw-r--r--test/lib/spec/matchers/match.rb41
-rw-r--r--test/lib/spec/matchers/raise_error.rb100
-rw-r--r--test/lib/spec/matchers/respond_to.rb35
-rw-r--r--test/lib/spec/matchers/satisfy.rb47
-rw-r--r--test/lib/spec/matchers/throw_symbol.rb75
-rw-r--r--test/lib/spec/mocks.rb232
-rw-r--r--test/lib/spec/mocks/argument_expectation.rb132
-rw-r--r--test/lib/spec/mocks/error_generator.rb85
-rw-r--r--test/lib/spec/mocks/errors.rb10
-rw-r--r--test/lib/spec/mocks/extensions/object.rb3
-rw-r--r--test/lib/spec/mocks/message_expectation.rb231
-rw-r--r--test/lib/spec/mocks/methods.rb40
-rw-r--r--test/lib/spec/mocks/mock.rb26
-rw-r--r--test/lib/spec/mocks/mock_handler.rb166
-rw-r--r--test/lib/spec/mocks/order_group.rb29
-rw-r--r--test/lib/spec/rake/spectask.rb173
-rw-r--r--test/lib/spec/rake/verify_rcov.rb47
-rw-r--r--test/lib/spec/runner.rb132
-rw-r--r--test/lib/spec/runner/backtrace_tweaker.rb55
-rw-r--r--test/lib/spec/runner/command_line.rb34
-rw-r--r--test/lib/spec/runner/context.rb154
-rw-r--r--test/lib/spec/runner/context_eval.rb142
-rw-r--r--test/lib/spec/runner/context_runner.rb55
-rw-r--r--test/lib/spec/runner/drb_command_line.rb21
-rw-r--r--test/lib/spec/runner/execution_context.rb17
-rw-r--r--test/lib/spec/runner/extensions/kernel.rb17
-rw-r--r--test/lib/spec/runner/extensions/object.rb32
-rw-r--r--test/lib/spec/runner/formatter.rb5
-rw-r--r--test/lib/spec/runner/formatter/base_text_formatter.rb118
-rw-r--r--test/lib/spec/runner/formatter/html_formatter.rb219
-rw-r--r--test/lib/spec/runner/formatter/progress_bar_formatter.rb27
-rw-r--r--test/lib/spec/runner/formatter/rdoc_formatter.rb22
-rw-r--r--test/lib/spec/runner/formatter/specdoc_formatter.rb23
-rw-r--r--test/lib/spec/runner/heckle_runner.rb71
-rw-r--r--test/lib/spec/runner/heckle_runner_win.rb10
-rw-r--r--test/lib/spec/runner/option_parser.rb224
-rw-r--r--test/lib/spec/runner/reporter.rb105
-rwxr-xr-xtest/lib/spec/runner/spec_matcher.rb25
-rw-r--r--test/lib/spec/runner/spec_parser.rb41
-rw-r--r--test/lib/spec/runner/spec_should_raise_handler.rb74
-rw-r--r--test/lib/spec/runner/specification.rb114
-rw-r--r--test/lib/spec/translator.rb87
-rw-r--r--test/lib/spec/version.rb30
72 files changed, 5087 insertions, 0 deletions
diff --git a/test/lib/spec/callback.rb b/test/lib/spec/callback.rb
new file mode 100644
index 000000000..aa7ecec0f
--- /dev/null
+++ b/test/lib/spec/callback.rb
@@ -0,0 +1,11 @@
+require 'spec/callback/callback_container'
+require 'spec/callback/extensions/module'
+require 'spec/callback/extensions/object'
+
+# Callback is a fork of Brian Takita's "callback library" (see http://callback.rubyforge.org),
+# which Brian graciously contributed to RSpec in order to avoid the dependency.
+#
+# RSpec uses Callback internally to create hooks to Spec::Runner events. If you're interested
+# in a simple, powerful API for generating callback events, check out http://callback.rubyforge.org.
+module Callback
+end
diff --git a/test/lib/spec/callback/callback_container.rb b/test/lib/spec/callback/callback_container.rb
new file mode 100644
index 000000000..24d4c0ced
--- /dev/null
+++ b/test/lib/spec/callback/callback_container.rb
@@ -0,0 +1,60 @@
+module Callback
+ class CallbackContainer
+ def initialize
+ @callback_registry = Hash.new do |hash, key|
+ hash[key] = Array.new
+ end
+ end
+
+ # Defines the callback with the key in this container.
+ def define(key, callback_proc=nil, &callback_block)
+ callback = extract_callback(callback_block, callback_proc) do
+ raise "You must define the callback that accepts the call method."
+ end
+ @callback_registry[key] << callback
+ callback
+ end
+
+ # Undefines the callback with the key in this container.
+ def undefine(key, callback_proc)
+ callback = extract_callback(callback_proc) do
+ raise "You may only undefine callbacks that use the call method."
+ end
+ @callback_registry[key].delete callback
+ callback
+ end
+
+ # Notifies the callbacks for the key. Arguments may be passed.
+ # An error handler may be passed in as a block. If there is an error, the block is called with
+ # error object as an argument.
+ # An array of the return values of the callbacks is returned.
+ def notify(key, *args, &error_handler)
+ @callback_registry[key].collect do |callback|
+ begin
+ callback.call(*args)
+ rescue Exception => e
+ yield(e) if error_handler
+ end
+ end
+ end
+
+ # Clears all of the callbacks in this container.
+ def clear
+ @callback_registry.clear
+ end
+
+ protected
+ def extract_callback(first_choice_callback, second_choice_callback = nil)
+ callback = nil
+ if first_choice_callback
+ callback = first_choice_callback
+ elsif second_choice_callback
+ callback = second_choice_callback
+ end
+ unless callback.respond_to? :call
+ yield
+ end
+ return callback
+ end
+ end
+end
diff --git a/test/lib/spec/callback/extensions/module.rb b/test/lib/spec/callback/extensions/module.rb
new file mode 100644
index 000000000..429268ed1
--- /dev/null
+++ b/test/lib/spec/callback/extensions/module.rb
@@ -0,0 +1,24 @@
+module Callback
+ module ModuleMethods
+ # For each event_name submitted, defines a callback event with this name.
+ # Client code can then register as a callback listener using object.event_name.
+ def callback_events(*event_names)
+ event_names.each do |event_name|
+ define_callback_event(event_name)
+ end
+ end
+
+ private
+ def define_callback_event(event_name)
+ module_eval <<-EOS
+ def #{event_name}(&block)
+ register_callback(:#{event_name}, &block)
+ end
+ EOS
+ end
+ end
+end
+
+class Module
+ include Callback::ModuleMethods
+end
diff --git a/test/lib/spec/callback/extensions/object.rb b/test/lib/spec/callback/extensions/object.rb
new file mode 100644
index 000000000..c6ac6fd14
--- /dev/null
+++ b/test/lib/spec/callback/extensions/object.rb
@@ -0,0 +1,37 @@
+module Callback
+ module InstanceMethods
+ # Registers a callback for the event on the object. The callback can either be a block or a proc.
+ # When the callbacks are notified, the return value of the proc is passed to the caller.
+ def register_callback(event, callback_proc=nil, &callback_block)
+ callbacks.define(event, callback_proc, &callback_block)
+ end
+
+ # Removes the callback from the event. The callback proc must be the same
+ # object as the one that was passed to register_callback.
+ def unregister_callback(event, callback_proc)
+ callbacks.undefine(event, callback_proc)
+ end
+
+ protected
+ # Notifies the callbacks registered with the event on the object. Arguments can be passed to the callbacks.
+ # An error handler may be passed in as a block. If there is an error, the block is called with
+ # error object as an argument.
+ # An array of the return values of the callbacks is returned.
+ def notify_callbacks(event, *args, &error_handler)
+ callbacks.notify(event, *args, &error_handler)
+ end
+
+ def notify_class_callbacks(event, *args, &error_handler)
+ self.class.send(:notify_callbacks, event, *args, &error_handler)
+ end
+
+ # The CallbackContainer for this object.
+ def callbacks
+ @callbacks ||= CallbackContainer.new
+ end
+ end
+end
+
+class Object
+ include Callback::InstanceMethods
+end \ No newline at end of file
diff --git a/test/lib/spec/deprecated.rb b/test/lib/spec/deprecated.rb
new file mode 100644
index 000000000..e9c1cd829
--- /dev/null
+++ b/test/lib/spec/deprecated.rb
@@ -0,0 +1,3 @@
+def deprecated(&block)
+ block.call unless ENV['RSPEC_DISABLE_DEPRECATED_FEATURES'] == 'true'
+end
diff --git a/test/lib/spec/expectations.rb b/test/lib/spec/expectations.rb
new file mode 100644
index 000000000..cc58bba15
--- /dev/null
+++ b/test/lib/spec/expectations.rb
@@ -0,0 +1,59 @@
+require 'spec/deprecated'
+require 'spec/matchers'
+require 'spec/expectations/sugar'
+require 'spec/expectations/errors'
+require 'spec/expectations/extensions'
+require 'spec/expectations/should'
+require 'spec/expectations/handler'
+
+module Spec
+
+ # Spec::Expectations lets you set expectations on your objects.
+ #
+ # result.should == 37
+ # team.should have(11).players_on_the_field
+ #
+ # == How Expectations work.
+ #
+ # Spec::Expectations adds two methods to Object:
+ #
+ # should(matcher=nil)
+ # should_not(matcher=nil)
+ #
+ # Both methods take an optional Expression Matcher (See Spec::Matchers).
+ #
+ # When +should+ receives an Expression Matcher, it calls <tt>matches?(self)</tt>. If
+ # it returns +true+, the spec passes and execution continues. If it returns
+ # +false+, then the spec fails with the message returned by <tt>matcher.failure_message</tt>.
+ #
+ # Similarly, when +should_not+ receives a matcher, it calls <tt>matches?(self)</tt>. If
+ # it returns +false+, the spec passes and execution continues. If it returns
+ # +true+, then the spec fails with the message returned by <tt>matcher.negative_failure_message</tt>.
+ #
+ # RSpec ships with a standard set of useful matchers, and writing your own
+ # matchers is quite simple. See Spec::Matchers for details.
+ module Expectations
+ class << self
+ attr_accessor :differ
+
+ # raises a Spec::Expectations::ExpectationNotMetError with message
+ #
+ # When a differ has been assigned and fail_with is passed
+ # <code>expected</code> and <code>target</code>, passes them
+ # to the differ to append a diff message to the failure message.
+ def fail_with(message, expected=nil, target=nil) # :nodoc:
+ if Array === message && message.length == 3
+ message, expected, target = message[0], message[1], message[2]
+ end
+ unless (differ.nil? || expected.nil? || target.nil?)
+ if expected.is_a?(String)
+ message << "\nDiff:" << self.differ.diff_as_string(target.to_s, expected)
+ elsif !target.is_a?(Proc)
+ message << "\nDiff:" << self.differ.diff_as_object(target, expected)
+ end
+ end
+ Kernel::raise(Spec::Expectations::ExpectationNotMetError.new(message))
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/test/lib/spec/expectations/differs/default.rb b/test/lib/spec/expectations/differs/default.rb
new file mode 100644
index 000000000..e08325728
--- /dev/null
+++ b/test/lib/spec/expectations/differs/default.rb
@@ -0,0 +1,62 @@
+begin
+ require 'rubygems'
+ require 'diff/lcs' #necessary due to loading bug on some machines - not sure why - DaC
+ require 'diff/lcs/hunk'
+rescue LoadError ; raise "You must gem install diff-lcs to use diffing" ; end
+
+require 'pp'
+
+module Spec
+ module Expectations
+ module Differs
+
+ # TODO add colour support
+ # TODO add some rdoc
+ class Default
+ def initialize(format=:unified,context_lines=nil,colour=nil)
+
+ context_lines ||= 3
+ colour ||= false
+
+ @format,@context_lines,@colour = format,context_lines,colour
+ end
+
+ # This is snagged from diff/lcs/ldiff.rb (which is a commandline tool)
+ def diff_as_string(data_old, data_new)
+ data_old = data_old.split(/\n/).map! { |e| e.chomp }
+ data_new = data_new.split(/\n/).map! { |e| e.chomp }
+ output = ""
+ diffs = Diff::LCS.diff(data_old, data_new)
+ return output if diffs.empty?
+ oldhunk = hunk = nil
+ file_length_difference = 0
+ diffs.each do |piece|
+ begin
+ hunk = Diff::LCS::Hunk.new(data_old, data_new, piece, @context_lines,
+ file_length_difference)
+ file_length_difference = hunk.file_length_difference
+ next unless oldhunk
+ # Hunks may overlap, which is why we need to be careful when our
+ # diff includes lines of context. Otherwise, we might print
+ # redundant lines.
+ if (@context_lines > 0) and hunk.overlaps?(oldhunk)
+ hunk.unshift(oldhunk)
+ else
+ output << oldhunk.diff(@format)
+ end
+ ensure
+ oldhunk = hunk
+ output << "\n"
+ end
+ end
+ #Handle the last remaining hunk
+ output << oldhunk.diff(@format) << "\n"
+ end
+
+ def diff_as_object(target,expected)
+ diff_as_string(PP.pp(target,""), PP.pp(expected,""))
+ end
+ end
+ end
+ end
+end
diff --git a/test/lib/spec/expectations/errors.rb b/test/lib/spec/expectations/errors.rb
new file mode 100644
index 000000000..03e81a064
--- /dev/null
+++ b/test/lib/spec/expectations/errors.rb
@@ -0,0 +1,6 @@
+module Spec
+ module Expectations
+ class ExpectationNotMetError < StandardError
+ end
+ end
+end
diff --git a/test/lib/spec/expectations/extensions.rb b/test/lib/spec/expectations/extensions.rb
new file mode 100644
index 000000000..0381dc7f3
--- /dev/null
+++ b/test/lib/spec/expectations/extensions.rb
@@ -0,0 +1,3 @@
+require 'spec/expectations/extensions/object'
+require 'spec/expectations/extensions/proc'
+require 'spec/expectations/extensions/string_and_symbol'
diff --git a/test/lib/spec/expectations/extensions/object.rb b/test/lib/spec/expectations/extensions/object.rb
new file mode 100644
index 000000000..dd5498fdd
--- /dev/null
+++ b/test/lib/spec/expectations/extensions/object.rb
@@ -0,0 +1,109 @@
+module Spec
+ module Expectations
+ # rspec adds #should and #should_not to every Object (and,
+ # implicitly, every Class).
+ module ObjectExpectations
+
+ # :call-seq:
+ # should(matcher)
+ # should == expected
+ # should =~ expected
+ #
+ # receiver.should(matcher)
+ # => Passes if matcher.matches?(receiver)
+ #
+ # receiver.should == expected #any value
+ # => Passes if (receiver == expected)
+ #
+ # receiver.should =~ regexp
+ # => Passes if (receiver =~ regexp)
+ #
+ # See Spec::Matchers for more information about matchers
+ #
+ # == Warning
+ #
+ # NOTE that this does NOT support receiver.should != expected.
+ # Instead, use receiver.should_not == expected
+ def should(matcher=nil, &block)
+ return ExpectationMatcherHandler.handle_matcher(self, matcher, &block) if matcher
+ Should::Should.new(self)
+ end
+
+ # :call-seq:
+ # should_not(matcher)
+ # should_not == expected
+ # should_not =~ expected
+ #
+ # receiver.should_not(matcher)
+ # => Passes unless matcher.matches?(receiver)
+ #
+ # receiver.should_not == expected
+ # => Passes unless (receiver == expected)
+ #
+ # receiver.should_not =~ regexp
+ # => Passes unless (receiver =~ regexp)
+ #
+ # See Spec::Matchers for more information about matchers
+ def should_not(matcher=nil, &block)
+ return NegativeExpectationMatcherHandler.handle_matcher(self, matcher, &block) if matcher
+ should.not
+ end
+
+ deprecated do
+ # Deprecated: use should have(n).items (see Spec::Matchers)
+ # This will be removed in 0.9
+ def should_have(expected)
+ should.have(expected)
+ end
+ alias_method :should_have_exactly, :should_have
+
+ # Deprecated: use should have_at_least(n).items (see Spec::Matchers)
+ # This will be removed in 0.9
+ def should_have_at_least(expected)
+ should.have.at_least(expected)
+ end
+
+ # Deprecated: use should have_at_most(n).items (see Spec::Matchers)
+ # This will be removed in 0.9
+ def should_have_at_most(expected)
+ should.have.at_most(expected)
+ end
+
+ # Deprecated: use should include(expected) (see Spec::Matchers)
+ # This will be removed in 0.9
+ def should_include(expected)
+ should.include(expected)
+ end
+
+ # Deprecated: use should_not include(expected) (see Spec::Matchers)
+ # This will be removed in 0.9
+ def should_not_include(expected)
+ should.not.include(expected)
+ end
+
+ # Deprecated: use should be(expected) (see Spec::Matchers)
+ # This will be removed in 0.9
+ def should_be(expected = :___no_arg)
+ should.be(expected)
+ end
+
+ # Deprecated: use should_not be(expected) (see Spec::Matchers)
+ # This will be removed in 0.9
+ def should_not_be(expected = :___no_arg)
+ should_not.be(expected)
+ end
+ end
+ end
+ end
+end
+
+class Object
+ include Spec::Expectations::ObjectExpectations
+ deprecated do
+ include Spec::Expectations::UnderscoreSugar
+ end
+end
+
+deprecated do
+ Object.handle_underscores_for_rspec!
+end \ No newline at end of file
diff --git a/test/lib/spec/expectations/extensions/proc.rb b/test/lib/spec/expectations/extensions/proc.rb
new file mode 100644
index 000000000..8286708ed
--- /dev/null
+++ b/test/lib/spec/expectations/extensions/proc.rb
@@ -0,0 +1,57 @@
+module Spec
+ module Expectations
+ module ProcExpectations
+ # Given a receiver and a message (Symbol), specifies that the result
+ # of sending that message that receiver should change after
+ # executing the proc.
+ #
+ # lambda { @team.add player }.should_change(@team.players, :size)
+ # lambda { @team.add player }.should_change(@team.players, :size).by(1)
+ # lambda { @team.add player }.should_change(@team.players, :size).to(23)
+ # lambda { @team.add player }.should_change(@team.players, :size).from(22).to(23)
+ #
+ # You can use a block instead of a message and receiver.
+ #
+ # lambda { @team.add player }.should_change{@team.players.size}
+ # lambda { @team.add player }.should_change{@team.players.size}.by(1)
+ # lambda { @team.add player }.should_change{@team.players.size}.to(23)
+ # lambda { @team.add player }.should_change{@team.players.size}.from(22).to(23)
+ def should_change(receiver=nil, message=nil, &block)
+ should.change(receiver, message, &block)
+ end
+
+ # Given a receiver and a message (Symbol), specifies that the result
+ # of sending that message that receiver should NOT change after
+ # executing the proc.
+ #
+ # lambda { @team.add player }.should_not_change(@team.players, :size)
+ #
+ # You can use a block instead of a message and receiver.
+ #
+ # lambda { @team.add player }.should_not_change{@team.players.size}
+ def should_not_change(receiver, message)
+ should.not.change(receiver, message)
+ end
+
+ def should_raise(exception=Exception, message=nil)
+ should.raise(exception, message)
+ end
+
+ def should_not_raise(exception=Exception, message=nil)
+ should.not.raise(exception, message)
+ end
+
+ def should_throw(symbol)
+ should.throw(symbol)
+ end
+
+ def should_not_throw(symbol=:___this_is_a_symbol_that_will_likely_never_occur___)
+ should.not.throw(symbol)
+ end
+ end
+ end
+end
+
+class Proc
+ include Spec::Expectations::ProcExpectations
+end \ No newline at end of file
diff --git a/test/lib/spec/expectations/extensions/string_and_symbol.rb b/test/lib/spec/expectations/extensions/string_and_symbol.rb
new file mode 100644
index 000000000..30f60d4d0
--- /dev/null
+++ b/test/lib/spec/expectations/extensions/string_and_symbol.rb
@@ -0,0 +1,17 @@
+module Spec
+ module Expectations
+ module StringHelpers
+ def starts_with?(prefix)
+ to_s[0..(prefix.length - 1)] == prefix
+ end
+ end
+ end
+end
+
+class String
+ include Spec::Expectations::StringHelpers
+end
+
+class Symbol
+ include Spec::Expectations::StringHelpers
+end \ No newline at end of file
diff --git a/test/lib/spec/expectations/handler.rb b/test/lib/spec/expectations/handler.rb
new file mode 100644
index 000000000..9d3fd1f88
--- /dev/null
+++ b/test/lib/spec/expectations/handler.rb
@@ -0,0 +1,47 @@
+module Spec
+ module Expectations
+
+ module MatcherHandlerHelper
+ def describe(matcher)
+ matcher.respond_to?(:description) ? matcher.description : "[#{matcher.class.name} does not provide a description]"
+ end
+ end
+
+ class ExpectationMatcherHandler
+ class << self
+ include MatcherHandlerHelper
+ def handle_matcher(actual, matcher, &block)
+ unless matcher.nil?
+ match = matcher.matches?(actual, &block)
+ ::Spec::Matchers.generated_description = "should #{describe(matcher)}"
+ Spec::Expectations.fail_with(matcher.failure_message) unless match
+ end
+ end
+ end
+ end
+
+ class NegativeExpectationMatcherHandler
+ class << self
+ include MatcherHandlerHelper
+ def handle_matcher(actual, matcher, &block)
+ unless matcher.nil?
+ unless matcher.respond_to?(:negative_failure_message)
+ Spec::Expectations.fail_with(
+ <<-EOF
+ Matcher does not support should_not.
+ See Spec::Matchers for more information
+ about matchers.
+ EOF
+ )
+ end
+ match = matcher.matches?(actual, &block)
+ ::Spec::Matchers.generated_description = "should not #{describe(matcher)}"
+ Spec::Expectations.fail_with(matcher.negative_failure_message) if match
+ end
+ end
+ end
+ end
+
+ end
+end
+
diff --git a/test/lib/spec/expectations/should.rb b/test/lib/spec/expectations/should.rb
new file mode 100644
index 000000000..f64e6ff78
--- /dev/null
+++ b/test/lib/spec/expectations/should.rb
@@ -0,0 +1,5 @@
+require 'spec/expectations/should/base'
+require 'spec/expectations/should/have'
+require 'spec/expectations/should/not'
+require 'spec/expectations/should/should'
+require 'spec/expectations/should/change'
diff --git a/test/lib/spec/expectations/should/base.rb b/test/lib/spec/expectations/should/base.rb
new file mode 100755
index 000000000..1be4677e8
--- /dev/null
+++ b/test/lib/spec/expectations/should/base.rb
@@ -0,0 +1,64 @@
+module Spec
+ module Expectations
+ module Should
+ class Base
+
+ #== and =~ will stay after the new syntax
+ def ==(expected)
+ __delegate_method_missing_to_target "==", "==", expected
+ end
+
+ def =~(expected)
+ __delegate_method_missing_to_target "=~", "=~", expected
+ end
+
+ #<, <=, >=, > are all implemented in Spec::Matchers::Be
+ # and will be removed with 0.9
+ deprecated do
+ def <(expected)
+ __delegate_method_missing_to_target "<", "<", expected
+ end
+
+ def <=(expected)
+ __delegate_method_missing_to_target "<=", "<=", expected
+ end
+
+ def >=(expected)
+ __delegate_method_missing_to_target ">=", ">=", expected
+ end
+
+ def >(expected)
+ __delegate_method_missing_to_target ">", ">", expected
+ end
+ end
+
+ def default_message(expectation, expected=nil)
+ return "expected #{expected.inspect}, got #{@target.inspect} (using #{expectation})" if expectation == '=='
+ "expected #{expectation} #{expected.inspect}, got #{@target.inspect}" unless expectation == '=='
+ end
+
+ def fail_with_message(message, expected=nil, target=nil)
+ Spec::Expectations.fail_with(message, expected, target)
+ end
+
+ def find_supported_sym(original_sym)
+ ["#{original_sym}?", "#{original_sym}s?"].each do |alternate_sym|
+ return alternate_sym.to_s if @target.respond_to?(alternate_sym.to_s)
+ end
+ end
+
+ deprecated do
+ def method_missing(original_sym, *args, &block)
+ if original_sym.to_s =~ /^not_/
+ return Not.new(@target).__send__(sym, *args, &block)
+ end
+ if original_sym.to_s =~ /^have_/
+ return have.__send__(original_sym.to_s[5..-1].to_sym, *args, &block)
+ end
+ __delegate_method_missing_to_target original_sym, find_supported_sym(original_sym), *args
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/test/lib/spec/expectations/should/change.rb b/test/lib/spec/expectations/should/change.rb
new file mode 100644
index 000000000..98304f1b3
--- /dev/null
+++ b/test/lib/spec/expectations/should/change.rb
@@ -0,0 +1,69 @@
+module Spec
+ module Expectations
+ module Should
+ class Change < Base
+
+ def initialize(target, receiver=nil, message=nil, &block)
+ @block = block
+ @target = target
+ @receiver = receiver
+ @message = message
+ execute_change
+ evaluate_change
+ end
+
+ def execute_change
+ @before_change = @block.nil? ? @receiver.send(@message) : @block.call
+ @target.call
+ @after_change = @block.nil? ? @receiver.send(@message) : @block.call
+ end
+
+ def message
+ @message.nil? ? 'result' : @message
+ end
+
+ def evaluate_change
+ if @before_change == @after_change
+ fail_with_message "#{message} should have changed, but is still #{@after_change.inspect}"
+ end
+ end
+
+ def from(value)
+ if @before_change != value
+ fail_with_message "#{message} should have initially been #{value.inspect}, but was #{@before_change.inspect}"
+ end
+ self
+ end
+
+ def to(value)
+ if @after_change != value
+ fail_with_message "#{message} should have been changed to #{value.inspect}, but is now #{@after_change.inspect}"
+ end
+ self
+ end
+
+ def by(expected_delta)
+ if actual_delta != expected_delta
+ fail_with_message "#{message} should have been changed by #{expected_delta}, but was changed by #{actual_delta}"
+ end
+ self
+ end
+
+ private
+ def actual_delta
+ @after_change - @before_change
+ end
+ end
+
+ class NotChange < Change
+ def evaluate_change
+ if @before_change != @after_change
+ fail_with_message "#{@message} should not have changed, but did change from #{@before_change.inspect} to #{@after_change.inspect}"
+ end
+ end
+ end
+
+ end
+ end
+end
+
diff --git a/test/lib/spec/expectations/should/have.rb b/test/lib/spec/expectations/should/have.rb
new file mode 100644
index 000000000..47ebe81db
--- /dev/null
+++ b/test/lib/spec/expectations/should/have.rb
@@ -0,0 +1,128 @@
+module Spec
+ module Expectations
+ module Should
+ class Have
+ def initialize(target, relativity=:exactly, expected=nil)
+ @target = target
+ init_collection_handler(target, relativity, expected)
+ init_item_handler(target)
+ end
+
+ def init_collection_handler(target, relativity, expected)
+ @collection_handler = CollectionHandler.new(target, relativity, expected)
+ end
+
+ def init_item_handler(target)
+ @item_handler = PositiveItemHandler.new(target)
+ end
+
+ def method_missing(sym, *args)
+ if @collection_handler.wants_to_handle(sym)
+ @collection_handler.handle_message(sym, *args)
+ elsif @item_handler.wants_to_handle(sym)
+ @item_handler.handle_message(sym, *args)
+ else
+ Spec::Expectations.fail_with("target does not respond to #has_#{sym}?")
+ end
+ end
+ end
+
+ class NotHave < Have
+ def init_item_handler(target)
+ @item_handler = NegativeItemHandler.new(target)
+ end
+ end
+
+ class CollectionHandler
+ def initialize(target, relativity=:exactly, expected=nil)
+ @target = target
+ @expected = expected == :no ? 0 : expected
+ @at_least = (relativity == :at_least)
+ @at_most = (relativity == :at_most)
+ end
+
+ def at_least(expected_number=nil)
+ @at_least = true
+ @at_most = false
+ @expected = expected_number == :no ? 0 : expected_number
+ self
+ end
+
+ def at_most(expected_number=nil)
+ @at_least = false
+ @at_most = true
+ @expected = expected_number == :no ? 0 : expected_number
+ self
+ end
+
+ def method_missing(sym, *args)
+ if @target.respond_to?(sym)
+ handle_message(sym, *args)
+ end
+ end
+
+ def wants_to_handle(sym)
+ respond_to?(sym) || @target.respond_to?(sym)
+ end
+
+ def handle_message(sym, *args)
+ return at_least(args[0]) if sym == :at_least
+ return at_most(args[0]) if sym == :at_most
+ Spec::Expectations.fail_with(build_message(sym, args)) unless as_specified?(sym, args)
+ end
+
+ def build_message(sym, args)
+ message = "expected"
+ message += " at least" if @at_least
+ message += " at most" if @at_most
+ message += " #{@expected} #{sym}, got #{actual_size_of(collection(sym, args))}"
+ end
+
+ def as_specified?(sym, args)
+ return actual_size_of(collection(sym, args)) >= @expected if @at_least
+ return actual_size_of(collection(sym, args)) <= @expected if @at_most
+ return actual_size_of(collection(sym, args)) == @expected
+ end
+
+ def collection(sym, args)
+ @target.send(sym, *args)
+ end
+
+ def actual_size_of(collection)
+ return collection.length if collection.respond_to? :length
+ return collection.size if collection.respond_to? :size
+ end
+ end
+
+ class ItemHandler
+ def wants_to_handle(sym)
+ @target.respond_to?("has_#{sym}?")
+ end
+
+ def initialize(target)
+ @target = target
+ end
+
+ def fail_with(message)
+ Spec::Expectations.fail_with(message)
+ end
+ end
+
+ class PositiveItemHandler < ItemHandler
+ def handle_message(sym, *args)
+ fail_with(
+ "expected #has_#{sym}?(#{args.collect{|arg| arg.inspect}.join(', ')}) to return true, got false"
+ ) unless @target.send("has_#{sym}?", *args)
+ end
+ end
+
+ class NegativeItemHandler < ItemHandler
+ def handle_message(sym, *args)
+ fail_with(
+ "expected #has_#{sym}?(#{args.collect{|arg| arg.inspect}.join(', ')}) to return false, got true"
+ ) if @target.send("has_#{sym}?", *args)
+ end
+ end
+ end
+ end
+end
diff --git a/test/lib/spec/expectations/should/not.rb b/test/lib/spec/expectations/should/not.rb
new file mode 100755
index 000000000..5ad530be6
--- /dev/null
+++ b/test/lib/spec/expectations/should/not.rb
@@ -0,0 +1,74 @@
+module Spec
+ module Expectations
+ module Should
+
+ class Not < Base #:nodoc:
+ def initialize(target)
+ @target = target
+ @be_seen = false
+ end
+
+ deprecated do
+ #Gone for 0.9
+ def be(expected = :___no_arg)
+ @be_seen = true
+ return self if (expected == :___no_arg)
+ fail_with_message(default_message("should not be", expected)) if (@target.equal?(expected))
+ end
+
+ #Gone for 0.9
+ def have(expected_number=nil)
+ NotHave.new(@target, :exactly, expected_number)
+ end
+
+ #Gone for 0.9
+ def change(receiver, message)
+ NotChange.new(@target, receiver, message)
+ end
+
+ #Gone for 0.9
+ def raise(exception=Exception, message=nil)
+ begin
+ @target.call
+ rescue exception => e
+ return unless message.nil? || e.message == message || (message.is_a?(Regexp) && e.message =~ message)
+ if e.kind_of?(exception)
+ failure_message = "expected no "
+ failure_message << exception.to_s
+ unless message.nil?
+ failure_message << " with "
+ failure_message << "message matching " if message.is_a?(Regexp)
+ failure_message << message.inspect
+ end
+ failure_message << ", got " << e.inspect
+ fail_with_message(failure_message)
+ end
+ rescue
+ true
+ end
+ end
+
+ #Gone for 0.9
+ def throw(symbol=:___this_is_a_symbol_that_will_likely_never_occur___)
+ begin
+ catch symbol do
+ @target.call
+ return true
+ end
+ fail_with_message("expected #{symbol.inspect} not to be thrown, but it was")
+ rescue NameError
+ true
+ end
+ end
+
+ def __delegate_method_missing_to_target original_sym, actual_sym, *args
+ ::Spec::Matchers.generated_description = "should not #{original_sym} #{args[0].inspect}"
+ return unless @target.__send__(actual_sym, *args)
+ fail_with_message(default_message("not #{original_sym}", args[0]))
+ end
+ end
+ end
+
+ end
+ end
+end
diff --git a/test/lib/spec/expectations/should/should.rb b/test/lib/spec/expectations/should/should.rb
new file mode 100755
index 000000000..cb9f3c4ce
--- /dev/null
+++ b/test/lib/spec/expectations/should/should.rb
@@ -0,0 +1,81 @@
+module Spec
+ module Expectations
+ module Should # :nodoc:
+
+ class Should < Base
+
+ def initialize(target, expectation=nil)
+ @target = target
+ @be_seen = false
+ end
+
+ deprecated do
+ #Gone for 0.9
+ def not
+ Not.new(@target)
+ end
+
+ #Gone for 0.9
+ def be(expected = :___no_arg)
+ @be_seen = true
+ return self if (expected == :___no_arg)
+ if Symbol === expected
+ fail_with_message(default_message("should be", expected)) unless (@target.equal?(expected))
+ else
+ fail_with_message("expected #{expected}, got #{@target} (using .equal?)") unless (@target.equal?(expected))
+ end
+ end
+
+ #Gone for 0.9
+ def have(expected_number=nil)
+ Have.new(@target, :exactly, expected_number)
+ end
+
+ #Gone for 0.9
+ def change(receiver=nil, message=nil, &block)
+ Change.new(@target, receiver, message, &block)
+ end
+
+ #Gone for 0.9
+ def raise(exception=Exception, message=nil)
+ begin
+ @target.call
+ rescue exception => e
+ unless message.nil?
+ if message.is_a?(Regexp)
+ e.message.should =~ message
+ else
+ e.message.should == message
+ end
+ end
+ return
+ rescue => e
+ fail_with_message("expected #{exception}#{message.nil? ? "" : " with #{message.inspect}"}, got #{e.inspect}")
+ end
+ fail_with_message("expected #{exception}#{message.nil? ? "" : " with #{message.inspect}"} but nothing was raised")
+ end
+
+ #Gone for 0.9
+ def throw(symbol)
+ begin
+ catch symbol do
+ @target.call
+ fail_with_message("expected #{symbol.inspect} to be thrown, but nothing was thrown")
+ end
+ rescue NameError => e
+ fail_with_message("expected #{symbol.inspect} to be thrown, got #{e.inspect}")
+ end
+ end
+ end
+
+ private
+ def __delegate_method_missing_to_target(original_sym, actual_sym, *args)
+ ::Spec::Matchers.generated_description = "should #{original_sym} #{args[0].inspect}"
+ return if @target.send(actual_sym, *args)
+ fail_with_message(default_message(original_sym, args[0]), args[0], @target)
+ end
+ end
+
+ end
+ end
+end
diff --git a/test/lib/spec/expectations/sugar.rb b/test/lib/spec/expectations/sugar.rb
new file mode 100644
index 000000000..906111f0e
--- /dev/null
+++ b/test/lib/spec/expectations/sugar.rb
@@ -0,0 +1,47 @@
+deprecated do
+module Spec
+ module Expectations
+ # This module adds syntactic sugar that allows usage of should_* instead of should.*
+ module UnderscoreSugar
+ def handle_underscores_for_rspec! # :nodoc:
+ original_method_missing = instance_method(:method_missing)
+ class_eval do
+ def method_missing(sym, *args, &block)
+ _method_missing(sym, args, block)
+ end
+
+ define_method :_method_missing do |sym, args, block|
+ return original_method_missing.bind(self).call(sym, *args, &block) unless sym.to_s =~ /^should_/
+ if sym.to_s =~ /^should_not_/
+ if __matcher.respond_to?(__strip_should_not(sym))
+ return should_not(__matcher.__send__(__strip_should_not(sym), *args, &block))
+ else
+ return Spec::Expectations::Should::Not.new(self).__send__(__strip_should_not(sym), *args, &block) if sym.to_s =~ /^should_not_/
+ end
+ else
+ if __matcher.respond_to?(__strip_should(sym))
+ return should(__matcher.__send__(__strip_should(sym), *args, &block))
+ else
+ return Spec::Expectations::Should::Should.new(self).__send__(__strip_should(sym), *args, &block)
+ end
+ end
+ end
+
+ def __strip_should(sym) # :nodoc
+ sym.to_s[7..-1]
+ end
+
+ def __strip_should_not(sym) # :nodoc
+ sym.to_s[11..-1]
+ end
+
+ def __matcher
+ @matcher ||= Spec::Matchers::Matcher.new
+ end
+ end
+ end
+ end
+ end
+end
+
+end \ No newline at end of file
diff --git a/test/lib/spec/matchers.rb b/test/lib/spec/matchers.rb
new file mode 100644
index 000000000..9db24d486
--- /dev/null
+++ b/test/lib/spec/matchers.rb
@@ -0,0 +1,160 @@
+require 'spec/deprecated'
+require 'spec/callback'
+require 'spec/matchers/be'
+require 'spec/matchers/be_close'
+require 'spec/matchers/change'
+require 'spec/matchers/eql'
+require 'spec/matchers/equal'
+require 'spec/matchers/has'
+require 'spec/matchers/have'
+require 'spec/matchers/include'
+require 'spec/matchers/match'
+require 'spec/matchers/raise_error'
+require 'spec/matchers/respond_to'
+require 'spec/matchers/satisfy'
+require 'spec/matchers/throw_symbol'
+
+module Spec
+
+ # RSpec ships with a number of useful Expression Matchers. An Expression Matcher
+ # is any object that responds to the following methods:
+ #
+ # matches?(actual)
+ # failure_message
+ # negative_failure_message #optional
+ # description #optional
+ #
+ # See Spec::Expectations to learn how to use these as Expectation Matchers.
+ # See Spec::Mocks to learn how to use them as Mock Argument Constraints.
+ #
+ # == Predicates
+ #
+ # In addition to those Expression Matchers that are defined explicitly, RSpec will
+ # create custom Matchers on the fly for any arbitrary predicate, giving your specs
+ # a much more natural language feel.
+ #
+ # A Ruby predicate is a method that ends with a "?" and returns true or false.
+ # Common examples are +empty?+, +nil?+, and +instance_of?+.
+ #
+ # All you need to do is write +should be_+ followed by the predicate without
+ # the question mark, and RSpec will figure it out from there. For example:
+ #
+ # [].should be_empty => [].empty? #passes
+ # [].should_not be_empty => [].empty? #fails
+ #
+ # In addtion to prefixing the predicate matchers with "be_", you can also use "be_a_"
+ # and "be_an_", making your specs read much more naturally:
+ #
+ # "a string".should be_an_instance_of(String) =>"a string".instance_of?(String) #passes
+ #
+ # 3.should be_a_kind_of(Fixnum) => 3.kind_of?(Numeric) #passes
+ # 3.should be_a_kind_of(Numeric) => 3.kind_of?(Numeric) #passes
+ # 3.should be_an_instance_of(Fixnum) => 3.instance_of?(Fixnum) #passes
+ # 3.should_not be_instance_of(Numeric) => 3.instance_of?(Numeric) #fails
+ #
+ # RSpec will also create custom matchers for predicates like +has_key?+. To
+ # use this feature, just state that the object should have_key(:key) and RSpec will
+ # call has_key?(:key) on the target. For example:
+ #
+ # {:a => "A"}.should have_key(:a) => {:a => "A"}.has_key?(:a) #passes
+ # {:a => "A"}.should have_key(:b) => {:a => "A"}.has_key?(:b) #fails
+ #
+ # You can use this feature to invoke any predicate that begins with "has_", whether it is
+ # part of the Ruby libraries (like +Hash#has_key?+) or a method you wrote on your own class.
+ #
+ # == Custom Expression Matchers
+ #
+ # When you find that none of the stock Expression Matchers provide a natural
+ # feeling expectation, you can very easily write your own.
+ #
+ # For example, imagine that you are writing a game in which players can
+ # be in various zones on a virtual board. To specify that bob should
+ # be in zone 4, you could say:
+ #
+ # bob.current_zone.should eql(Zone.new("4"))
+ #
+ # But you might find it more expressive to say:
+ #
+ # bob.should be_in_zone("4")
+ #
+ # and/or
+ #
+ # bob.should_not be_in_zone("3")
+ #
+ # To do this, you would need to write a class like this:
+ #
+ # class BeInZone
+ # def initialize(expected)
+ # @expected = expected
+ # end
+ # def matches?(actual)
+ # @actual = actual
+ # bob.current_zone.eql?(Zone.new(@expected))
+ # end
+ # def failure_message
+ # "expected #{@actual.inspect} to be in Zone #{@expected}"
+ # end
+ # def negative_failure_message
+ # "expected #{@actual.inspect} not to be in Zone #{@expected}"
+ # end
+ # end
+ #
+ # ... and a method like this:
+ #
+ # def be_in_zone(expected)
+ # BeInZone.new(expected)
+ # end
+ #
+ # And then expose the method to your specs. This is normally done
+ # by including the method and the class in a module, which is then
+ # included in your spec:
+ #
+ # module CustomGameMatchers
+ # class BeInZone
+ # ...
+ # end
+ #
+ # def be_in_zone(expected)
+ # ...
+ # end
+ # end
+ #
+ # context "Player behaviour" do
+ # include CustomGameMatchers
+ # ...
+ # end
+ module Matchers
+
+ class << self
+ callback_events :description_generated
+ def generated_description=(name)
+ notify_callbacks(:description_generated, name)
+ end
+ end
+
+ def method_missing(sym, *args, &block) # :nodoc:
+ return Matchers::Be.new(sym, *args) if sym.starts_with?("be_")
+ return Matchers::Has.new(sym, *args) if sym.starts_with?("have_")
+ super
+ end
+
+ deprecated do
+ # This supports sugar delegating to Matchers
+ class Matcher #:nodoc:
+ include Matchers
+
+ def respond_to?(sym)
+ if sym.to_s[0..2] == "be_"
+ return true
+ else
+ super
+ end
+ end
+ end
+ end
+
+ class MatcherError < StandardError
+ end
+
+ end
+end \ No newline at end of file
diff --git a/test/lib/spec/matchers/be.rb b/test/lib/spec/matchers/be.rb
new file mode 100644
index 000000000..957f23de8
--- /dev/null
+++ b/test/lib/spec/matchers/be.rb
@@ -0,0 +1,161 @@
+module Spec
+ module Matchers
+
+ class Be #:nodoc:
+ def initialize(expected=nil, *args)
+ @expected = parse_expected(expected)
+ @args = args
+ @comparison = ""
+ end
+
+ def matches?(actual)
+ @actual = actual
+ return true if match_or_compare unless handling_predicate?
+ if handling_predicate?
+ begin
+ return @result = actual.__send__(predicate, *@args)
+ rescue => predicate_error
+ # This clause should be empty, but rcov will not report it as covered
+ # unless something (anything) is executed within the clause
+ rcov_error_report = "http://eigenclass.org/hiki.rb?rcov-0.8.0"
+ end
+
+ # This supports should_exist > target.exists? in the old world.
+ # We should consider deprecating that ability as in the new world
+ # you can't write "should exist" unless you have your own custom matcher.
+ begin
+ return @result = actual.__send__(present_tense_predicate, *@args)
+ rescue
+ raise predicate_error
+ end
+ end
+ return false
+ end
+
+ def failure_message
+ return "expected #{@comparison}#{expected}, got #{@actual.inspect}" unless handling_predicate?
+ return "expected #{predicate}#{args_to_s} to return true, got #{@result.inspect}"
+ end
+
+ def negative_failure_message
+ return "expected not #{expected}, got #{@actual.inspect}" unless handling_predicate?
+ return "expected #{predicate}#{args_to_s} to return false, got #{@result.inspect}"
+ end
+
+ def expected
+ return true if @expected == :true
+ return false if @expected == :false
+ return "nil" if @expected == :nil
+ return @expected.inspect
+ end
+
+ def match_or_compare
+ return @actual == true if @expected == :true
+ return @actual == false if @expected == :false
+ return @actual.nil? if @expected == :nil
+ return @actual < @expected if @less_than
+ return @actual <= @expected if @less_than_or_equal
+ return @actual >= @expected if @greater_than_or_equal
+ return @actual > @expected if @greater_than
+ return @actual.equal?(@expected)
+ end
+
+ def <(expected)
+ @less_than = true
+ @comparison = "< "
+ @expected = expected
+ self
+ end
+
+ def <=(expected)
+ @less_than_or_equal = true
+ @comparison = "<= "
+ @expected = expected
+ self
+ end
+
+ def >=(expected)
+ @greater_than_or_equal = true
+ @comparison = ">= "
+ @expected = expected
+ self
+ end
+
+ def >(expected)
+ @greater_than = true
+ @comparison = "> "
+ @expected = expected
+ self
+ end
+
+ def description
+ "be #{@comparison}#{@expected}"
+ end
+
+ private
+ def parse_expected(expected)
+ if Symbol === expected
+ ["be_an_","be_a_","be_"].each do |prefix|
+ @handling_predicate = true
+ return "#{expected.to_s.sub(prefix,"")}".to_sym if expected.starts_with?(prefix)
+ end
+ end
+ return expected
+ end
+
+ def predicate
+ "#{@expected.to_s}?".to_sym
+ end
+
+ def present_tense_predicate
+ "#{@expected.to_s}s?".to_sym
+ end
+
+ def args_to_s
+ return "" if @args.empty?
+ transformed_args = @args.collect{|a| a.inspect}
+ return "(#{transformed_args.join(', ')})"
+ end
+
+ def handling_predicate?
+ return false if [:true, :false, :nil].include?(@expected)
+ return @handling_predicate
+ end
+ end
+
+ # :call-seq:
+ # should be_true
+ # should be_false
+ # should be_nil
+ # should be_arbitrary_predicate(*args)
+ # should_not be_nil
+ # should_not be_arbitrary_predicate(*args)
+ #
+ # Given true, false, or nil, will pass if actual is
+ # true, false or nil (respectively).
+ #
+ # Predicates are any Ruby method that ends in a "?" and returns true or false.
+ # Given be_ followed by arbitrary_predicate (without the "?"), RSpec will match
+ # convert that into a query against the target object.
+ #
+ # The arbitrary_predicate feature will handle any predicate
+ # prefixed with "be_an_" (e.g. be_an_instance_of), "be_a_" (e.g. be_a_kind_of)
+ # or "be_" (e.g. be_empty), letting you choose the prefix that best suits the predicate.
+ #
+ # == Examples
+ #
+ # target.should be_true
+ # target.should be_false
+ # target.should be_nil
+ # target.should_not be_nil
+ #
+ # collection.should be_empty #passes if target.empty?
+ # "this string".should be_an_intance_of(String)
+ #
+ # target.should_not be_empty #passes unless target.empty?
+ # target.should_not be_old_enough(16) #passes unless target.old_enough?(16)
+ def be(*args)
+ Matchers::Be.new(*args)
+ end
+ end
+end
diff --git a/test/lib/spec/matchers/be_close.rb b/test/lib/spec/matchers/be_close.rb
new file mode 100644
index 000000000..b09e3fd2f
--- /dev/null
+++ b/test/lib/spec/matchers/be_close.rb
@@ -0,0 +1,37 @@
+module Spec
+ module Matchers
+
+ class BeClose #:nodoc:
+ def initialize(expected, delta)
+ @expected = expected
+ @delta = delta
+ end
+
+ def matches?(actual)
+ @actual = actual
+ (@actual - @expected).abs < @delta
+ end
+
+ def failure_message
+ "expected #{@expected} +/- (<#{@delta}), got #{@actual}"
+ end
+
+ def description
+ "be close to #{@expected} (+- #{@delta})"
+ end
+ end
+
+ # :call-seq:
+ # should be_close(expected, delta)
+ # should_not be_close(expected, delta)
+ #
+ # Passes if actual == expected +/- delta
+ #
+ # == Example
+ #
+ # result.should be_close(3.0, 0.5)
+ def be_close(expected, delta)
+ Matchers::BeClose.new(expected, delta)
+ end
+ end
+end \ No newline at end of file
diff --git a/test/lib/spec/matchers/change.rb b/test/lib/spec/matchers/change.rb
new file mode 100644
index 000000000..41a718aca
--- /dev/null
+++ b/test/lib/spec/matchers/change.rb
@@ -0,0 +1,120 @@
+module Spec
+ module Matchers
+
+ #Based on patch from Wilson Bilkovich
+ class Change #:nodoc:
+ def initialize(receiver=nil, message=nil, &block)
+ @receiver = receiver
+ @message = message
+ @block = block
+ end
+
+ def matches?(target, &block)
+ if block
+ raise MatcherError.new(<<-EOF
+block passed to should or should_not change must use {} instead of do/end
+EOF
+)
+ end
+ @target = target
+ execute_change
+ return false if @from && (@from != @before)
+ return false if @to && (@to != @after)
+ return (@before + @amount == @after) if @amount
+ return @before != @after
+ end
+
+ def execute_change
+ @before = @block.nil? ? @receiver.send(@message) : @block.call
+ @target.call
+ @after = @block.nil? ? @receiver.send(@message) : @block.call
+ end
+
+ def failure_message
+ if @to
+ "#{result} should have been changed to #{@to.inspect}, but is now #{@after.inspect}"
+ elsif @from
+ "#{result} should have initially been #{@from.inspect}, but was #{@before.inspect}"
+ elsif @amount
+ "#{result} should have been changed by #{@amount.inspect}, but was changed by #{actual_delta.inspect}"
+ else
+ "#{result} should have changed, but is still #{@before.inspect}"
+ end
+ end
+
+ def result
+ @message || "result"
+ end
+
+ def actual_delta
+ @after - @before
+ end
+
+ def negative_failure_message
+ "#{result} should not have changed, but did change from #{@before.inspect} to #{@after.inspect}"
+ end
+
+ def by(amount)
+ @amount = amount
+ self
+ end
+
+ def to(to)
+ @to = to
+ self
+ end
+
+ def from (from)
+ @from = from
+ self
+ end
+ end
+
+ # :call-seq:
+ # should change(receiver, message, &block)
+ # should change(receiver, message, &block).by(value)
+ # should change(receiver, message, &block).from(old).to(new)
+ # should_not change(receiver, message, &block)
+ #
+ # Allows you to specify that a Proc will cause some value to change.
+ #
+ # == Examples
+ #
+ # lambda {
+ # team.add_player(player)
+ # }.should change(roster, :count)
+ #
+ # lambda {
+ # team.add_player(player)
+ # }.should change(roster, :count).by(1)
+ #
+ # string = "string"
+ # lambda {
+ # string.reverse
+ # }.should change { string }.from("string").to("gnirts")
+ #
+ # lambda {
+ # person.happy_birthday
+ # }.should change(person, :birthday).from(32).to(33)
+ #
+ # lambda {
+ # employee.develop_great_new_social_networking_app
+ # }.should change(employee, :title).from("Mail Clerk").to("CEO")
+ #
+ # Evaluates +receiver.message+ or +block+ before and
+ # after it evaluates the c object (generated by the lambdas in the examples above).
+ #
+ # Then compares the values before and after the +receiver.message+ and
+ # evaluates the difference compared to the expected difference.
+ #
+ # == Warning
+ # +should_not+ +change+ only supports the form with no subsequent calls to
+ # +be+, +to+ or +from+.
+ #
+ # blocks passed to +should+ +change+ and +should_not+ +change+
+ # must use the <tt>{}</tt> form (<tt>do/end</tt> is not supported)
+ def change(target=nil, message=nil, &block)
+ Matchers::Change.new(target, message, &block)
+ end
+ end
+end
diff --git a/test/lib/spec/matchers/eql.rb b/test/lib/spec/matchers/eql.rb
new file mode 100644
index 000000000..caca1f7c6
--- /dev/null
+++ b/test/lib/spec/matchers/eql.rb
@@ -0,0 +1,43 @@
+module Spec
+ module Matchers
+
+ class Eql #:nodoc:
+ def initialize(expected)
+ @expected = expected
+ end
+
+ def matches?(actual)
+ @actual = actual
+ @actual.eql?(@expected)
+ end
+
+ def failure_message
+ return "expected #{@expected.inspect}, got #{@actual.inspect} (using .eql?)", @expected, @actual
+ end
+
+ def negative_failure_message
+ return "expected #{@actual.inspect} not to equal #{@expected.inspect} (using .eql?)", @expected, @actual
+ end
+
+ def description
+ "eql #{@expected.inspect}"
+ end
+ end
+
+ # :call-seq:
+ # should eql(expected)
+ # should_not eql(expected)
+ #
+ # Passes if actual and expected are of equal value, but not necessarily the same object.
+ #
+ # See http://www.ruby-doc.org/core/classes/Object.html#M001057 for more information about equality in Ruby.
+ #
+ # == Examples
+ #
+ # 5.should eql(5)
+ # 5.should_not eql(3)
+ def eql(expected)
+ Matchers::Eql.new(expected)
+ end
+ end
+end \ No newline at end of file
diff --git a/test/lib/spec/matchers/equal.rb b/test/lib/spec/matchers/equal.rb
new file mode 100644
index 000000000..e987e73cb
--- /dev/null
+++ b/test/lib/spec/matchers/equal.rb
@@ -0,0 +1,43 @@
+module Spec
+ module Matchers
+
+ class Equal #:nodoc:
+ def initialize(expected)
+ @expected = expected
+ end
+
+ def matches?(actual)
+ @actual = actual
+ @actual.equal?(@expected)
+ end
+
+ def failure_message
+ return "expected #{@expected.inspect}, got #{@actual.inspect} (using .equal?)", @expected, @actual
+ end
+
+ def negative_failure_message
+ return "expected #{@actual.inspect} not to equal #{@expected.inspect} (using .equal?)", @expected, @actual
+ end
+
+ def description
+ "equal #{@expected.inspect}"
+ end
+ end
+
+ # :call-seq:
+ # should equal(expected)
+ # should_not equal(expected)
+ #
+ # Passes if actual and expected are the same object (object identity).
+ #
+ # See http://www.ruby-doc.org/core/classes/Object.html#M001057 for more information about equality in Ruby.
+ #
+ # == Examples
+ #
+ # 5.should equal(5) #Fixnums are equal
+ # "5".should_not equal("5") #Strings that look the same are not the same object
+ def equal(expected)
+ Matchers::Equal.new(expected)
+ end
+ end
+end \ No newline at end of file
diff --git a/test/lib/spec/matchers/has.rb b/test/lib/spec/matchers/has.rb
new file mode 100644
index 000000000..cc5a250b8
--- /dev/null
+++ b/test/lib/spec/matchers/has.rb
@@ -0,0 +1,44 @@
+module Spec
+ module Matchers
+
+ class Has #:nodoc:
+ def initialize(sym, *args)
+ @sym = sym
+ @args = args
+ end
+
+ def matches?(target)
+ @target = target
+ begin
+ return target.send(predicate, *@args)
+ rescue => @error
+ # This clause should be empty, but rcov will not report it as covered
+ # unless something (anything) is executed within the clause
+ rcov_error_report = "http://eigenclass.org/hiki.rb?rcov-0.8.0"
+ end
+ return false
+ end
+
+ def failure_message
+ raise @error if @error
+ "expected ##{predicate}(#{@args[0].inspect}) to return true, got false"
+ end
+
+ def negative_failure_message
+ raise @error if @error
+ "expected ##{predicate}(#{@args[0].inspect}) to return false, got true"
+ end
+
+ def description
+ "have key #{@args[0].inspect}"
+ end
+
+ private
+ def predicate
+ "#{@sym.to_s.sub("have_","has_")}?".to_sym
+ end
+
+ end
+
+ end
+end
diff --git a/test/lib/spec/matchers/have.rb b/test/lib/spec/matchers/have.rb
new file mode 100644
index 000000000..81f9af3e3
--- /dev/null
+++ b/test/lib/spec/matchers/have.rb
@@ -0,0 +1,140 @@
+module Spec
+ module Matchers
+
+ class Have #:nodoc:
+ def initialize(expected, relativity=:exactly)
+ @expected = (expected == :no ? 0 : expected)
+ @relativity = relativity
+ end
+
+ def relativities
+ @relativities ||= {
+ :exactly => "",
+ :at_least => "at least ",
+ :at_most => "at most "
+ }
+ end
+
+ def method_missing(sym, *args, &block)
+ @collection_name = sym
+ @args = args
+ @block = block
+ self
+ end
+
+ def matches?(collection_owner)
+ if collection_owner.respond_to?(collection_name)
+ collection = collection_owner.send(collection_name, *@args, &@block)
+ elsif (collection_owner.respond_to?(:length) || collection_owner.respond_to?(:size))
+ collection = collection_owner
+ else
+ collection_owner.send(collection_name, *@args, &@block)
+ end
+ @actual = collection.length if collection.respond_to?(:length)
+ @actual = collection.size if collection.respond_to?(:size)
+ return @actual >= @expected if @relativity == :at_least
+ return @actual <= @expected if @relativity == :at_most
+ return @actual == @expected
+ end
+
+ def failure_message
+ "expected #{relative_expectation} #{collection_name}, got #{@actual}"
+ end
+
+ def negative_failure_message
+ if @relativity == :exactly
+ return "expected target not to have #{@expected} #{collection_name}, got #{@actual}"
+ elsif @relativity == :at_most
+ return <<-EOF
+Isn't life confusing enough?
+Instead of having to figure out the meaning of this:
+ should_not have_at_most(#{@expected}).#{collection_name}
+We recommend that you use this instead:
+ should have_at_least(#{@expected + 1}).#{collection_name}
+EOF
+ elsif @relativity == :at_least
+ return <<-EOF
+Isn't life confusing enough?
+Instead of having to figure out the meaning of this:
+ should_not have_at_least(#{@expected}).#{collection_name}
+We recommend that you use this instead:
+ should have_at_most(#{@expected - 1}).#{collection_name}
+EOF
+ end
+ end
+
+ def description
+ "have #{relative_expectation} #{collection_name}"
+ end
+
+ private
+ def collection_name
+ @collection_name
+ end
+
+ def relative_expectation
+ "#{relativities[@relativity]}#{@expected}"
+ end
+ end
+
+ # :call-seq:
+ # should have(number).named_collection__or__sugar
+ # should_not have(number).named_collection__or__sugar
+ #
+ # Passes if receiver is a collection with the submitted
+ # number of items OR if the receiver OWNS a collection
+ # with the submitted number of items.
+ #
+ # If the receiver OWNS the collection, you must use the name
+ # of the collection. So if a <tt>Team</tt> instance has a
+ # collection named <tt>#players</tt>, you must use that name
+ # to set the expectation.
+ #
+ # If the receiver IS the collection, you can use any name
+ # you like for <tt>named_collection</tt>. We'd recommend using
+ # either "elements", "members", or "items" as these are all
+ # standard ways of describing the things IN a collection.
+ #
+ # This also works for Strings, letting you set an expectation
+ # about its length
+ #
+ # == Examples
+ #
+ # # Passes if team.players.size == 11
+ # team.should have(11).players
+ #
+ # # Passes if [1,2,3].length == 3
+ # [1,2,3].should have(3).items #"items" is pure sugar
+ #
+ # # Passes if "this string".length == 11
+ # "this string".should have(11).characters #"characters" is pure sugar
+ def have(n)
+ Matchers::Have.new(n)
+ end
+ alias :have_exactly :have
+
+ # :call-seq:
+ # should have_at_least(number).items
+ #
+ # Exactly like have() with >=.
+ #
+ # == Warning
+ #
+ # +should_not+ +have_at_least+ is not supported
+ def have_at_least(n)
+ Matchers::Have.new(n, :at_least)
+ end
+
+ # :call-seq:
+ # should have_at_most(number).items
+ #
+ # Exactly like have() with <=.
+ #
+ # == Warning
+ #
+ # +should_not+ +have_at_most+ is not supported
+ def have_at_most(n)
+ Matchers::Have.new(n, :at_most)
+ end
+ end
+end \ No newline at end of file
diff --git a/test/lib/spec/matchers/include.rb b/test/lib/spec/matchers/include.rb
new file mode 100644
index 000000000..0d387f323
--- /dev/null
+++ b/test/lib/spec/matchers/include.rb
@@ -0,0 +1,50 @@
+module Spec
+ module Matchers
+
+ class Include #:nodoc:
+
+ def initialize(expected)
+ @expected = expected
+ end
+
+ def matches?(actual)
+ @actual = actual
+ actual.include?(@expected)
+ end
+
+ def failure_message
+ _message
+ end
+
+ def negative_failure_message
+ _message("not ")
+ end
+
+ def description
+ "include #{@expected.inspect}"
+ end
+
+ private
+ def _message(maybe_not="")
+ "expected #{@actual.inspect} #{maybe_not}to include #{@expected.inspect}"
+ end
+ end
+
+ # :call-seq:
+ # should include(expected)
+ # should_not include(expected)
+ #
+ # Passes if actual includes expected. This works for
+ # collections and Strings
+ #
+ # == Examples
+ #
+ # [1,2,3].should include(3)
+ # [1,2,3].should_not include(4)
+ # "spread".should include("read")
+ # "spread".should_not include("red")
+ def include(expected)
+ Matchers::Include.new(expected)
+ end
+ end
+end
diff --git a/test/lib/spec/matchers/match.rb b/test/lib/spec/matchers/match.rb
new file mode 100644
index 000000000..61ab52429
--- /dev/null
+++ b/test/lib/spec/matchers/match.rb
@@ -0,0 +1,41 @@
+module Spec
+ module Matchers
+
+ class Match #:nodoc:
+ def initialize(expected)
+ @expected = expected
+ end
+
+ def matches?(actual)
+ @actual = actual
+ return true if actual =~ @expected
+ return false
+ end
+
+ def failure_message
+ return "expected #{@actual.inspect} to match #{@expected.inspect}", @expected, @actual
+ end
+
+ def negative_failure_message
+ return "expected #{@actual.inspect} not to match #{@expected.inspect}", @expected, @actual
+ end
+
+ def description
+ "match #{@expected.inspect}"
+ end
+ end
+
+ # :call-seq:
+ # should match(regexp)
+ # should_not match(regexp)
+ #
+ # Given a Regexp, passes if actual =~ regexp
+ #
+ # == Examples
+ #
+ # email.should match(/^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i)
+ def match(regexp)
+ Matchers::Match.new(regexp)
+ end
+ end
+end
diff --git a/test/lib/spec/matchers/raise_error.rb b/test/lib/spec/matchers/raise_error.rb
new file mode 100644
index 000000000..95e82ad5e
--- /dev/null
+++ b/test/lib/spec/matchers/raise_error.rb
@@ -0,0 +1,100 @@
+module Spec
+ module Matchers
+
+ class RaiseError #:nodoc:
+ def initialize(exception=Exception, message=nil)
+ @expected_error = exception
+ @expected_message = message
+ end
+
+ def matches?(proc)
+ @raised_expected_error = false
+ @raised_other = false
+ begin
+ proc.call
+ rescue @expected_error => @actual_error
+ if @expected_message.nil?
+ @raised_expected_error = true
+ else
+ case @expected_message
+ when Regexp
+ if @expected_message =~ @actual_error.message
+ @raised_expected_error = true
+ else
+ @raised_other = true
+ end
+ else
+ if @actual_error.message == @expected_message
+ @raised_expected_error = true
+ else
+ @raised_other = true
+ end
+ end
+ end
+ rescue => @actual_error
+ @raised_other = true
+ ensure
+ return @raised_expected_error
+ end
+ end
+
+ def failure_message
+ return "expected #{expected_error}#{actual_error}" if @raised_other || !@raised_expected_error
+ end
+
+ def negative_failure_message
+ "expected no #{expected_error}#{actual_error}"
+ end
+
+ def description
+ "raise #{expected_error}"
+ end
+
+ private
+ def expected_error
+ case @expected_message
+ when nil
+ @expected_error
+ when Regexp
+ "#{@expected_error} with message matching #{@expected_message.inspect}"
+ else
+ "#{@expected_error} with #{@expected_message.inspect}"
+ end
+ end
+
+ def actual_error
+ @actual_error.nil? ? " but nothing was raised" : ", got #{@actual_error.inspect}"
+ end
+ end
+
+ # :call-seq:
+ # should raise_error()
+ # should raise_error(NamedError)
+ # should raise_error(NamedError, String)
+ # should raise_error(NamedError, Regexp)
+ # should_not raise_error()
+ # should_not raise_error(NamedError)
+ # should_not raise_error(NamedError, String)
+ # should_not raise_error(NamedError, Regexp)
+ #
+ # With no args, matches if any error is raised.
+ # With a named error, matches only if that specific error is raised.
+ # With a named error and messsage specified as a String, matches only if both match.
+ # With a named error and messsage specified as a Regexp, matches only if both match.
+ #
+ # == Examples
+ #
+ # lambda { do_something_risky }.should raise_error
+ # lambda { do_something_risky }.should raise_error(PoorRiskDecisionError)
+ # lambda { do_something_risky }.should raise_error(PoorRiskDecisionError, "that was too risky")
+ # lambda { do_something_risky }.should raise_error(PoorRiskDecisionError, /oo ri/)
+ #
+ # lambda { do_something_risky }.should_not raise_error
+ # lambda { do_something_risky }.should_not raise_error(PoorRiskDecisionError)
+ # lambda { do_something_risky }.should_not raise_error(PoorRiskDecisionError, "that was too risky")
+ # lambda { do_something_risky }.should_not raise_error(PoorRiskDecisionError, /oo ri/)
+ def raise_error(error=Exception, message=nil)
+ Matchers::RaiseError.new(error, message)
+ end
+ end
+end
diff --git a/test/lib/spec/matchers/respond_to.rb b/test/lib/spec/matchers/respond_to.rb
new file mode 100644
index 000000000..013a36f1d
--- /dev/null
+++ b/test/lib/spec/matchers/respond_to.rb
@@ -0,0 +1,35 @@
+module Spec
+ module Matchers
+
+ class RespondTo #:nodoc:
+ def initialize(sym)
+ @sym = sym
+ end
+
+ def matches?(target)
+ return target.respond_to?(@sym)
+ end
+
+ def failure_message
+ "expected target to respond to #{@sym.inspect}"
+ end
+
+ def negative_failure_message
+ "expected target not to respond to #{@sym.inspect}"
+ end
+
+ def description
+ "respond to ##{@sym.to_s}"
+ end
+ end
+
+ # :call-seq:
+ # should respond_to(:sym)
+ # should_not respond_to(:sym)
+ #
+ # Matches if the target object responds to :sym
+ def respond_to(sym)
+ Matchers::RespondTo.new(sym)
+ end
+ end
+end
diff --git a/test/lib/spec/matchers/satisfy.rb b/test/lib/spec/matchers/satisfy.rb
new file mode 100644
index 000000000..6c0ca95bc
--- /dev/null
+++ b/test/lib/spec/matchers/satisfy.rb
@@ -0,0 +1,47 @@
+module Spec
+ module Matchers
+
+ class Satisfy #:nodoc:
+ def initialize(&block)
+ @block = block
+ end
+
+ def matches?(actual, &block)
+ @block = block if block
+ @actual = actual
+ @block.call(actual)
+ end
+
+ def failure_message
+ "expected #{@actual} to satisfy block"
+ end
+
+ def negative_failure_message
+ "expected #{@actual} not to satisfy block"
+ end
+ end
+
+ # :call-seq:
+ # should satisfy {}
+ # should_not satisfy {}
+ #
+ # Passes if the submitted block returns true. Yields target to the
+ # block.
+ #
+ # Generally speaking, this should be thought of as a last resort when
+ # you can't find any other way to specify the behaviour you wish to
+ # specify.
+ #
+ # If you do find yourself in such a situation, you could always write
+ # a custom matcher, which would likely make your specs more expressive.
+ #
+ # == Examples
+ #
+ # 5.should satisfy { |n|
+ # n > 3
+ # }
+ def satisfy(&block)
+ Matchers::Satisfy.new(&block)
+ end
+ end
+end
diff --git a/test/lib/spec/matchers/throw_symbol.rb b/test/lib/spec/matchers/throw_symbol.rb
new file mode 100644
index 000000000..6732f6fed
--- /dev/null
+++ b/test/lib/spec/matchers/throw_symbol.rb
@@ -0,0 +1,75 @@
+module Spec
+ module Matchers
+
+ class ThrowSymbol #:nodoc:
+ def initialize(expected=nil)
+ @expected = expected
+ end
+
+ def matches?(proc)
+ begin
+ proc.call
+ rescue NameError => e
+ @actual = extract_sym_from_name_error(e)
+ ensure
+ if @expected.nil?
+ return @actual.nil? ? false : true
+ else
+ return @actual == @expected
+ end
+ end
+ end
+
+ def failure_message
+ if @actual
+ "expected #{expected}, got #{@actual.inspect}"
+ else
+ "expected #{expected} but nothing was thrown"
+ end
+ end
+
+ def negative_failure_message
+ if @expected
+ "expected #{expected} not to be thrown"
+ else
+ "expected no Symbol, got :#{@actual}"
+ end
+ end
+
+ def description
+ "throw #{expected}"
+ end
+
+ private
+
+ def expected
+ @expected.nil? ? "a Symbol" : @expected.inspect
+ end
+
+ def extract_sym_from_name_error(error)
+ return "#{error.message.split("`").last.split("'").first}".to_sym
+ end
+ end
+
+ # :call-seq:
+ # should throw_symbol()
+ # should throw_symbol(:sym)
+ # should_not throw_symbol()
+ # should_not throw_symbol(:sym)
+ #
+ # Given a Symbol argument, matches if a proc throws the specified Symbol.
+ #
+ # Given no argument, matches if a proc throws any Symbol.
+ #
+ # == Examples
+ #
+ # lambda { do_something_risky }.should throw_symbol
+ # lambda { do_something_risky }.should throw_symbol(:that_was_risky)
+ #
+ # lambda { do_something_risky }.should_not throw_symbol
+ # lambda { do_something_risky }.should_not throw_symbol(:that_was_risky)
+ def throw_symbol(sym=nil)
+ Matchers::ThrowSymbol.new(sym)
+ end
+ end
+end
diff --git a/test/lib/spec/mocks.rb b/test/lib/spec/mocks.rb
new file mode 100644
index 000000000..d0a5d0299
--- /dev/null
+++ b/test/lib/spec/mocks.rb
@@ -0,0 +1,232 @@
+require 'spec/mocks/methods'
+require 'spec/mocks/mock_handler'
+require 'spec/mocks/mock'
+require 'spec/mocks/argument_expectation'
+require 'spec/mocks/message_expectation'
+require 'spec/mocks/order_group'
+require 'spec/mocks/errors'
+require 'spec/mocks/error_generator'
+require 'spec/mocks/extensions/object'
+
+module Spec
+ # == Mocks and Stubs
+ #
+ # RSpec will create Mock Objects and Stubs for you at runtime, or attach stub/mock behaviour
+ # to any of your real objects (Partial Mock/Stub). Because the underlying implementation
+ # for mocks and stubs is the same, you can intermingle mock and stub
+ # behaviour in either dynamically generated mocks or your pre-existing classes.
+ # There is a semantic difference in how they are created, however,
+ # which can help clarify the role it is playing within a given spec.
+ #
+ # == Mock Objects
+ #
+ # Mocks are objects that allow you to set and verify expectations that they will
+ # receive specific messages during run time. They are very useful for specifying how the subject of
+ # the spec interacts with its collaborators. This approach is widely known as "interaction
+ # testing".
+ #
+ # Mocks are also very powerful as a design tool. As you are
+ # driving the implementation of a given class, Mocks provide an anonymous
+ # collaborator that can change in behaviour as quickly as you can write an expectation in your
+ # spec. This flexibility allows you to design the interface of a collaborator that often
+ # does not yet exist. As the shape of the class being specified becomes more clear, so do the
+ # requirements for its collaborators - often leading to the discovery of new types that are
+ # needed in your system.
+ #
+ # Read Endo-Testing[http://www.mockobjects.com/files/endotesting.pdf] for a much
+ # more in depth description of this process.
+ #
+ # == Stubs
+ #
+ # Stubs are objects that allow you to set "stub" responses to
+ # messages. As Martin Fowler points out on his site,
+ # mocks_arent_stubs[http://www.martinfowler.com/articles/mocksArentStubs.html].
+ # Paraphrasing Fowler's paraphrasing
+ # of Gerard Meszaros: Stubs provide canned responses to messages they might receive in a test, while
+ # mocks allow you to specify and, subsquently, verify that certain messages should be received during
+ # the execution of a test.
+ #
+ # == Partial Mocks/Stubs
+ #
+ # RSpec also supports partial mocking/stubbing, allowing you to add stub/mock behaviour
+ # to instances of your existing classes. This is generally
+ # something to be avoided, because changes to the class can have ripple effects on
+ # seemingly unrelated specs. When specs fail due to these ripple effects, the fact
+ # that some methods are being mocked can make it difficult to understand why a
+ # failure is occurring.
+ #
+ # That said, partials do allow you to expect and
+ # verify interactions with class methods such as +#find+ and +#create+
+ # on Ruby on Rails model classes.
+ #
+ # == Further Reading
+ #
+ # There are many different viewpoints about the meaning of mocks and stubs. If you are interested
+ # in learning more, here is some recommended reading:
+ #
+ # * Mock Objects: http://www.mockobjects.com/
+ # * Endo-Testing: http://www.mockobjects.com/files/endotesting.pdf
+ # * Mock Roles, Not Objects: http://www.mockobjects.com/files/mockrolesnotobjects.pdf
+ # * Test Double Patterns: http://xunitpatterns.com/Test%20Double%20Patterns.html
+ # * Mocks aren't stubs: http://www.martinfowler.com/articles/mocksArentStubs.html
+ #
+ # == Creating a Mock
+ #
+ # You can create a mock in any specification (or setup) using:
+ #
+ # mock(name, options={})
+ #
+ # The optional +options+ argument is a +Hash+. Currently the only supported
+ # option is +:null_object+. Setting this to true instructs the mock to ignore
+ # any messages it hasn’t been told to expect – and quietly return itself. For example:
+ #
+ # mock("person", :null_object => true)
+ #
+ # == Creating a Stub
+ #
+ # You can create a stub in any specification (or setup) using:
+ #
+ # stub(name, stub_methods_and_values_hash)
+ #
+ # For example, if you wanted to create an object that always returns
+ # "More?!?!?!" to "please_sir_may_i_have_some_more" you would do this:
+ #
+ # stub("Mr Sykes", :please_sir_may_i_have_some_more => "More?!?!?!")
+ #
+ # == Creating a Partial Mock
+ #
+ # You don't really "create" a partial mock, you simply add method stubs and/or
+ # mock expectations to existing classes and objects:
+ #
+ # Factory.should_receive(:find).with(id).and_return(value)
+ # obj.stub!(:to_i).and_return(3)
+ # etc ...
+ #
+ # == Expecting Messages
+ #
+ # my_mock.should_receive(:sym)
+ # my_mock.should_not_receive(:sym)
+ #
+ # == Expecting Arguments
+ #
+ # my_mock.should_receive(:sym).with(*args)
+ # my_mock.should_not_receive(:sym).with(*args)
+ #
+ # == Argument Constraints using Expression Matchers
+ #
+ # Arguments that are passed to #with are compared with actual arguments received
+ # using == by default. In cases in which you want to specify things about the arguments
+ # rather than the arguments themselves, you can use any of the Expression Matchers.
+ # They don't all make syntactic sense (they were primarily designed for use with
+ # Spec::Expectations), but you are free to create your own custom Spec::Matchers.
+ #
+ # Spec::Mocks does provide one additional Matcher method named #ducktype.
+ #
+ # In addition, Spec::Mocks adds some keyword Symbols that you can use to
+ # specify certain kinds of arguments:
+ #
+ # my_mock.should_receive(:sym).with(:no_args)
+ # my_mock.should_receive(:sym).with(:any_args)
+ # my_mock.should_receive(:sym).with(1, :numeric, "b") #2nd argument can any type of Numeric
+ # my_mock.should_receive(:sym).with(1, :boolean, "b") #2nd argument can true or false
+ # my_mock.should_receive(:sym).with(1, :string, "b") #2nd argument can be any String
+ # my_mock.should_receive(:sym).with(1, /abc/, "b") #2nd argument can be any String matching the submitted Regexp
+ # my_mock.should_receive(:sym).with(1, :anything, "b") #2nd argument can be anything at all
+ # my_mock.should_receive(:sym).with(1, ducktype(:abs, :div), "b")
+ # #2nd argument can be object that responds to #abs and #div
+ #
+ # == Receive Counts
+ #
+ # my_mock.should_receive(:sym).once
+ # my_mock.should_receive(:sym).twice
+ # my_mock.should_receive(:sym).exactly(n).times
+ # my_mock.should_receive(:sym).at_least(:once)
+ # my_mock.should_receive(:sym).at_least(:twice)
+ # my_mock.should_receive(:sym).at_least(n).times
+ # my_mock.should_receive(:sym).at_most(:once)
+ # my_mock.should_receive(:sym).at_most(:twice)
+ # my_mock.should_receive(:sym).at_most(n).times
+ # my_mock.should_receive(:sym).any_number_of_times
+ #
+ # == Ordering
+ #
+ # my_mock.should_receive(:sym).ordered
+ # my_mock.should_receive(:other_sym).ordered
+ # #This will fail if the messages are received out of order
+ #
+ # == Setting Reponses
+ #
+ # Whether you are setting a mock expectation or a simple stub, you can tell the
+ # object precisely how to respond:
+ #
+ # my_mock.should_receive(:sym).and_return(value)
+ # my_mock.should_receive(:sym).exactly(3).times.and_return(value1, value2, value3)
+ # # returns value1 the first time, value2 the second, etc
+ # my_mock.should_receive(:sym).and_return { ... } #returns value returned by the block
+ # my_mock.should_receive(:sym).and_raise(error)
+ # #error can be an instantiated object or a class
+ # #if it is a class, it must be instantiable with no args
+ # my_mock.should_receive(:sym).and_throw(:sym)
+ # my_mock.should_receive(:sym).and_yield([array,of,values,to,yield])
+ #
+ # Any of these responses can be applied to a stub as well, but stubs do
+ # not support any qualifiers about the message received (i.e. you can't specify arguments
+ # or receive counts):
+ #
+ # my_mock.stub!(:sym).and_return(value)
+ # my_mock.stub!(:sym).and_return(value1, value2, value3)
+ # my_mock.stub!(:sym).and_raise(error)
+ # my_mock.stub!(:sym).and_throw(:sym)
+ # my_mock.stub!(:sym).and_yield([array,of,values,to,yield])
+ #
+ # == Arbitrary Handling
+ #
+ # Once in a while you'll find that the available expectations don't solve the
+ # particular problem you are trying to solve. Imagine that you expect the message
+ # to come with an Array argument that has a specific length, but you don't care
+ # what is in it. You could do this:
+ #
+ # my_mock.should_receive(:sym) do |arg|
+ # arg.should be_an_istance_of(Array)
+ # arg.length.should == 7
+ # end
+ #
+ # Note that this would fail if the number of arguments received was different from
+ # the number of block arguments (in this case 1).
+ #
+ # == Combining Expectation Details
+ #
+ # Combining the message name with specific arguments, receive counts and responses
+ # you can get quite a bit of detail in your expectations:
+ #
+ # my_mock.should_receive(:<<).with("illegal value").once.and_raise(ArgumentError)
+ module Mocks
+ # Shortcut for creating an instance of Spec::Mocks::Mock.
+ def mock(name, options={})
+ Spec::Mocks::Mock.new(name, options)
+ end
+
+ # Shortcut for creating an instance of Spec::Mocks::Mock with
+ # predefined method stubs.
+ #
+ # == Examples
+ #
+ # stub_thing = stub("thing", :a => "A")
+ # stub_thing.a == "A" => true
+ #
+ # stub_person = stub("thing", :name => "Joe", :email => "joe@domain.com")
+ # stub_person.name => "Joe"
+ # stub_person.email => "joe@domain.com"
+ def stub(name, stubs={})
+ object_stub = mock(name)
+ stubs.each { |key, value| object_stub.stub!(key).and_return(value) }
+ object_stub
+ end
+
+ # Shortcut for creating an instance of Spec::Mocks::DuckTypeArgConstraint
+ def duck_type(*args)
+ return Spec::Mocks::DuckTypeArgConstraint.new(*args)
+ end
+
+ end
+end \ No newline at end of file
diff --git a/test/lib/spec/mocks/argument_expectation.rb b/test/lib/spec/mocks/argument_expectation.rb
new file mode 100644
index 000000000..a4870e767
--- /dev/null
+++ b/test/lib/spec/mocks/argument_expectation.rb
@@ -0,0 +1,132 @@
+module Spec
+ module Mocks
+
+ class MatcherConstraint
+ def initialize(matcher)
+ @matcher = matcher
+ end
+
+ def matches?(value)
+ @matcher.matches?(value)
+ end
+ end
+
+ class LiteralArgConstraint
+ def initialize(literal)
+ @literal_value = literal
+ end
+
+ def matches?(value)
+ @literal_value == value
+ end
+ end
+
+ class RegexpArgConstraint
+ def initialize(regexp)
+ @regexp = regexp
+ end
+
+ def matches?(value)
+ return value =~ @regexp unless value.is_a?(Regexp)
+ value == @regexp
+ end
+ end
+
+ class AnyArgConstraint
+ def initialize(ignore)
+ end
+
+ def matches?(value)
+ true
+ end
+ end
+
+ class NumericArgConstraint
+ def initialize(ignore)
+ end
+
+ def matches?(value)
+ value.is_a?(Numeric)
+ end
+ end
+
+ class BooleanArgConstraint
+ def initialize(ignore)
+ end
+
+ def matches?(value)
+ return true if value.is_a?(TrueClass)
+ return true if value.is_a?(FalseClass)
+ false
+ end
+ end
+
+ class StringArgConstraint
+ def initialize(ignore)
+ end
+
+ def matches?(value)
+ value.is_a?(String)
+ end
+ end
+
+ class DuckTypeArgConstraint
+ def initialize(*methods_to_respond_do)
+ @methods_to_respond_do = methods_to_respond_do
+ end
+
+ def matches?(value)
+ @methods_to_respond_do.all? { |sym| value.respond_to?(sym) }
+ end
+ end
+
+ class ArgumentExpectation
+ attr_reader :args
+ @@constraint_classes = Hash.new { |hash, key| LiteralArgConstraint}
+ @@constraint_classes[:anything] = AnyArgConstraint
+ @@constraint_classes[:numeric] = NumericArgConstraint
+ @@constraint_classes[:boolean] = BooleanArgConstraint
+ @@constraint_classes[:string] = StringArgConstraint
+
+ def initialize(args)
+ @args = args
+ if [:any_args] == args then @expected_params = nil
+ elsif [:no_args] == args then @expected_params = []
+ else @expected_params = process_arg_constraints(args)
+ end
+ end
+
+ def process_arg_constraints(constraints)
+ constraints.collect do |constraint|
+ convert_constraint(constraint)
+ end
+ end
+
+ def convert_constraint(constraint)
+ return @@constraint_classes[constraint].new(constraint) if constraint.is_a?(Symbol)
+ return constraint if constraint.is_a?(DuckTypeArgConstraint)
+ return MatcherConstraint.new(constraint) if is_matcher?(constraint)
+ return RegexpArgConstraint.new(constraint) if constraint.is_a?(Regexp)
+ return LiteralArgConstraint.new(constraint)
+ end
+
+ def is_matcher?(obj)
+ return obj.respond_to?(:matches?) && obj.respond_to?(:description)
+ end
+
+ def check_args(args)
+ return true if @expected_params.nil?
+ return true if @expected_params == args
+ return constraints_match?(args)
+ end
+
+ def constraints_match?(args)
+ return false if args.length != @expected_params.length
+ @expected_params.each_index { |i| return false unless @expected_params[i].matches?(args[i]) }
+ return true
+ end
+
+ end
+
+ end
+end
diff --git a/test/lib/spec/mocks/error_generator.rb b/test/lib/spec/mocks/error_generator.rb
new file mode 100644
index 000000000..950864a7a
--- /dev/null
+++ b/test/lib/spec/mocks/error_generator.rb
@@ -0,0 +1,85 @@
+module Spec
+ module Mocks
+ class ErrorGenerator
+ attr_writer :opts
+
+ def initialize(target, name)
+ @target = target
+ @name = name
+ end
+
+ def opts
+ @opts ||= {}
+ end
+
+ def raise_unexpected_message_error(sym, *args)
+ __raise "#{intro} received unexpected message :#{sym}#{arg_message(*args)}"
+ end
+
+ def raise_unexpected_message_args_error(expectation, *args)
+ #this is either :no_args or an Array
+ expected_args = (expectation.expected_args == :no_args ? "(no args)" : format_args(*expectation.expected_args))
+ actual_args = args.empty? ? "(no args)" : format_args(*args)
+ __raise "#{intro} expected #{expectation.sym.inspect} with #{expected_args} but received it with #{actual_args}"
+ end
+
+ def raise_expectation_error(sym, expected_received_count, actual_received_count, *args)
+ __raise "#{intro} expected :#{sym}#{arg_message(*args)} #{count_message(expected_received_count)}, but received it #{count_message(actual_received_count)}"
+ end
+
+ def raise_out_of_order_error(sym)
+ __raise "#{intro} received :#{sym} out of order"
+ end
+
+ def raise_block_failed_error(sym, detail)
+ __raise "#{intro} received :#{sym} but passed block failed with: #{detail}"
+ end
+
+ def raise_missing_block_error(args_to_yield)
+ __raise "#{intro} asked to yield |#{arg_list(*args_to_yield)}| but no block was passed"
+ end
+
+ def raise_wrong_arity_error(args_to_yield, arity)
+ __raise "#{intro} yielded |#{arg_list(*args_to_yield)}| to block with arity of #{arity}"
+ end
+
+ private
+ def intro
+ @name ? "Mock '#{@name}'" : @target.to_s
+ end
+
+ def __raise(message)
+ message = opts[:message] unless opts[:message].nil?
+ Kernel::raise(Spec::Mocks::MockExpectationError, message)
+ end
+
+ def arg_message(*args)
+ " with " + format_args(*args)
+ end
+
+ def format_args(*args)
+ return "(no args)" if args.empty? || args == [:no_args]
+ return "(any args)" if args == [:any_args]
+ "(" + arg_list(*args) + ")"
+ end
+
+ def arg_list(*args)
+ args.collect do |arg|
+ arg.respond_to?(:description) ? arg.description : arg.inspect
+ end.join(", ")
+ end
+
+ def count_message(count)
+ return "at least #{pretty_print(count.abs)}" if count < 0
+ return pretty_print(count)
+ end
+
+ def pretty_print(count)
+ return "once" if count == 1
+ return "twice" if count == 2
+ return "#{count} times"
+ end
+
+ end
+ end
+end \ No newline at end of file
diff --git a/test/lib/spec/mocks/errors.rb b/test/lib/spec/mocks/errors.rb
new file mode 100644
index 000000000..68fdfe006
--- /dev/null
+++ b/test/lib/spec/mocks/errors.rb
@@ -0,0 +1,10 @@
+module Spec
+ module Mocks
+ class MockExpectationError < StandardError
+ end
+
+ class AmbiguousReturnError < StandardError
+ end
+ end
+end
+
diff --git a/test/lib/spec/mocks/extensions/object.rb b/test/lib/spec/mocks/extensions/object.rb
new file mode 100644
index 000000000..4b7531066
--- /dev/null
+++ b/test/lib/spec/mocks/extensions/object.rb
@@ -0,0 +1,3 @@
+class Object
+ include Spec::Mocks::Methods
+end
diff --git a/test/lib/spec/mocks/message_expectation.rb b/test/lib/spec/mocks/message_expectation.rb
new file mode 100644
index 000000000..152e65a47
--- /dev/null
+++ b/test/lib/spec/mocks/message_expectation.rb
@@ -0,0 +1,231 @@
+module Spec
+ module Mocks
+
+ class BaseExpectation
+ attr_reader :sym
+
+ def initialize(error_generator, expectation_ordering, expected_from, sym, method_block, expected_received_count=1, opts={})
+ @error_generator = error_generator
+ @error_generator.opts = opts
+ @expected_from = expected_from
+ @sym = sym
+ @method_block = method_block
+ @return_block = lambda {}
+ @received_count = 0
+ @expected_received_count = expected_received_count
+ @args_expectation = ArgumentExpectation.new([:any_args])
+ @consecutive = false
+ @exception_to_raise = nil
+ @symbol_to_throw = nil
+ @order_group = expectation_ordering
+ @at_least = nil
+ @at_most = nil
+ @args_to_yield = nil
+ end
+
+ def expected_args
+ @args_expectation.args
+ end
+
+ def and_return(*values, &return_block)
+ Kernel::raise AmbiguousReturnError unless @method_block.nil?
+ if values.size == 0
+ value = nil
+ elsif values.size == 1
+ value = values[0]
+ else
+ value = values
+ @consecutive = true
+ @expected_received_count = values.size if @expected_received_count != :any &&
+ @expected_received_count < values.size
+ end
+ @return_block = block_given? ? return_block : lambda { value }
+ end
+
+ def and_raise(exception=Exception)
+ @exception_to_raise = exception
+ end
+
+ def and_throw(symbol)
+ @symbol_to_throw = symbol
+ end
+
+ def and_yield(*args)
+ @args_to_yield = args
+ end
+
+ def matches(sym, args)
+ @sym == sym and @args_expectation.check_args(args)
+ end
+
+ def invoke(args, block)
+ @order_group.handle_order_constraint self
+
+ begin
+ if @exception_to_raise.class == Class
+ @exception_instance_to_raise = @exception_to_raise.new
+ else
+ @exception_instance_to_raise = @exception_to_raise
+ end
+ Kernel::raise @exception_to_raise unless @exception_to_raise.nil?
+ Kernel::throw @symbol_to_throw unless @symbol_to_throw.nil?
+
+ if !@method_block.nil?
+ return invoke_method_block(args)
+ elsif !@args_to_yield.nil?
+ return invoke_with_yield(block)
+ elsif @consecutive
+ return invoke_consecutive_return_block(args, block)
+ else
+ return invoke_return_block(args, block)
+ end
+ ensure
+ @received_count += 1
+ end
+ end
+
+ protected
+
+ def invoke_method_block(args)
+ begin
+ @method_block.call(*args)
+ rescue => detail
+ @error_generator.raise_block_failed_error @sym, detail.message
+ end
+ end
+
+ def invoke_with_yield(block)
+ if block.nil?
+ @error_generator.raise_missing_block_error @args_to_yield
+ end
+ if block.arity > -1 && @args_to_yield.length != block.arity
+ @error_generator.raise_wrong_arity_error @args_to_yield, block.arity
+ end
+ block.call(*@args_to_yield)
+ end
+
+ def invoke_consecutive_return_block(args, block)
+ args << block unless block.nil?
+ value = @return_block.call(*args)
+
+ index = [@received_count, value.size-1].min
+ value[index]
+ end
+
+ def invoke_return_block(args, block)
+ args << block unless block.nil?
+ value = @return_block.call(*args)
+
+ value
+ end
+ end
+
+ class MessageExpectation < BaseExpectation
+
+ def matches_name_but_not_args(sym, args)
+ @sym == sym and not @args_expectation.check_args(args)
+ end
+
+ def verify_messages_received
+ return if @expected_received_count == :any
+ return if (@at_least) && (@received_count >= @expected_received_count)
+ return if (@at_most) && (@received_count <= @expected_received_count)
+ return if @expected_received_count == @received_count
+
+ begin
+ @error_generator.raise_expectation_error(@sym, @expected_received_count, @received_count, *@args_expectation.args)
+ rescue => error
+ error.backtrace.insert(0, @expected_from)
+ Kernel::raise error
+ end
+ end
+
+ def with(*args, &block)
+ @method_block = block if block
+ @args_expectation = ArgumentExpectation.new(args)
+ self
+ end
+
+ def exactly(n)
+ set_expected_received_count :exactly, n
+ self
+ end
+
+ def at_least(n)
+ set_expected_received_count :at_least, n
+ self
+ end
+
+ def at_most(n)
+ set_expected_received_count :at_most, n
+ self
+ end
+
+ def times(&block)
+ @method_block = block if block
+ self
+ end
+
+ def any_number_of_times(&block)
+ @method_block = block if block
+ @expected_received_count = :any
+ self
+ end
+
+ def never
+ @expected_received_count = 0
+ self
+ end
+
+ def once(&block)
+ @method_block = block if block
+ @expected_received_count = 1
+ self
+ end
+
+ def twice(&block)
+ @method_block = block if block
+ @expected_received_count = 2
+ self
+ end
+
+ def ordered(&block)
+ @method_block = block if block
+ @order_group.register(self)
+ @ordered = true
+ self
+ end
+
+ def negative_expectation_for?(sym)
+ return false
+ end
+
+ protected
+ def set_expected_received_count(relativity, n)
+ @at_least = (relativity == :at_least)
+ @at_most = (relativity == :at_most)
+ @expected_received_count = 1 if n == :once
+ @expected_received_count = 2 if n == :twice
+ @expected_received_count = n if n.kind_of? Numeric
+ end
+
+ end
+
+ class NegativeMessageExpectation < MessageExpectation
+ def initialize(message, expectation_ordering, expected_from, sym, method_block)
+ super(message, expectation_ordering, expected_from, sym, method_block, 0)
+ end
+
+ def negative_expectation_for?(sym)
+ return @sym == sym
+ end
+ end
+
+ class MethodStub < BaseExpectation
+ def initialize(message, expectation_ordering, expected_from, sym, method_block)
+ super(message, expectation_ordering, expected_from, sym, method_block, 0)
+ @expected_received_count = :any
+ end
+ end
+ end
+end
diff --git a/test/lib/spec/mocks/methods.rb b/test/lib/spec/mocks/methods.rb
new file mode 100644
index 000000000..a5f102fcf
--- /dev/null
+++ b/test/lib/spec/mocks/methods.rb
@@ -0,0 +1,40 @@
+module Spec
+ module Mocks
+ module Methods
+ def should_receive(sym, opts={}, &block)
+ __mock_handler.add_message_expectation(opts[:expected_from] || caller(1)[0], sym, opts, &block)
+ end
+
+ def should_not_receive(sym, &block)
+ __mock_handler.add_negative_message_expectation(caller(1)[0], sym, &block)
+ end
+
+ def stub!(sym)
+ __mock_handler.add_stub(caller(1)[0], sym)
+ end
+
+ def received_message?(sym, *args, &block) #:nodoc:
+ __mock_handler.received_message?(sym, *args, &block)
+ end
+
+ def __verify #:nodoc:
+ __mock_handler.verify
+ end
+
+ def __reset_mock #:nodoc:
+ __mock_handler.reset
+ end
+
+ def method_missing(sym, *args, &block) #:nodoc:
+ __mock_handler.instance_eval {@messages_received << [sym, args, block]}
+ super(sym, *args, &block)
+ end
+
+ private
+
+ def __mock_handler
+ @mock_handler ||= MockHandler.new(self, @name, @options)
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/test/lib/spec/mocks/mock.rb b/test/lib/spec/mocks/mock.rb
new file mode 100644
index 000000000..68de11ff4
--- /dev/null
+++ b/test/lib/spec/mocks/mock.rb
@@ -0,0 +1,26 @@
+module Spec
+ module Mocks
+ class Mock
+ include Methods
+
+ # Creates a new mock with a +name+ (that will be used in error messages only)
+ # == Options:
+ # * <tt>:null_object</tt> - if true, the mock object acts as a forgiving null object allowing any message to be sent to it.
+ def initialize(name, options={})
+ @name = name
+ @options = options
+ end
+
+ def method_missing(sym, *args, &block)
+ __mock_handler.instance_eval {@messages_received << [sym, args, block]}
+ begin
+ return self if __mock_handler.null_object?
+ super(sym, *args, &block)
+ rescue NoMethodError
+ __mock_handler.raise_unexpected_message_error sym, *args
+ end
+ end
+
+ end
+ end
+end \ No newline at end of file
diff --git a/test/lib/spec/mocks/mock_handler.rb b/test/lib/spec/mocks/mock_handler.rb
new file mode 100644
index 000000000..ef6f97a1c
--- /dev/null
+++ b/test/lib/spec/mocks/mock_handler.rb
@@ -0,0 +1,166 @@
+module Spec
+ module Mocks
+ class MockHandler
+ DEFAULT_OPTIONS = {
+ :null_object => false,
+ :auto_verify => true
+ }
+
+ def initialize(target, name, options={})
+ @target = target
+ @name = name
+ @error_generator = ErrorGenerator.new target, name
+ @expectation_ordering = OrderGroup.new @error_generator
+ @expectations = []
+ @messages_received = []
+ @stubs = []
+ @proxied_methods = []
+ @options = options ? DEFAULT_OPTIONS.dup.merge(options) : DEFAULT_OPTIONS
+ end
+
+ def null_object?
+ @options[:null_object]
+ end
+
+ def add_message_expectation(expected_from, sym, opts={}, &block)
+ __add expected_from, sym, block
+ @expectations << MessageExpectation.new(@error_generator, @expectation_ordering, expected_from, sym, block_given? ? block : nil, 1, opts)
+ @expectations.last
+ end
+
+ def add_negative_message_expectation(expected_from, sym, &block)
+ __add expected_from, sym, block
+ @expectations << NegativeMessageExpectation.new(@error_generator, @expectation_ordering, expected_from, sym, block_given? ? block : nil)
+ @expectations.last
+ end
+
+ def add_stub(expected_from, sym)
+ __add expected_from, sym, nil
+ @stubs.unshift MethodStub.new(@error_generator, @expectation_ordering, expected_from, sym, nil)
+ @stubs.first
+ end
+
+ def verify #:nodoc:
+ begin
+ verify_expectations
+ ensure
+ reset
+ end
+ end
+
+ def reset
+ clear_expectations
+ clear_stubs
+ reset_proxied_methods
+ clear_proxied_methods
+ end
+
+ def received_message?(sym, *args, &block)
+ return true if @messages_received.find {|array| array == [sym, args, block]}
+ return false
+ end
+
+ def has_negative_expectation?(sym)
+ @expectations.detect {|expectation| expectation.negative_expectation_for?(sym)}
+ end
+
+ def message_received(sym, *args, &block)
+ if expectation = find_matching_expectation(sym, *args)
+ expectation.invoke(args, block)
+ elsif stub = find_matching_method_stub(sym)
+ stub.invoke([], block)
+ elsif expectation = find_almost_matching_expectation(sym, *args)
+ raise_unexpected_message_args_error(expectation, *args) unless has_negative_expectation?(sym) unless null_object?
+ else
+ @target.send :method_missing, sym, *args, &block
+ end
+ end
+
+ def raise_unexpected_message_args_error(expectation, *args)
+ @error_generator.raise_unexpected_message_args_error expectation, *args
+ end
+
+ def raise_unexpected_message_error(sym, *args)
+ @error_generator.raise_unexpected_message_error sym, *args
+ end
+
+ private
+
+ def __add(expected_from, sym, block)
+ # TODO - this is the only reference in the 'spec/mocks' to the Runner
+ current_spec = Runner::Specification.current
+ current_spec.after_teardown {verify} if current_spec && @options[:auto_verify]
+ define_expected_method(sym)
+ end
+
+ def define_expected_method(sym)
+ if target_responds_to?(sym) && !@proxied_methods.include?(sym)
+ @proxied_methods << sym
+ metaclass.__send__(:alias_method, munge(sym), sym)
+ end
+
+ metaclass_eval(<<-EOF, __FILE__, __LINE__)
+ def #{sym}(*args, &block)
+ __mock_handler.message_received :#{sym}, *args, &block
+ end
+ EOF
+ end
+
+ def target_responds_to?(sym)
+ return @target.send(munge(:respond_to?),sym) if @already_proxied_respond_to
+ return @already_proxied_respond_to = true if sym == :respond_to?
+ return @target.respond_to?(sym)
+ end
+
+ def munge(sym)
+ "proxied_by_rspec__#{sym.to_s}".to_sym
+ end
+
+ def clear_expectations
+ @expectations.clear
+ end
+
+ def clear_stubs
+ @stubs.clear
+ end
+
+ def clear_proxied_methods
+ @proxied_methods.clear
+ end
+
+ def metaclass_eval(str, filename, lineno)
+ metaclass.class_eval(str, filename, lineno)
+ end
+
+ def metaclass
+ (class << @target; self; end)
+ end
+
+ def verify_expectations
+ @expectations.each do |expectation|
+ expectation.verify_messages_received
+ end
+ end
+
+ def reset_proxied_methods
+ @proxied_methods.each do |sym|
+ metaclass.__send__(:alias_method, sym, munge(sym))
+ metaclass.__send__(:undef_method, munge(sym))
+ end
+ end
+
+ def find_matching_expectation(sym, *args)
+ @expectations.find {|expectation| expectation.matches(sym, args)}
+ end
+
+ def find_almost_matching_expectation(sym, *args)
+ @expectations.find {|expectation| expectation.matches_name_but_not_args(sym, args)}
+ end
+
+ def find_matching_method_stub(sym)
+ @stubs.find {|stub| stub.matches(sym, [])}
+ end
+
+ end
+ end
+end \ No newline at end of file
diff --git a/test/lib/spec/mocks/order_group.rb b/test/lib/spec/mocks/order_group.rb
new file mode 100644
index 000000000..9983207eb
--- /dev/null
+++ b/test/lib/spec/mocks/order_group.rb
@@ -0,0 +1,29 @@
+module Spec
+ module Mocks
+ class OrderGroup
+ def initialize error_generator
+ @error_generator = error_generator
+ @ordering = Array.new
+ end
+
+ def register(expectation)
+ @ordering << expectation
+ end
+
+ def ready_for?(expectation)
+ return @ordering.first == expectation
+ end
+
+ def consume
+ @ordering.shift
+ end
+
+ def handle_order_constraint expectation
+ return unless @ordering.include? expectation
+ return consume if ready_for?(expectation)
+ @error_generator.raise_out_of_order_error expectation.sym
+ end
+
+ end
+ end
+end
diff --git a/test/lib/spec/rake/spectask.rb b/test/lib/spec/rake/spectask.rb
new file mode 100644
index 000000000..5c9b365c1
--- /dev/null
+++ b/test/lib/spec/rake/spectask.rb
@@ -0,0 +1,173 @@
+#!/usr/bin/env ruby
+
+# Define a task library for running RSpec contexts.
+
+require 'rake'
+require 'rake/tasklib'
+
+module Spec
+ module Rake
+
+ # A Rake task that runs a set of RSpec contexts.
+ #
+ # Example:
+ #
+ # Spec::Rake::SpecTask.new do |t|
+ # t.warning = true
+ # t.rcov = true
+ # end
+ #
+ # This will create a task that can be run with:
+ #
+ # rake spec
+ #
+ class SpecTask < ::Rake::TaskLib
+
+ # Name of spec task. (default is :spec)
+ attr_accessor :name
+
+ # Array of directories to be added to $LOAD_PATH before running the
+ # specs. Defaults to ['<the absolute path to RSpec's lib directory>']
+ attr_accessor :libs
+
+ # If true, requests that the specs be run with the warning flag set.
+ # E.g. warning=true implies "ruby -w" used to run the specs. Defaults to false.
+ attr_accessor :warning
+
+ # Glob pattern to match spec files. (default is 'spec/**/*_spec.rb')
+ attr_accessor :pattern
+
+ # Array of commandline options to pass to RSpec. Defaults to [].
+ attr_accessor :spec_opts
+
+ # Where RSpec's output is written. Defaults to STDOUT.
+ attr_accessor :out
+
+ # Whether or not to use RCov (default is false)
+ # See http://eigenclass.org/hiki.rb?rcov
+ attr_accessor :rcov
+
+ # Array of commandline options to pass to RCov. Defaults to ['--exclude', 'lib\/spec,bin\/spec'].
+ # Ignored if rcov=false
+ attr_accessor :rcov_opts
+
+ # Directory where the RCov report is written. Defaults to "coverage"
+ # Ignored if rcov=false
+ attr_accessor :rcov_dir
+
+ # Array of commandline options to pass to ruby. Defaults to [].
+ attr_accessor :ruby_opts
+
+ # Whether or not to fail Rake when an error occurs (typically when specs fail).
+ # Defaults to true.
+ attr_accessor :fail_on_error
+
+ # A message to print to stdout when there are failures.
+ attr_accessor :failure_message
+
+ # Explicitly define the list of spec files to be included in a
+ # spec. +list+ is expected to be an array of file names (a
+ # FileList is acceptable). If both +pattern+ and +spec_files+ are
+ # used, then the list of spec files is the union of the two.
+ def spec_files=(list)
+ @spec_files = list
+ end
+
+ # Create a specing task.
+ def initialize(name=:spec)
+ @name = name
+ @libs = [File.expand_path(File.dirname(__FILE__) + '/../../../lib')]
+ @pattern = nil
+ @spec_files = nil
+ @spec_opts = []
+ @warning = false
+ @ruby_opts = []
+ @out = nil
+ @fail_on_error = true
+ @rcov = false
+ @rcov_opts = ['--exclude', 'lib\/spec,bin\/spec,config\/boot.rb']
+ @rcov_dir = "coverage"
+
+ yield self if block_given?
+ @pattern = 'spec/**/*_spec.rb' if @pattern.nil? && @spec_files.nil?
+ define
+ end
+
+ def define
+ spec_script = File.expand_path(File.dirname(__FILE__) + '/../../../bin/spec')
+
+ lib_path = @libs.join(File::PATH_SEPARATOR)
+ actual_name = Hash === name ? name.keys.first : name
+ unless ::Rake.application.last_comment
+ desc "Run RSpec for #{actual_name}" + (@rcov ? " using RCov" : "")
+ end
+ task @name do
+ RakeFileUtils.verbose(@verbose) do
+ ruby_opts = @ruby_opts.clone
+ ruby_opts.push( "-I\"#{lib_path}\"" )
+ ruby_opts.push( "-S rcov" ) if @rcov
+ ruby_opts.push( "-w" ) if @warning
+
+ redirect = @out.nil? ? "" : " > \"#{@out}\""
+
+ unless spec_file_list.empty?
+ # ruby [ruby_opts] -Ilib -S rcov [rcov_opts] bin/spec -- [spec_opts] examples
+ # or
+ # ruby [ruby_opts] -Ilib bin/spec [spec_opts] examples
+ begin
+ ruby(
+ ruby_opts.join(" ") + " " +
+ rcov_option_list +
+ (@rcov ? %[ -o "#{@rcov_dir}" ] : "") +
+ '"' + spec_script + '"' + " " +
+ (@rcov ? "-- " : "") +
+ spec_file_list.collect { |fn| %["#{fn}"] }.join(' ') + " " +
+ spec_option_list + " " +
+ redirect
+ )
+ rescue => e
+ puts @failure_message if @failure_message
+ raise e if @fail_on_error
+ end
+ end
+ end
+ end
+
+ if @rcov
+ desc "Remove rcov products for #{actual_name}"
+ task paste("clobber_", actual_name) do
+ rm_r @rcov_dir rescue nil
+ end
+
+ clobber_task = paste("clobber_", actual_name)
+ task :clobber => [clobber_task]
+
+ task actual_name => clobber_task
+ end
+ self
+ end
+
+ def rcov_option_list # :nodoc:
+ return "" unless @rcov
+ ENV['RCOVOPTS'] || @rcov_opts.join(" ") || ""
+ end
+
+ def spec_option_list # :nodoc:
+ ENV['RSPECOPTS'] || @spec_opts.join(" ") || ""
+ end
+
+ def spec_file_list # :nodoc:
+ if ENV['SPEC']
+ FileList[ ENV['SPEC'] ]
+ else
+ result = []
+ result += @spec_files.to_a if @spec_files
+ result += FileList[ @pattern ].to_a if @pattern
+ FileList[result]
+ end
+ end
+
+ end
+ end
+end
+
diff --git a/test/lib/spec/rake/verify_rcov.rb b/test/lib/spec/rake/verify_rcov.rb
new file mode 100644
index 000000000..a05153e99
--- /dev/null
+++ b/test/lib/spec/rake/verify_rcov.rb
@@ -0,0 +1,47 @@
+module RCov
+ # A task that can verify that the RCov coverage doesn't
+ # drop below a certain threshold. It should be run after
+ # running Spec::Rake::SpecTask.
+ class VerifyTask < Rake::TaskLib
+ # Name of the task. Defaults to :verify_rcov
+ attr_accessor :name
+
+ # Path to the index.html file generated by RCov, which
+ # is the file containing the total coverage.
+ # Defaults to 'coverage/index.html'
+ attr_accessor :index_html
+
+ # Whether or not to output details. Defaults to true.
+ attr_accessor :verbose
+
+ # The threshold value (in percent) for coverage. If the
+ # actual coverage is not equal to this value, the task will raise an
+ # exception.
+ attr_accessor :threshold
+
+ def initialize(name=:verify_rcov)
+ @name = name
+ @index_html = 'coverage/index.html'
+ @verbose = true
+ yield self if block_given?
+ raise "Threshold must be set" if @threshold.nil?
+ define
+ end
+
+ def define
+ desc "Verify that rcov coverage is at least #{threshold}%"
+ task @name do
+ total_coverage = nil
+ File.open(index_html).each_line do |line|
+ if line =~ /<tt.*>(\d+\.\d+)%<\/tt>&nbsp;<\/td>/
+ total_coverage = eval($1)
+ break
+ end
+ end
+ puts "Coverage: #{total_coverage}% (threshold: #{threshold}%)" if verbose
+ raise "Coverage must be at least #{threshold}% but was #{total_coverage}%" if total_coverage < threshold
+ raise "Coverage has increased above the threshold of #{threshold}% to #{total_coverage}%. You should update your threshold value." if total_coverage > threshold
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/test/lib/spec/runner.rb b/test/lib/spec/runner.rb
new file mode 100644
index 000000000..976802bd1
--- /dev/null
+++ b/test/lib/spec/runner.rb
@@ -0,0 +1,132 @@
+require 'spec/runner/formatter'
+require 'spec/runner/context'
+require 'spec/runner/context_eval'
+require 'spec/runner/specification'
+require 'spec/runner/execution_context'
+require 'spec/runner/context_runner'
+require 'spec/runner/option_parser'
+require 'spec/runner/command_line'
+require 'spec/runner/drb_command_line'
+require 'spec/runner/backtrace_tweaker'
+require 'spec/runner/reporter'
+require 'spec/runner/spec_matcher'
+require 'spec/runner/extensions/object'
+require 'spec/runner/extensions/kernel'
+require 'spec/runner/spec_should_raise_handler'
+require 'spec/runner/spec_parser'
+
+module Spec
+ # == Contexts and Specifications
+ #
+ # Rather than expressing examples in classes, RSpec uses a custom domain specific language to express
+ # examples using contexts and specifications.
+ #
+ # A context is the equivalent of a fixture in xUnit-speak. It is a metaphor for the context
+ # in which you will run your executable example - a set of known objects in a known starting state.
+ #
+ # context "A new account" do
+ #
+ # setup do
+ # @account = Account.new
+ # end
+ #
+ # specify "should have a balance of $0" do
+ # @account.balance.should_eql Money.new(0, :dollars)
+ # end
+ #
+ # end
+ #
+ # We use the setup block to set up the context (given), and then the specify method to
+ # hold the example code that expresses the event (when) and the expected outcome (then).
+ #
+ # == Helper Methods
+ #
+ # A primary goal of RSpec is to keep the examples clear. We therefore prefer
+ # less indirection than you might see in xUnit examples and in well factored, DRY production code. We feel
+ # that duplication is OK if removing it makes it harder to understand an example without
+ # having to look elsewhere to understand its context.
+ #
+ # That said, RSpec does support some level of encapsulating common code in helper
+ # methods that can exist within a context or within an included module.
+ #
+ # == Setup and Teardown
+ #
+ # You can use setup, teardown, context_setup and context_teardown within a context:
+ #
+ # context "..." do
+ # context_setup do
+ # ...
+ # end
+ #
+ # setup do
+ # ...
+ # end
+ #
+ # specify "number one" do
+ # ...
+ # end
+ #
+ # specify "number two" do
+ # ...
+ # end
+ #
+ # teardown do
+ # ...
+ # end
+ #
+ # context_teardown do
+ # ...
+ # end
+ #
+ # end
+ #
+ # The <tt>setup</tt> block will run before each of the specs, once for each spec. Likewise,
+ # the <tt>teardown</tt> block will run after each of the specs.
+ #
+ # It is also possible to specify a <tt>context_setup</tt> and <tt>context_teardown</tt>
+ # block that will run only once for each context, respectively before the first <code>setup</code>
+ # and after the last <code>teardown</code>. The use of these is generally discouraged, because it
+ # introduces dependencies between the specs. Still, it might prove useful for very expensive operations
+ # if you know what you are doing.
+ #
+ # == Local helper methods
+ #
+ # You can include local helper methods by simply expressing them within a context:
+ #
+ # context "..." do
+ #
+ # specify "..." do
+ # helper_method
+ # end
+ #
+ # def helper_method
+ # ...
+ # end
+ #
+ # end
+ #
+ # == Included helper methods
+ #
+ # You can include helper methods in multiple contexts by expressing them within
+ # a module, and then including that module in your context:
+ #
+ # module AccountExampleHelperMethods
+ # def helper_method
+ # ...
+ # end
+ # end
+ #
+ # context "A new account" do
+ # include AccountExampleHelperMethods
+ # setup do
+ # @account = Account.new
+ # end
+ #
+ # specify "should have a balance of $0" do
+ # helper_method
+ # @account.balance.should eql(Money.new(0, :dollars))
+ # end
+ # end
+ module Runner
+ end
+end
diff --git a/test/lib/spec/runner/backtrace_tweaker.rb b/test/lib/spec/runner/backtrace_tweaker.rb
new file mode 100644
index 000000000..7300b36b8
--- /dev/null
+++ b/test/lib/spec/runner/backtrace_tweaker.rb
@@ -0,0 +1,55 @@
+module Spec
+ module Runner
+ class BacktraceTweaker
+ def clean_up_double_slashes(line)
+ line.gsub!('//','/')
+ end
+ end
+
+ class NoisyBacktraceTweaker < BacktraceTweaker
+ def tweak_backtrace(error, spec_name)
+ return if error.backtrace.nil?
+ error.backtrace.each do |line|
+ clean_up_double_slashes(line)
+ end
+ end
+ end
+
+ # Tweaks raised Exceptions to mask noisy (unneeded) parts of the backtrace
+ class QuietBacktraceTweaker < BacktraceTweaker
+ unless defined?(IGNORE_PATTERNS)
+ root_dir = File.expand_path(File.join(__FILE__, '..', '..', '..', '..'))
+ spec_files = Dir["#{root_dir}/lib/spec/*"].map do |path|
+ subpath = path[root_dir.length..-1]
+ /#{subpath}/
+ end
+ IGNORE_PATTERNS = spec_files + [
+ /\/lib\/ruby\//,
+ /bin\/spec:/,
+ /bin\/rcov:/,
+ /lib\/rspec_on_rails/,
+ /vendor\/rails/,
+ # TextMate's Ruby and RSpec plugins
+ /Ruby\.tmbundle\/Support\/tmruby.rb:/,
+ /RSpec\.tmbundle\/Support\/lib/,
+ /temp_textmate\./
+ ]
+ end
+
+ def tweak_backtrace(error, spec_name)
+ return if error.backtrace.nil?
+ error.backtrace.collect! do |line|
+ clean_up_double_slashes(line)
+ IGNORE_PATTERNS.each do |ignore|
+ if line =~ ignore
+ line = nil
+ break
+ end
+ end
+ line
+ end
+ error.backtrace.compact!
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/test/lib/spec/runner/command_line.rb b/test/lib/spec/runner/command_line.rb
new file mode 100644
index 000000000..db928ad9b
--- /dev/null
+++ b/test/lib/spec/runner/command_line.rb
@@ -0,0 +1,34 @@
+require 'spec/runner/option_parser'
+
+module Spec
+ module Runner
+ # Facade to run specs without having to fork a new ruby process (using `spec ...`)
+ class CommandLine
+ # Runs specs. +argv+ is the commandline args as per the spec commandline API, +err+
+ # and +out+ are the streams output will be written to. +exit+ tells whether or
+ # not a system exit should be called after the specs are run and
+ # +warn_if_no_files+ tells whether or not a warning (the help message)
+ # should be printed to +err+ in case no files are specified.
+ def self.run(argv, err, out, exit=true, warn_if_no_files=true)
+ old_context_runner = defined?($context_runner) ? $context_runner : nil
+ $context_runner = OptionParser.new.create_context_runner(argv, err, out, warn_if_no_files)
+ return if $context_runner.nil? # This is the case if we use --drb
+
+ # If ARGV is a glob, it will actually each over each one of the matching files.
+ argv.each do |file_or_dir|
+ if File.directory?(file_or_dir)
+ Dir["#{file_or_dir}/**/*.rb"].each do |file|
+ load file
+ end
+ elsif File.file?(file_or_dir)
+ load file_or_dir
+ else
+ raise "File or directory not found: #{file_or_dir}"
+ end
+ end
+ $context_runner.run(exit)
+ $context_runner = old_context_runner
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/test/lib/spec/runner/context.rb b/test/lib/spec/runner/context.rb
new file mode 100644
index 000000000..3155e169a
--- /dev/null
+++ b/test/lib/spec/runner/context.rb
@@ -0,0 +1,154 @@
+module Spec
+ module Runner
+ class ContextEvalModule < Module
+ end
+ class Context
+ module InstanceMethods
+ def initialize(description, &context_block)
+ @description = description
+
+ @context_eval_module = ContextEvalModule.new
+ @context_eval_module.extend ContextEval::ModuleMethods
+ @context_eval_module.include ContextEval::InstanceMethods
+ before_context_eval
+ @context_eval_module.class_eval(&context_block)
+ end
+
+ def before_context_eval
+ end
+
+ def inherit_context_eval_module_from(klass)
+ @context_eval_module.inherit klass
+ end
+ alias :inherit :inherit_context_eval_module_from
+
+ def include(mod)
+ @context_eval_module.include(mod)
+ end
+
+ def run(reporter, dry_run=false)
+ reporter.add_context(@description)
+ prepare_execution_context_class
+ errors = run_context_setup(reporter, dry_run)
+
+ specifications.each do |specification|
+ specification_execution_context = execution_context(specification)
+ specification_execution_context.copy_instance_variables_from(@once_only_execution_context_instance, []) unless context_setup_block.nil?
+ specification.run(reporter, setup_block, teardown_block, dry_run, specification_execution_context)
+ end unless errors.length > 0
+
+ run_context_teardown(reporter, dry_run)
+ end
+
+ def number_of_specs
+ specifications.length
+ end
+
+ def matches?(full_description)
+ matcher ||= SpecMatcher.new(@description)
+ specifications.each do |spec|
+ return true if spec.matches?(matcher, full_description)
+ end
+ return false
+ end
+
+ def run_single_spec(full_description)
+ return if @description == full_description
+ matcher = SpecMatcher.new(@description)
+ specifications.reject! do |spec|
+ !spec.matches?(matcher, full_description)
+ end
+ end
+
+ def methods
+ my_methods = super
+ my_methods |= @context_eval_module.methods
+ my_methods
+ end
+
+ protected
+
+ def method_missing(*args)
+ @context_eval_module.method_missing(*args)
+ end
+
+ def context_setup_block
+ @context_eval_module.send :context_setup_block
+ end
+
+ def context_teardown_block
+ @context_eval_module.send :context_teardown_block
+ end
+
+ def specifications
+ @context_eval_module.send :specifications
+ end
+
+ def setup_block
+ @context_eval_module.send :setup_block
+ end
+
+ def teardown_block
+ @context_eval_module.send :teardown_block
+ end
+
+ def prepare_execution_context_class
+ weave_in_context_modules
+ execution_context_class
+ end
+
+ def weave_in_context_modules
+ mods = context_modules
+ context_eval_module = @context_eval_module
+ execution_context_class.class_eval do
+ include context_eval_module
+ mods.each do |mod|
+ include mod
+ end
+ end
+ end
+
+ def context_modules
+ @context_eval_module.send :context_modules
+ end
+
+ def execution_context_class
+ @context_eval_module.send :execution_context_class
+ end
+
+ def execution_context specification
+ execution_context_class.new(specification)
+ end
+
+ def run_context_setup(reporter, dry_run)
+ errors = []
+ unless dry_run
+ begin
+ @once_only_execution_context_instance = execution_context(nil)
+ @once_only_execution_context_instance.instance_eval(&context_setup_block)
+ rescue => e
+ errors << e
+ location = "context_setup"
+ reporter.spec_finished(location, e, location) if reporter
+ end
+ end
+ errors
+ end
+
+ def run_context_teardown(reporter, dry_run)
+ unless dry_run
+ begin
+ @once_only_execution_context_instance ||= execution_context(nil)
+ @once_only_execution_context_instance.instance_eval(&context_teardown_block)
+ rescue => e
+ location = "context_teardown"
+ reporter.spec_finished(location, e, location) if reporter
+ end
+ end
+ end
+
+ end
+ include InstanceMethods
+ end
+ end
+end
diff --git a/test/lib/spec/runner/context_eval.rb b/test/lib/spec/runner/context_eval.rb
new file mode 100644
index 000000000..2cee8f1cd
--- /dev/null
+++ b/test/lib/spec/runner/context_eval.rb
@@ -0,0 +1,142 @@
+module Spec
+ module Runner
+ module ContextEval
+ module ModuleMethods
+ def inherit(klass)
+ @context_superclass = klass
+ derive_execution_context_class_from_context_superclass
+ end
+
+ def include(mod)
+ context_modules << mod
+ mod.send :included, self
+ end
+
+ def context_setup(&block)
+ context_setup_parts << block
+ end
+
+ def context_teardown(&block)
+ context_teardown_parts << block
+ end
+
+ def setup(&block)
+ setup_parts << block
+ end
+
+ def teardown(&block)
+ teardown_parts << block
+ end
+
+ def specify(spec_name=:__generate_description, opts={}, &block)
+ specifications << Specification.new(spec_name, opts, &block)
+ end
+
+ def methods
+ my_methods = super
+ my_methods |= context_superclass.methods
+ my_methods
+ end
+
+ protected
+
+ def method_missing(method_name, *args)
+ if context_superclass.respond_to?(method_name)
+ return execution_context_class.send(method_name, *args)
+ end
+ super
+ end
+
+ private
+
+ def context_setup_block
+ parts = context_setup_parts.dup
+ add_context_superclass_method(:context_setup, parts)
+ create_block_from_parts(parts)
+ end
+
+ def context_teardown_block
+ parts = context_teardown_parts.dup
+ add_context_superclass_method(:context_teardown, parts)
+ create_block_from_parts(parts)
+ end
+
+ def setup_block
+ parts = setup_parts.dup
+ add_context_superclass_method(:setup, parts)
+ create_block_from_parts(parts)
+ end
+
+ def teardown_block
+ parts = teardown_parts.dup
+ add_context_superclass_method(:teardown, parts)
+ create_block_from_parts(parts)
+ end
+
+ def execution_context_class
+ @execution_context_class ||= derive_execution_context_class_from_context_superclass
+ end
+
+ def derive_execution_context_class_from_context_superclass
+ @execution_context_class = Class.new(context_superclass)
+ @execution_context_class.class_eval do
+ include ::Spec::Runner::ExecutionContext::InstanceMethods
+ end
+ end
+
+ def context_superclass
+ @context_superclass ||= Object
+ end
+
+ def context_modules
+ @context_modules ||= [Spec::Matchers, Spec::Mocks]
+ end
+
+ def specifications
+ @specifications ||= []
+ end
+
+ def context_setup_parts
+ @context_setup_parts ||= []
+ end
+
+ def context_teardown_parts
+ @context_teardown_parts ||= []
+ end
+
+ def setup_parts
+ @setup_parts ||= []
+ end
+
+ def teardown_parts
+ @teardown_parts ||= []
+ end
+
+ def add_context_superclass_method sym, parts
+ superclass_method = begin
+ context_superclass.instance_method(sym)
+ rescue
+ nil
+ end
+ parts.unshift superclass_method if superclass_method
+ end
+
+ def create_block_from_parts(parts)
+ proc do
+ parts.each do |part|
+ if part.is_a?(UnboundMethod)
+ part.bind(self).call
+ else
+ instance_eval(&part)
+ end
+ end
+ end
+ end
+ end
+
+ module InstanceMethods
+ end
+
+ end
+ end
+end
diff --git a/test/lib/spec/runner/context_runner.rb b/test/lib/spec/runner/context_runner.rb
new file mode 100644
index 000000000..0a4d7e6e9
--- /dev/null
+++ b/test/lib/spec/runner/context_runner.rb
@@ -0,0 +1,55 @@
+module Spec
+ module Runner
+ class ContextRunner
+
+ def initialize(options)
+ @contexts = []
+ @options = options
+ end
+
+ def add_context(context)
+ return unless spec_description.nil? || context.matches?(spec_description)
+ context.run_single_spec(spec_description) if context.matches?(spec_description)
+ @contexts << context
+ end
+
+ # Runs all contexts and returns the number of failures.
+ def run(exit_when_done)
+ @options.reporter.start(number_of_specs)
+ begin
+ @contexts.each do |context|
+ context.run(@options.reporter, @options.dry_run)
+ end
+ rescue Interrupt
+ ensure
+ @options.reporter.end
+ end
+ failure_count = @options.reporter.dump
+
+ if(failure_count == 0 && !@options.heckle_runner.nil?)
+ heckle_runner = @options.heckle_runner
+ @options.heckle_runner = nil
+ context_runner = self.class.new(@options)
+ context_runner.instance_variable_set(:@contexts, @contexts)
+ heckle_runner.heckle_with(context_runner)
+ end
+
+ if(exit_when_done)
+ exit_code = (failure_count == 0) ? 0 : 1
+ exit(exit_code)
+ end
+ failure_count
+ end
+
+ def number_of_specs
+ @contexts.inject(0) {|sum, context| sum + context.number_of_specs}
+ end
+
+ private
+ def spec_description
+ @options.spec_name
+ end
+
+ end
+ end
+end
diff --git a/test/lib/spec/runner/drb_command_line.rb b/test/lib/spec/runner/drb_command_line.rb
new file mode 100644
index 000000000..d4c7d937d
--- /dev/null
+++ b/test/lib/spec/runner/drb_command_line.rb
@@ -0,0 +1,21 @@
+require "drb/drb"
+
+module Spec
+ module Runner
+ # Facade to run specs by connecting to a DRB server
+ class DrbCommandLine
+ # Runs specs on a DRB server. Note that this API is similar to that of
+ # CommandLine - making it possible for clients to use both interchangeably.
+ def self.run(argv, stderr, stdout, exit=true, warn_if_no_files=true)
+ begin
+ DRb.start_service
+ rails_spec_server = DRbObject.new_with_uri("druby://localhost:8989")
+ rails_spec_server.run(argv, stderr, stdout)
+ rescue DRb::DRbConnError
+ stderr.puts "No server is running"
+ exit 1 if exit
+ end
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/test/lib/spec/runner/execution_context.rb b/test/lib/spec/runner/execution_context.rb
new file mode 100644
index 000000000..484c55830
--- /dev/null
+++ b/test/lib/spec/runner/execution_context.rb
@@ -0,0 +1,17 @@
+module Spec
+ module Runner
+ class ExecutionContext
+ module InstanceMethods
+ def initialize(*args) #:nodoc:
+ #necessary for RSpec's own specs
+ end
+
+ def violated(message="")
+ raise Spec::Expectations::ExpectationNotMetError.new(message)
+ end
+
+ end
+ include InstanceMethods
+ end
+ end
+end \ No newline at end of file
diff --git a/test/lib/spec/runner/extensions/kernel.rb b/test/lib/spec/runner/extensions/kernel.rb
new file mode 100644
index 000000000..f060ec859
--- /dev/null
+++ b/test/lib/spec/runner/extensions/kernel.rb
@@ -0,0 +1,17 @@
+module Kernel
+ def context(name, &block)
+ context = Spec::Runner::Context.new(name, &block)
+ context_runner.add_context(context)
+ end
+
+private
+
+ def context_runner
+ # TODO: Figure out a better way to get this considered "covered" and keep this statement on multiple lines
+ unless $context_runner; \
+ $context_runner = ::Spec::Runner::OptionParser.new.create_context_runner(ARGV.dup, STDERR, STDOUT, false); \
+ at_exit { $context_runner.run(false) }; \
+ end
+ $context_runner
+ end
+end
diff --git a/test/lib/spec/runner/extensions/object.rb b/test/lib/spec/runner/extensions/object.rb
new file mode 100644
index 000000000..49745352f
--- /dev/null
+++ b/test/lib/spec/runner/extensions/object.rb
@@ -0,0 +1,32 @@
+# The following copyright applies to Object#copy_instance_variables_from,
+# which we borrowed from active_support.
+#
+# Copyright (c) 2004 David Heinemeier Hansson
+#
+# 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.
+class Object
+ # From active_support
+ def copy_instance_variables_from(object, exclude = []) # :nodoc:
+ exclude += object.protected_instance_variables if object.respond_to? :protected_instance_variables
+
+ instance_variables = object.instance_variables - exclude.map { |name| name.to_s }
+ instance_variables.each { |name| instance_variable_set(name, object.instance_variable_get(name)) }
+ end
+end
diff --git a/test/lib/spec/runner/formatter.rb b/test/lib/spec/runner/formatter.rb
new file mode 100644
index 000000000..f62e81733
--- /dev/null
+++ b/test/lib/spec/runner/formatter.rb
@@ -0,0 +1,5 @@
+require 'spec/runner/formatter/base_text_formatter'
+require 'spec/runner/formatter/progress_bar_formatter'
+require 'spec/runner/formatter/rdoc_formatter'
+require 'spec/runner/formatter/specdoc_formatter'
+require 'spec/runner/formatter/html_formatter'
diff --git a/test/lib/spec/runner/formatter/base_text_formatter.rb b/test/lib/spec/runner/formatter/base_text_formatter.rb
new file mode 100644
index 000000000..31d1c3132
--- /dev/null
+++ b/test/lib/spec/runner/formatter/base_text_formatter.rb
@@ -0,0 +1,118 @@
+module Spec
+ module Runner
+ module Formatter
+ # Baseclass for text-based formatters. Can in fact be used for
+ # non-text based ones too - just ignore the +output+ constructor
+ # argument.
+ class BaseTextFormatter
+ def initialize(output, dry_run=false, colour=false)
+ @output = output
+ @dry_run = dry_run
+ @colour = colour
+ begin ; require 'Win32/Console/ANSI' if @colour && PLATFORM =~ /win32/ ; rescue LoadError ; raise "You must gem install win32console to use colour on Windows" ; end
+ end
+
+ # This method is invoked before any specs are run, right after
+ # they have all been collected. This can be useful for special
+ # formatters that need to provide progress on feedback (graphical ones)
+ #
+ # This method will only be invoked once, and the next one to be invoked
+ # is #add_context
+ def start(spec_count)
+ end
+
+ # This method is invoked at the beginning of the execution of each context.
+ # +name+ is the name of the context and +first+ is true if it is the
+ # first context - otherwise it's false.
+ #
+ # The next method to be invoked after this is #spec_started
+ def add_context(name, first)
+ end
+
+ # This method is invoked right before a spec is executed.
+ # The next method to be invoked after this one is one of #spec_failed
+ # or #spec_passed.
+ def spec_started(name)
+ end
+
+ # This method is invoked when a spec fails, i.e. an exception occurred
+ # inside it (such as a failed should or other exception). +name+ is the name
+ # of the specification. +counter+ is the sequence number of the failure
+ # (starting at 1) and +failure+ is the associated Failure object.
+ def spec_failed(name, counter, failure)
+ end
+
+ # This method is invoked when a spec passes. +name+ is the name of the
+ # specification.
+ def spec_passed(name)
+ end
+
+ # This method is invoked after all of the specs have executed. The next method
+ # to be invoked after this one is #dump_failure (once for each failed spec),
+ def start_dump
+ end
+
+ # Dumps detailed information about a spec failure.
+ # This method is invoked for each failed spec after all specs have run. +counter+ is the sequence number
+ # of the associated spec. +failure+ is a Failure object, which contains detailed
+ # information about the failure.
+ def dump_failure(counter, failure)
+ @output.puts
+ @output.puts "#{counter.to_s})"
+ if(failure.expectation_not_met?)
+ @output.puts red(failure.header)
+ @output.puts red(failure.exception.message)
+ else
+ @output.puts magenta(failure.header)
+ @output.puts magenta(failure.exception.message)
+ end
+ @output.puts format_backtrace(failure.exception.backtrace)
+ STDOUT.flush
+ end
+
+ # This method is invoked at the very end.
+ def dump_summary(duration, spec_count, failure_count)
+ return if @dry_run
+ @output.puts
+ @output.puts "Finished in #{duration} seconds"
+ @output.puts
+ summary = "#{spec_count} specification#{'s' unless spec_count == 1}, #{failure_count} failure#{'s' unless failure_count == 1}"
+ if failure_count == 0
+ @output.puts green(summary)
+ else
+ @output.puts red(summary)
+ end
+ end
+
+ def format_backtrace(backtrace)
+ return "" if backtrace.nil?
+ backtrace.map { |line| backtrace_line(line) }.join("\n")
+ end
+
+ protected
+
+ def backtrace_line(line)
+ line.sub(/\A([^:]+:\d+)$/, '\\1:')
+ end
+
+ def colour(text, colour_code)
+ return text unless @colour && output_to_tty?
+ "#{colour_code}#{text}\e[0m"
+ end
+
+ def output_to_tty?
+ begin
+ @output == Kernel || @output.tty?
+ rescue NoMethodError
+ false
+ end
+ end
+
+ def red(text); colour(text, "\e[31m"); end
+ def green(text); colour(text, "\e[32m"); end
+ def magenta(text); colour(text, "\e[35m"); end
+
+ end
+ end
+ end
+end
diff --git a/test/lib/spec/runner/formatter/html_formatter.rb b/test/lib/spec/runner/formatter/html_formatter.rb
new file mode 100644
index 000000000..13b796581
--- /dev/null
+++ b/test/lib/spec/runner/formatter/html_formatter.rb
@@ -0,0 +1,219 @@
+module Spec
+ module Runner
+ module Formatter
+ class HtmlFormatter < BaseTextFormatter
+ attr_reader :current_spec_number, :current_context_number
+
+ def initialize(output, dry_run=false, colour=false)
+ super
+ @current_spec_number = 0
+ @current_context_number = 0
+ end
+
+ def start(spec_count)
+ @spec_count = spec_count
+
+ @output.puts HEADER_1
+ @output.puts extra_header_content unless extra_header_content.nil?
+ @output.puts HEADER_2
+ STDOUT.flush
+ end
+
+ def add_context(name, first)
+ @current_context_number += 1
+ unless first
+ @output.puts " </dl>"
+ @output.puts "</div>"
+ end
+ @output.puts "<div class=\"context\">"
+ @output.puts " <dl>"
+ @output.puts " <dt id=\"context_#{@current_context_number}\">#{name}</dt>"
+ STDOUT.flush
+ end
+
+ def start_dump
+ @output.puts " </dl>"
+ @output.puts "</div>"
+ STDOUT.flush
+ end
+
+ def spec_started(name)
+ @current_spec_number += 1
+ STDOUT.flush
+ end
+
+ def spec_passed(name)
+ move_progress
+ @output.puts " <dd class=\"spec passed\"><span class=\"passed_spec_name\">#{escape(name)}</span></dd>"
+ STDOUT.flush
+ end
+
+ def spec_failed(name, counter, failure)
+ @output.puts " <script type=\"text/javascript\">makeRed('header');</script>"
+ @output.puts " <script type=\"text/javascript\">makeRed('context_#{@current_context_number}');</script>"
+ move_progress
+ @output.puts " <dd class=\"spec failed\">"
+ @output.puts " <span class=\"failed_spec_name\">#{escape(name)}</span>"
+ @output.puts " <div class=\"failure\" id=\"failure_#{counter}\">"
+ @output.puts " <div class=\"message\"><pre>#{escape(failure.exception.message)}</pre></div>" unless failure.exception.nil?
+ @output.puts " <div class=\"backtrace\"><pre>#{format_backtrace(failure.exception.backtrace)}</pre></div>" unless failure.exception.nil?
+ @output.puts extra_failure_content unless extra_failure_content.nil?
+ @output.puts " </div>"
+ @output.puts " </dd>"
+ STDOUT.flush
+ end
+
+ # Override this method if you wish to output extra HTML in the header
+ #
+ def extra_header_content
+ end
+
+ # Override this method if you wish to output extra HTML for a failed spec. For example, you
+ # could output links to images or other files produced during the specs.
+ #
+ def extra_failure_content
+ end
+
+ def move_progress
+ percent_done = @spec_count == 0 ? 100.0 : (@current_spec_number.to_f / @spec_count.to_f * 1000).to_i / 10.0
+ @output.puts " <script type=\"text/javascript\">moveProgressBar('#{percent_done}');</script>"
+ end
+
+ def escape(string)
+ string.gsub(/&/n, '&amp;').gsub(/\"/n, '&quot;').gsub(/>/n, '&gt;').gsub(/</n, '&lt;')
+ end
+
+ def dump_failure(counter, failure)
+ end
+
+ def dump_summary(duration, spec_count, failure_count)
+ if @dry_run
+ totals = "This was a dry-run"
+ else
+ totals = "#{spec_count} specification#{'s' unless spec_count == 1}, #{failure_count} failure#{'s' unless failure_count == 1}"
+ end
+ @output.puts "<script type=\"text/javascript\">document.getElementById('duration').innerHTML = \"Finished in <strong>#{duration} seconds</strong>\";</script>"
+ @output.puts "<script type=\"text/javascript\">document.getElementById('totals').innerHTML = \"#{totals}\";</script>"
+ @output.puts "</div>"
+ @output.puts "</body>"
+ @output.puts "</html>"
+ STDOUT.flush
+ end
+
+ HEADER_1 = <<-EOF
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!DOCTYPE html
+ PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+ <title>RSpec results</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
+ <meta http-equiv="Expires" content="-1" />
+ <meta http-equiv="Pragma" content="no-cache" />
+EOF
+
+ HEADER_2 = <<-EOF
+ <script type="text/javascript">
+ function moveProgressBar(percentDone) {
+ document.getElementById("header").style.width = percentDone +"%";
+ }
+ function makeRed(element_id) {
+ document.getElementById(element_id).style.background = '#C40D0D';
+ }
+ </script>
+ <style type="text/css">
+ body {
+ margin: 0; padding: 0;
+ background: #fff;
+ }
+
+ #header {
+ background: #65C400; color: #fff;
+ }
+
+ h1 {
+ margin: 0 0 10px;
+ padding: 10px;
+ font: bold 18px "Lucida Grande", Helvetica, sans-serif;
+ }
+
+ #summary {
+ margin: 0; padding: 5px 10px;
+ font: bold 10px "Lucida Grande", Helvetica, sans-serif;
+ text-align: right;
+ position: absolute;
+ top: 0px;
+ right: 0px;
+ }
+
+ #summary p {
+ margin: 0 0 2px;
+ }
+
+ #summary #totals {
+ font-size: 14px;
+ }
+
+ .context {
+ margin: 0 10px 5px;
+ background: #fff;
+ }
+
+ dl {
+ margin: 0; padding: 0 0 5px;
+ font: normal 11px "Lucida Grande", Helvetica, sans-serif;
+ }
+
+ dt {
+ padding: 3px;
+ background: #65C400;
+ color: #fff;
+ font-weight: bold;
+ }
+
+ dd {
+ margin: 5px 0 5px 5px;
+ padding: 3px 3px 3px 18px;
+ }
+
+ dd.spec.passed {
+ border-left: 5px solid #65C400;
+ border-bottom: 1px solid #65C400;
+ background: #DBFFB4; color: #3D7700;
+ }
+
+ dd.spec.failed {
+ border-left: 5px solid #C20000;
+ border-bottom: 1px solid #C20000;
+ color: #C20000; background: #FFFBD3;
+ }
+
+ div.backtrace {
+ color: #000;
+ font-size: 12px;
+ }
+
+ a {
+ color: #BE5C00;
+ }
+ </style>
+</head>
+<body>
+
+<div id="header">
+ <h1>RSpec Results</h1>
+
+ <div id="summary">
+ <p id="duration">&nbsp;</p>
+ <p id="totals">&nbsp;</p>
+ </div>
+</div>
+
+<div id="results">
+EOF
+ end
+ end
+ end
+end
diff --git a/test/lib/spec/runner/formatter/progress_bar_formatter.rb b/test/lib/spec/runner/formatter/progress_bar_formatter.rb
new file mode 100644
index 000000000..fe519d4d8
--- /dev/null
+++ b/test/lib/spec/runner/formatter/progress_bar_formatter.rb
@@ -0,0 +1,27 @@
+module Spec
+ module Runner
+ module Formatter
+ class ProgressBarFormatter < BaseTextFormatter
+ def add_context(name, first)
+ @output.puts if first
+ STDOUT.flush
+ end
+
+ def spec_failed(name, counter, failure)
+ @output.print failure.expectation_not_met? ? red('F') : magenta('F')
+ STDOUT.flush
+ end
+
+ def spec_passed(name)
+ @output.print green('.')
+ STDOUT.flush
+ end
+
+ def start_dump
+ @output.puts
+ STDOUT.flush
+ end
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/test/lib/spec/runner/formatter/rdoc_formatter.rb b/test/lib/spec/runner/formatter/rdoc_formatter.rb
new file mode 100644
index 000000000..eae55c3ea
--- /dev/null
+++ b/test/lib/spec/runner/formatter/rdoc_formatter.rb
@@ -0,0 +1,22 @@
+module Spec
+ module Runner
+ module Formatter
+ class RdocFormatter < BaseTextFormatter
+ def add_context(name, first)
+ @output.print "# #{name}\n"
+ STDOUT.flush
+ end
+
+ def spec_passed(name)
+ @output.print "# * #{name}\n"
+ STDOUT.flush
+ end
+
+ def spec_failed(name, counter, failure)
+ @output.print "# * #{name} [#{counter} - FAILED]\n"
+ STDOUT.flush
+ end
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/test/lib/spec/runner/formatter/specdoc_formatter.rb b/test/lib/spec/runner/formatter/specdoc_formatter.rb
new file mode 100644
index 000000000..67b4312bf
--- /dev/null
+++ b/test/lib/spec/runner/formatter/specdoc_formatter.rb
@@ -0,0 +1,23 @@
+module Spec
+ module Runner
+ module Formatter
+ class SpecdocFormatter < BaseTextFormatter
+ def add_context(name, first)
+ @output.puts
+ @output.puts name
+ STDOUT.flush
+ end
+
+ def spec_failed(name, counter, failure)
+ @output.puts failure.expectation_not_met? ? red("- #{name} (FAILED - #{counter})") : magenta("- #{name} (ERROR - #{counter})")
+ STDOUT.flush
+ end
+
+ def spec_passed(name)
+ @output.print green("- #{name}\n")
+ STDOUT.flush
+ end
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/test/lib/spec/runner/heckle_runner.rb b/test/lib/spec/runner/heckle_runner.rb
new file mode 100644
index 000000000..fd36389de
--- /dev/null
+++ b/test/lib/spec/runner/heckle_runner.rb
@@ -0,0 +1,71 @@
+begin
+ require 'rubygems'
+ require 'heckle'
+rescue LoadError ; raise "You must gem install heckle to use --heckle" ; end
+
+module Spec
+ module Runner
+ # Creates a new Heckler configured to heckle all methods in the classes
+ # whose name matches +filter+
+ class HeckleRunner
+ def initialize(filter, heckle_class=Heckler)
+ @filter = filter
+ @heckle_class = heckle_class
+ end
+
+ # Runs all the contexts held by +context_runner+ once for each of the
+ # methods in the matched classes.
+ def heckle_with(context_runner)
+ if @filter =~ /(.*)[#\.](.*)/
+ heckle_method($1, $2)
+ else
+ heckle_class_or_module(@filter)
+ end
+ end
+
+ def heckle_method(class_name, method_name)
+ verify_constant(class_name)
+ heckle = @heckle_class.new(class_name, method_name, context_runner)
+ heckle.validate
+ end
+
+ def heckle_class_or_module(class_or_module_name)
+ verify_constant(class_or_module_name)
+ pattern = /^#{class_or_module_name}/
+ classes = []
+ ObjectSpace.each_object(Class) do |klass|
+ classes << klass if klass.name =~ pattern
+ end
+
+ classes.each do |klass|
+ klass.instance_methods(false).each do |method_name|
+ heckle = @heckle_class.new(klass.name, method_name, context_runner)
+ heckle.validate
+ end
+ end
+ end
+
+ def verify_constant(name)
+ begin
+ # This is defined in Heckle
+ name.to_class
+ rescue
+ raise "Heckling failed - \"#{name}\" is not a known class or module"
+ end
+ end
+ end
+
+ #Supports Heckle 1.2 and prior (earlier versions used Heckle::Base)
+ class Heckler < (Heckle.const_defined?(:Base) ? Heckle::Base : Heckle)
+ def initialize(klass_name, method_name, context_runner)
+ super(klass_name, method_name)
+ @context_runner = context_runner
+ end
+
+ def tests_pass?
+ failure_count = @context_runner.run(false)
+ failure_count == 0
+ end
+ end
+ end
+end
diff --git a/test/lib/spec/runner/heckle_runner_win.rb b/test/lib/spec/runner/heckle_runner_win.rb
new file mode 100644
index 000000000..031386599
--- /dev/null
+++ b/test/lib/spec/runner/heckle_runner_win.rb
@@ -0,0 +1,10 @@
+module Spec
+ module Runner
+ # Dummy implementation for Windows that just fails (Heckle is not supported on Windows)
+ class HeckleRunner
+ def initialize(filter)
+ raise "Heckle not supported on Windows"
+ end
+ end
+ end
+end
diff --git a/test/lib/spec/runner/option_parser.rb b/test/lib/spec/runner/option_parser.rb
new file mode 100644
index 000000000..38725d848
--- /dev/null
+++ b/test/lib/spec/runner/option_parser.rb
@@ -0,0 +1,224 @@
+require 'ostruct'
+require 'optparse'
+require 'spec/runner/spec_parser'
+require 'spec/runner/formatter'
+require 'spec/runner/backtrace_tweaker'
+require 'spec/runner/reporter'
+require 'spec/runner/context_runner'
+
+module Spec
+ module Runner
+ class OptionParser
+ def initialize
+ @spec_parser = SpecParser.new
+ @file_factory = File
+ end
+
+ def create_context_runner(args, err, out, warn_if_no_files)
+ options = parse(args, err, out, warn_if_no_files)
+ # Some exit points in parse (--generate-options, --drb) don't return the options,
+ # but hand over control. In that case we don't want to continue.
+ return nil unless options.is_a?(OpenStruct)
+
+ formatter = options.formatter_type.new(options.out, options.dry_run, options.colour)
+ options.reporter = Reporter.new(formatter, options.backtrace_tweaker)
+
+ # this doesn't really belong here.
+ # it should, but the way things are coupled, it doesn't
+ if options.differ_class
+ Spec::Expectations.differ = options.differ_class.new(options.diff_format, options.context_lines, options.colour)
+ end
+
+ unless options.generate
+ ContextRunner.new(options)
+ end
+ end
+
+ def parse(args, err, out, warn_if_no_files)
+ options_file = nil
+ args_copy = args.dup
+ options = OpenStruct.new
+ options.out = (out == STDOUT ? Kernel : out)
+ options.formatter_type = Formatter::ProgressBarFormatter
+ options.backtrace_tweaker = QuietBacktraceTweaker.new
+ options.spec_name = nil
+
+ opts = ::OptionParser.new do |opts|
+ opts.banner = "Usage: spec [options] (FILE|DIRECTORY|GLOB)+"
+ opts.separator ""
+
+ opts.on("-D", "--diff [FORMAT]", "Show diff of objects that are expected to be equal when they are not",
+ "Builtin formats: unified|u|context|c",
+ "You can also specify a custom differ class",
+ "(in which case you should also specify --require)") do |format|
+
+ # TODO make context_lines settable
+ options.context_lines = 3
+
+ case format
+ when 'context', 'c'
+ options.diff_format = :context
+ when 'unified', 'u', '', nil
+ options.diff_format = :unified
+ end
+
+ if [:context,:unified].include? options.diff_format
+ require 'spec/expectations/differs/default'
+ options.differ_class = Spec::Expectations::Differs::Default
+ else
+ begin
+ options.diff_format = :custom
+ options.differ_class = eval(format)
+ rescue NameError
+ err.puts "Couldn't find differ class #{format}"
+ err.puts "Make sure the --require option is specified *before* --diff"
+ exit if out == $stdout
+ end
+ end
+
+ end
+
+ opts.on("-c", "--colour", "--color", "Show coloured (red/green) output") do
+ options.colour = true
+ end
+
+ opts.on("-s", "--spec SPECIFICATION_NAME", "Execute context or specification with matching name") do |spec_name|
+ options.spec_name = spec_name
+ end
+
+ opts.on("-l", "--line LINE_NUMBER", Integer, "Execute context or specification at given line") do |line_number|
+ options.line_number = line_number.to_i
+ end
+
+ opts.on("-f", "--format FORMAT", "Builtin formats: specdoc|s|rdoc|r|html|h",
+ "You can also specify a custom formatter class",
+ "(in which case you should also specify --require)") do |format|
+ case format
+ when 'specdoc', 's'
+ options.formatter_type = Formatter::SpecdocFormatter
+ when 'html', 'h'
+ options.formatter_type = Formatter::HtmlFormatter
+ when 'rdoc', 'r'
+ options.formatter_type = Formatter::RdocFormatter
+ options.dry_run = true
+ else
+ begin
+ options.formatter_type = eval(format)
+ rescue NameError
+ err.puts "Couldn't find formatter class #{format}"
+ err.puts "Make sure the --require option is specified *before* --format"
+ exit if out == $stdout
+ end
+ end
+ end
+
+ opts.on("-r", "--require FILE", "Require FILE before running specs",
+ "Useful for loading custom formatters or other extensions",
+ "If this option is used it must come before the others") do |req|
+ req.split(",").each{|file| require file}
+ end
+
+ opts.on("-b", "--backtrace", "Output full backtrace") do
+ options.backtrace_tweaker = NoisyBacktraceTweaker.new
+ end
+
+ opts.on("-H", "--heckle CODE", "If all specs pass, this will run your specs many times, mutating",
+ "the specced code a little each time. The intent is that specs",
+ "*should* fail, and RSpec will tell you if they don't.",
+ "CODE should be either Some::Module, Some::Class or Some::Fabulous#method}") do |heckle|
+ heckle_runner = PLATFORM == 'i386-mswin32' ? 'spec/runner/heckle_runner_win' : 'spec/runner/heckle_runner'
+ require heckle_runner
+ options.heckle_runner = HeckleRunner.new(heckle)
+ end
+
+ opts.on("-d", "--dry-run", "Don't execute specs") do
+ options.dry_run = true
+ end
+
+ opts.on("-o", "--out OUTPUT_FILE", "Path to output file (defaults to STDOUT)") do |out_file|
+ options.out = File.new(out_file, 'w')
+ end
+
+ opts.on("-O", "--options PATH", "Read options from a file") do |options_file|
+ # Remove the --options option and the argument before writing to file
+ index = args_copy.index("-O") || args_copy.index("--options")
+ args_copy.delete_at(index)
+ args_copy.delete_at(index)
+
+ new_args = args_copy + IO.readlines(options_file).each {|s| s.chomp!}
+ return CommandLine.run(new_args, err, out, true, warn_if_no_files)
+ end
+
+ opts.on("-G", "--generate-options PATH", "Generate an options file for --options") do |options_file|
+ # Remove the --generate-options option and the argument before writing to file
+ index = args_copy.index("-G") || args_copy.index("--generate-options")
+ args_copy.delete_at(index)
+ args_copy.delete_at(index)
+
+ File.open(options_file, 'w') do |io|
+ io.puts args_copy.join("\n")
+ end
+ out.puts "\nOptions written to #{options_file}. You can now use these options with:"
+ out.puts "spec --options #{options_file}"
+ options.generate = true
+ end
+
+ opts.on("-X", "--drb", "Run specs via DRb. (For example against script/rails_spec_server)") do |options_file|
+ # Remove the --options option and the argument before writing to file
+ index = args_copy.index("-X") || args_copy.index("--drb")
+ args_copy.delete_at(index)
+
+ return DrbCommandLine.run(args_copy, err, out, true, warn_if_no_files)
+ end
+
+ opts.on("-v", "--version", "Show version") do
+ out.puts ::Spec::VERSION::DESCRIPTION
+ exit if out == $stdout
+ end
+
+ opts.on_tail("-h", "--help", "You're looking at it") do
+ out.puts opts
+ exit if out == $stdout
+ end
+
+ end
+ opts.parse!(args)
+
+ if args.empty? && warn_if_no_files
+ err.puts "No files specified."
+ err.puts opts
+ exit(6) if err == $stderr
+ end
+
+ if options.line_number
+ set_spec_from_line_number(options, args, err)
+ end
+
+ options
+ end
+
+ def set_spec_from_line_number(options, args, err)
+ unless options.spec_name
+ if args.length == 1
+ if @file_factory.file?(args[0])
+ source = @file_factory.open(args[0])
+ options.spec_name = @spec_parser.spec_name_for(source, options.line_number)
+ elsif @file_factory.directory?(args[0])
+ err.puts "You must specify one file, not a directory when using the --line option"
+ exit(1) if err == $stderr
+ else
+ err.puts "#{args[0]} does not exist"
+ exit(2) if err == $stderr
+ end
+ else
+ err.puts "Only one file can be specified when using the --line option: #{args.inspect}"
+ exit(3) if err == $stderr
+ end
+ else
+ err.puts "You cannot use both --line and --spec"
+ exit(4) if err == $stderr
+ end
+ end
+ end
+ end
+end
diff --git a/test/lib/spec/runner/reporter.rb b/test/lib/spec/runner/reporter.rb
new file mode 100644
index 000000000..e4fb1cb0e
--- /dev/null
+++ b/test/lib/spec/runner/reporter.rb
@@ -0,0 +1,105 @@
+module Spec
+ module Runner
+ class Reporter
+
+ def initialize(formatter, backtrace_tweaker)
+ @formatter = formatter
+ @backtrace_tweaker = backtrace_tweaker
+ clear!
+ end
+
+ def add_context(name)
+ #TODO - @context_names.empty? tells the formatter whether this is the first context or not - that's a little slippery
+ @formatter.add_context(name, @context_names.empty?)
+ @context_names << name
+ end
+
+ def spec_started(name)
+ @spec_names << name
+ @formatter.spec_started(name)
+ end
+
+ def spec_finished(name, error=nil, failure_location=nil)
+ if error.nil?
+ spec_passed(name)
+ else
+ @backtrace_tweaker.tweak_backtrace(error, failure_location)
+ spec_failed(name, Failure.new(@context_names.last, name, error))
+ end
+ end
+
+ def start(number_of_specs)
+ clear!
+ @start_time = Time.new
+ @formatter.start(number_of_specs)
+ end
+
+ def end
+ @end_time = Time.new
+ end
+
+ # Dumps the summary and returns the total number of failures
+ def dump
+ @formatter.start_dump
+ dump_failures
+ @formatter.dump_summary(duration, @spec_names.length, @failures.length)
+ @failures.length
+ end
+
+ private
+
+ def clear!
+ @context_names = []
+ @failures = []
+ @spec_names = []
+ @start_time = nil
+ @end_time = nil
+ end
+
+ def dump_failures
+ return if @failures.empty?
+ @failures.inject(1) do |index, failure|
+ @formatter.dump_failure(index, failure)
+ index + 1
+ end
+ end
+
+ def duration
+ return @end_time - @start_time unless (@end_time.nil? or @start_time.nil?)
+ return "0.0"
+ end
+
+ def spec_passed(name)
+ @formatter.spec_passed(name)
+ end
+
+ def spec_failed(name, failure)
+ @failures << failure
+ @formatter.spec_failed(name, @failures.length, failure)
+ end
+
+ class Failure
+ attr_reader :exception
+
+ def initialize(context_name, spec_name, exception)
+ @context_name = context_name
+ @spec_name = spec_name
+ @exception = exception
+ end
+
+ def header
+ if expectation_not_met?
+ "'#{@context_name} #{@spec_name}' FAILED"
+ else
+ "#{@exception.class.name} in '#{@context_name} #{@spec_name}'"
+ end
+ end
+
+ def expectation_not_met?
+ @exception.is_a?(Spec::Expectations::ExpectationNotMetError)
+ end
+
+ end
+ end
+ end
+end
diff --git a/test/lib/spec/runner/spec_matcher.rb b/test/lib/spec/runner/spec_matcher.rb
new file mode 100755
index 000000000..687fdaa00
--- /dev/null
+++ b/test/lib/spec/runner/spec_matcher.rb
@@ -0,0 +1,25 @@
+module Spec
+ module Runner
+ class SpecMatcher
+
+ attr_writer :spec_desc
+ def initialize(context_desc, spec_desc=nil)
+ @context_desc = context_desc
+ @spec_desc = spec_desc
+ end
+
+ def matches?(desc)
+ desc =~ /(^#{context_regexp} #{spec_regexp}$|^#{context_regexp}$|^#{spec_regexp}$)/
+ end
+
+ private
+ def context_regexp
+ Regexp.escape(@context_desc)
+ end
+
+ def spec_regexp
+ Regexp.escape(@spec_desc)
+ end
+ end
+ end
+end
diff --git a/test/lib/spec/runner/spec_parser.rb b/test/lib/spec/runner/spec_parser.rb
new file mode 100644
index 000000000..2cb8518fc
--- /dev/null
+++ b/test/lib/spec/runner/spec_parser.rb
@@ -0,0 +1,41 @@
+module Spec
+ module Runner
+ # Parses a spec file and finds the nearest spec for a given line number.
+ class SpecParser
+ def spec_name_for(io, line_number)
+ source = io.read
+ context = context_at_line(source, line_number)
+ spec = spec_at_line(source, line_number)
+ if context && spec
+ "#{context} #{spec}"
+ elsif context
+ context
+ else
+ nil
+ end
+ end
+
+ protected
+
+ def context_at_line(source, line_number)
+ find_above(source, line_number, /^\s*context\s+['|"](.*)['|"]/)
+ end
+
+ def spec_at_line(source, line_number)
+ find_above(source, line_number, /^\s*specify\s+['|"](.*)['|"]/)
+ end
+
+ def find_above(source, line_number, pattern)
+ lines_above_reversed(source, line_number).each do |line|
+ return $1 if line =~ pattern
+ end
+ nil
+ end
+
+ def lines_above_reversed(source, line_number)
+ lines = source.split("\n")
+ lines[0...line_number].reverse
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/test/lib/spec/runner/spec_should_raise_handler.rb b/test/lib/spec/runner/spec_should_raise_handler.rb
new file mode 100644
index 000000000..c7fa41c4e
--- /dev/null
+++ b/test/lib/spec/runner/spec_should_raise_handler.rb
@@ -0,0 +1,74 @@
+module Spec
+ module Runner
+ class SpecShouldRaiseHandler
+ def initialize(file_and_line_number, opts)
+ @file_and_line_number = file_and_line_number
+ @options = opts
+ @expected_error_class = determine_error_class(opts)
+ @expected_error_message = determine_error_message(opts)
+ end
+
+ def determine_error_class(opts)
+ if candidate = opts[:should_raise]
+ if candidate.is_a?(Class)
+ return candidate
+ elsif candidate.is_a?(Array)
+ return candidate[0]
+ else
+ return Exception
+ end
+ end
+ end
+
+ def determine_error_message(opts)
+ if candidate = opts[:should_raise]
+ if candidate.is_a?(Array)
+ return candidate[1]
+ end
+ end
+ return nil
+ end
+
+ def build_message(exception=nil)
+ if @expected_error_message.nil?
+ message = "specify block expected #{@expected_error_class.to_s}"
+ else
+ message = "specify block expected #{@expected_error_class.new(@expected_error_message.to_s).inspect}"
+ end
+ message << " but raised #{exception.inspect}" if exception
+ message << " but nothing was raised" unless exception
+ message << "\n"
+ message << @file_and_line_number
+ end
+
+ def error_matches?(error)
+ return false unless error.kind_of?(@expected_error_class)
+ unless @expected_error_message.nil?
+ if @expected_error_message.is_a?(Regexp)
+ return false unless error.message =~ @expected_error_message
+ else
+ return false unless error.message == @expected_error_message
+ end
+ end
+ return true
+ end
+
+ def handle(errors)
+ if @expected_error_class
+ if errors.empty?
+ errors << Spec::Expectations::ExpectationNotMetError.new(build_message)
+ else
+ error_to_remove = errors.detect do |error|
+ error_matches?(error)
+ end
+ if error_to_remove.nil?
+ errors.insert(0,Spec::Expectations::ExpectationNotMetError.new(build_message(errors[0])))
+ else
+ errors.delete(error_to_remove)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/test/lib/spec/runner/specification.rb b/test/lib/spec/runner/specification.rb
new file mode 100644
index 000000000..de8d750fd
--- /dev/null
+++ b/test/lib/spec/runner/specification.rb
@@ -0,0 +1,114 @@
+module Spec
+ module Runner
+ class Specification
+
+ class << self
+ attr_accessor :current, :generated_description
+ protected :current=
+
+ callback_events :before_setup, :after_teardown
+ end
+
+ attr_reader :spec_block
+ callback_events :before_setup, :after_teardown
+
+ def initialize(name, opts={}, &spec_block)
+ @from = caller(0)[3]
+ @description = name
+ @options = opts
+ @spec_block = spec_block
+ @description_generated_callback = lambda { |desc| @generated_description = desc }
+ end
+
+ def run(reporter, setup_block, teardown_block, dry_run, execution_context)
+ reporter.spec_started(name) if reporter
+ return reporter.spec_finished(name) if dry_run
+
+ errors = []
+ begin
+ set_current
+ setup_ok = setup_spec(execution_context, errors, &setup_block)
+ spec_ok = execute_spec(execution_context, errors) if setup_ok
+ teardown_ok = teardown_spec(execution_context, errors, &teardown_block)
+ ensure
+ clear_current
+ end
+
+ SpecShouldRaiseHandler.new(@from, @options).handle(errors)
+ reporter.spec_finished(name, errors.first, failure_location(setup_ok, spec_ok, teardown_ok)) if reporter
+ end
+
+ def matches?(matcher, description)
+ matcher.spec_desc = name
+ matcher.matches?(description)
+ end
+
+ private
+ def name
+ @description == :__generate_description ? generated_description : @description
+ end
+
+ def generated_description
+ @generated_description || "NAME NOT GENERATED"
+ end
+
+ def setup_spec(execution_context, errors, &setup_block)
+ notify_before_setup(errors)
+ execution_context.instance_eval(&setup_block) if setup_block
+ return errors.empty?
+ rescue => e
+ errors << e
+ return false
+ end
+
+ def execute_spec(execution_context, errors)
+ begin
+ execution_context.instance_eval(&spec_block)
+ return true
+ rescue Exception => e
+ errors << e
+ return false
+ end
+ end
+
+ def teardown_spec(execution_context, errors, &teardown_block)
+ execution_context.instance_eval(&teardown_block) if teardown_block
+ notify_after_teardown(errors)
+ return errors.empty?
+ rescue => e
+ errors << e
+ return false
+ end
+
+ def notify_before_setup(errors)
+ notify_class_callbacks(:before_setup, self, &append_errors(errors))
+ notify_callbacks(:before_setup, self, &append_errors(errors))
+ end
+
+ def notify_after_teardown(errors)
+ notify_callbacks(:after_teardown, self, &append_errors(errors))
+ notify_class_callbacks(:after_teardown, self, &append_errors(errors))
+ end
+
+ def append_errors(errors)
+ proc {|error| errors << error}
+ end
+
+ def set_current
+ Spec::Matchers.description_generated(&@description_generated_callback)
+ self.class.send(:current=, self)
+ end
+
+ def clear_current
+ Spec::Matchers.unregister_callback(:description_generated, @description_generated_callback)
+ self.class.send(:current=, nil)
+ end
+
+ def failure_location(setup_ok, spec_ok, teardown_ok)
+ return 'setup' unless setup_ok
+ return name unless spec_ok
+ return 'teardown' unless teardown_ok
+ end
+ end
+ end
+end
diff --git a/test/lib/spec/translator.rb b/test/lib/spec/translator.rb
new file mode 100644
index 000000000..970c8ca00
--- /dev/null
+++ b/test/lib/spec/translator.rb
@@ -0,0 +1,87 @@
+require 'fileutils'
+
+module Spec
+ class Translator
+ def translate_dir(from, to)
+ from = File.expand_path(from)
+ to = File.expand_path(to)
+ if File.directory?(from)
+ FileUtils.mkdir_p(to) unless File.directory?(to)
+ Dir["#{from}/*"].each do |sub_from|
+ path = sub_from[from.length+1..-1]
+ sub_to = File.join(to, path)
+ translate_dir(sub_from, sub_to)
+ end
+ else
+ translate_file(from, to)
+ end
+ end
+
+ def translate_file(from, to)
+ translation = ""
+ File.open(from) do |io|
+ io.each_line do |line|
+ translation << translate(line)
+ end
+ end
+ File.open(to, "w") do |io|
+ io.write(translation)
+ end
+ end
+
+ def translate(line)
+ return line if line =~ /(should_not|should)_receive/
+
+ if line =~ /(.*\.)(should_not|should)(?:_be)(?!_)(.*)/m
+ pre = $1
+ should = $2
+ post = $3
+ be_or_equal = post =~ /(<|>)/ ? "be" : "equal"
+
+ return "#{pre}#{should} #{be_or_equal}#{post}"
+ end
+
+ if line =~ /(.*\.)(should_not|should)_(?!not)(.*)/m
+ pre = $1
+ should = $2
+ post = $3
+
+ post.gsub!(/^raise/, 'raise_error')
+ post.gsub!(/^throw/, 'throw_symbol')
+
+ unless standard_matcher?(post)
+ post = "be_#{post}"
+ end
+
+ line = "#{pre}#{should} #{post}"
+ end
+
+ line
+ end
+
+ def standard_matcher?(matcher)
+ patterns = [
+ /^be/,
+ /^be_close/,
+ /^eql/,
+ /^equal/,
+ /^has/,
+ /^have/,
+ /^change/,
+ /^include/,
+ /^match/,
+ /^raise_error/,
+ /^respond_to/,
+ /^satisfy/,
+ /^throw_symbol/,
+ # Extra ones that we use in spec_helper
+ /^pass/,
+ /^fail/,
+ /^fail_with/,
+ ]
+ matched = patterns.detect{ |p| matcher =~ p }
+ !matched.nil?
+ end
+
+ end
+end \ No newline at end of file
diff --git a/test/lib/spec/version.rb b/test/lib/spec/version.rb
new file mode 100644
index 000000000..924d8458a
--- /dev/null
+++ b/test/lib/spec/version.rb
@@ -0,0 +1,30 @@
+module Spec
+ module VERSION
+ def self.build_tag
+ tag = "REL_" + [MAJOR, MINOR, TINY].join('_')
+ if defined?(RELEASE_CANDIDATE)
+ tag << "_" << RELEASE_CANDIDATE
+ end
+ tag
+ end
+
+ unless defined? MAJOR
+ MAJOR = 0
+ MINOR = 8
+ TINY = 2
+ # RELEASE_CANDIDATE = "RC1"
+
+ # RANDOM_TOKEN: 0.375509844656552
+ REV = "$LastChangedRevision$".match(/LastChangedRevision: (\d+)/)[1]
+
+ STRING = [MAJOR, MINOR, TINY].join('.')
+ FULL_VERSION = "#{STRING} (r#{REV})"
+ TAG = build_tag
+
+ NAME = "RSpec"
+ URL = "http://rspec.rubyforge.org/"
+
+ DESCRIPTION = "#{NAME}-#{FULL_VERSION} - BDD for Ruby\n#{URL}"
+ end
+ end
+end \ No newline at end of file