summaryrefslogtreecommitdiffstats
path: root/test/lib
diff options
context:
space:
mode:
authorLuke Kanies <luke@madstop.com>2007-08-23 00:56:42 -0500
committerLuke Kanies <luke@madstop.com>2007-08-23 00:56:42 -0500
commit5601ecf75d3854a66d087a108e1b06885fa2be12 (patch)
tree28d5892bab14c9296bcd4232075f3658ee1224a0 /test/lib
parent7c4d39ec09c10871d7eb234fe4392381245ff443 (diff)
downloadpuppet-5601ecf75d3854a66d087a108e1b06885fa2be12.tar.gz
puppet-5601ecf75d3854a66d087a108e1b06885fa2be12.tar.xz
puppet-5601ecf75d3854a66d087a108e1b06885fa2be12.zip
Upgrading rspec to version 1.0.8. This only includes the contents of the lib directory, and even then only the spec-related stuff, not the autotest stuff.
Diffstat (limited to 'test/lib')
-rw-r--r--test/lib/spec.rb13
-rw-r--r--test/lib/spec/dsl.rb11
-rw-r--r--test/lib/spec/dsl/behaviour.rb220
-rw-r--r--test/lib/spec/dsl/behaviour_callbacks.rb82
-rw-r--r--test/lib/spec/dsl/behaviour_eval.rb231
-rwxr-xr-xtest/lib/spec/dsl/behaviour_factory.rb42
-rw-r--r--test/lib/spec/dsl/composite_proc_builder.rb33
-rwxr-xr-xtest/lib/spec/dsl/configuration.rb135
-rwxr-xr-xtest/lib/spec/dsl/description.rb76
-rw-r--r--test/lib/spec/dsl/errors.rb9
-rw-r--r--test/lib/spec/dsl/example.rb135
-rwxr-xr-xtest/lib/spec/dsl/example_matcher.rb40
-rw-r--r--test/lib/spec/dsl/example_should_raise_handler.rb74
-rw-r--r--test/lib/spec/expectations.rb5
-rw-r--r--test/lib/spec/expectations/differs/default.rb1
-rw-r--r--test/lib/spec/expectations/extensions.rb1
-rw-r--r--test/lib/spec/expectations/extensions/object.rb63
-rw-r--r--test/lib/spec/expectations/extensions/string_and_symbol.rb4
-rw-r--r--test/lib/spec/expectations/handler.rb32
-rwxr-xr-xtest/lib/spec/extensions.rb1
-rwxr-xr-xtest/lib/spec/extensions/object.rb6
-rw-r--r--test/lib/spec/matchers.rb66
-rw-r--r--test/lib/spec/matchers/be.rb67
-rw-r--r--test/lib/spec/matchers/be_close.rb6
-rw-r--r--test/lib/spec/matchers/eql.rb2
-rw-r--r--test/lib/spec/matchers/equal.rb2
-rw-r--r--test/lib/spec/matchers/have.rb32
-rw-r--r--test/lib/spec/matchers/include.rb36
-rwxr-xr-xtest/lib/spec/matchers/operator_matcher.rb72
-rw-r--r--test/lib/spec/matchers/raise_error.rb13
-rw-r--r--test/lib/spec/matchers/respond_to.rb32
-rw-r--r--test/lib/spec/matchers/throw_symbol.rb5
-rw-r--r--test/lib/spec/mocks.rb46
-rw-r--r--test/lib/spec/mocks/argument_constraint_matchers.rb27
-rw-r--r--test/lib/spec/mocks/argument_expectation.rb65
-rw-r--r--test/lib/spec/mocks/error_generator.rb7
-rw-r--r--test/lib/spec/mocks/message_expectation.rb13
-rw-r--r--test/lib/spec/mocks/methods.rb33
-rw-r--r--test/lib/spec/mocks/mock.rb11
-rw-r--r--test/lib/spec/mocks/proxy.rb167
-rw-r--r--test/lib/spec/mocks/space.rb28
-rw-r--r--test/lib/spec/mocks/spec_methods.rb30
-rw-r--r--test/lib/spec/rake/spectask.rb136
-rw-r--r--test/lib/spec/rake/verify_rcov.rb9
-rw-r--r--test/lib/spec/runner.rb103
-rw-r--r--test/lib/spec/runner/backtrace_tweaker.rb6
-rw-r--r--test/lib/spec/runner/behaviour_runner.rb123
-rw-r--r--test/lib/spec/runner/command_line.rb24
-rw-r--r--test/lib/spec/runner/drb_command_line.rb6
-rw-r--r--test/lib/spec/runner/extensions/kernel.rb51
-rw-r--r--test/lib/spec/runner/formatter.rb4
-rw-r--r--test/lib/spec/runner/formatter/base_formatter.rb76
-rw-r--r--test/lib/spec/runner/formatter/base_text_formatter.rb130
-rw-r--r--test/lib/spec/runner/formatter/failing_behaviours_formatter.rb29
-rw-r--r--test/lib/spec/runner/formatter/failing_examples_formatter.rb22
-rw-r--r--test/lib/spec/runner/formatter/html_formatter.rb350
-rw-r--r--test/lib/spec/runner/formatter/progress_bar_formatter.rb24
-rw-r--r--test/lib/spec/runner/formatter/rdoc_formatter.rb22
-rw-r--r--test/lib/spec/runner/formatter/snippet_extractor.rb52
-rw-r--r--test/lib/spec/runner/formatter/specdoc_formatter.rb24
-rw-r--r--test/lib/spec/runner/heckle_runner.rb15
-rw-r--r--test/lib/spec/runner/heckle_runner_unsupported.rb10
-rw-r--r--test/lib/spec/runner/option_parser.rb301
-rw-r--r--test/lib/spec/runner/options.rb175
-rw-r--r--test/lib/spec/runner/reporter.rb86
-rw-r--r--test/lib/spec/runner/spec_parser.rb39
-rwxr-xr-xtest/lib/spec/test_case_adapter.rb10
-rw-r--r--test/lib/spec/translator.rb51
-rw-r--r--test/lib/spec/version.rb27
69 files changed, 3081 insertions, 798 deletions
diff --git a/test/lib/spec.rb b/test/lib/spec.rb
index 9a83c1d5e..48c12595c 100644
--- a/test/lib/spec.rb
+++ b/test/lib/spec.rb
@@ -1,8 +1,13 @@
-require 'spec/deprecated'
+require 'spec/extensions'
require 'spec/version'
-require 'spec/callback'
require 'spec/matchers'
require 'spec/expectations'
-require 'spec/mocks'
-require 'spec/runner'
require 'spec/translator'
+require 'spec/dsl'
+require 'spec/runner'
+
+class Object
+ def metaclass
+ class << self; self; end
+ end
+end
diff --git a/test/lib/spec/dsl.rb b/test/lib/spec/dsl.rb
new file mode 100644
index 000000000..f960eb907
--- /dev/null
+++ b/test/lib/spec/dsl.rb
@@ -0,0 +1,11 @@
+require 'spec/dsl/description'
+require 'spec/dsl/errors'
+require 'spec/dsl/configuration'
+require 'spec/dsl/behaviour_callbacks'
+require 'spec/dsl/behaviour'
+require 'spec/dsl/behaviour_eval'
+require 'spec/dsl/composite_proc_builder'
+require 'spec/dsl/example'
+require 'spec/dsl/example_matcher'
+require 'spec/dsl/example_should_raise_handler'
+require 'spec/dsl/behaviour_factory'
diff --git a/test/lib/spec/dsl/behaviour.rb b/test/lib/spec/dsl/behaviour.rb
new file mode 100644
index 000000000..5158bb673
--- /dev/null
+++ b/test/lib/spec/dsl/behaviour.rb
@@ -0,0 +1,220 @@
+module Spec
+ module DSL
+ class EvalModule < Module; end
+ class Behaviour
+ extend BehaviourCallbacks
+
+ class << self
+ def add_shared_behaviour(behaviour)
+ return if behaviour.equal?(found_behaviour = find_shared_behaviour(behaviour.description))
+ return if found_behaviour and File.expand_path(behaviour.description[:spec_path]) == File.expand_path(found_behaviour.description[:spec_path])
+ raise ArgumentError.new("Shared Behaviour '#{behaviour.description}' already exists") if found_behaviour
+ shared_behaviours << behaviour
+ end
+
+ def find_shared_behaviour(behaviour_description)
+ shared_behaviours.find { |b| b.description == behaviour_description }
+ end
+
+ def shared_behaviours
+ # TODO - this needs to be global, or at least accessible from
+ # from subclasses of Behaviour in a centralized place. I'm not loving
+ # this as a solution, but it works for now.
+ $shared_behaviours ||= []
+ end
+ end
+
+ def initialize(*args, &behaviour_block)
+ init_description(*args)
+ init_eval_module
+ before_eval
+ eval_behaviour(&behaviour_block)
+ end
+
+ private
+
+ def init_description(*args)
+ unless self.class == Behaviour
+ args << {} unless Hash === args.last
+ args.last[:behaviour_class] = self.class
+ end
+ @description = Description.new(*args)
+ end
+
+ def init_eval_module
+ @eval_module = EvalModule.new
+ @eval_module.extend BehaviourEval::ModuleMethods
+ @eval_module.include BehaviourEval::InstanceMethods
+ @eval_module.include described_type if described_type.class == Module
+ @eval_module.behaviour = self
+ @eval_module.description = @description
+ end
+
+ def eval_behaviour(&behaviour_block)
+ @eval_module.class_eval(&behaviour_block)
+ end
+
+ protected
+
+ def before_eval
+ end
+
+ public
+
+ def run(reporter, dry_run=false, reverse=false, timeout=nil)
+ raise "shared behaviours should never run" if shared?
+ # TODO - change add_behaviour to add_description ??????
+ reporter.add_behaviour(@description)
+ prepare_execution_context_class
+ before_all_errors = run_before_all(reporter, dry_run)
+
+ exs = reverse ? examples.reverse : examples
+ example_execution_context = nil
+
+ if before_all_errors.empty?
+ exs.each do |example|
+ example_execution_context = execution_context(example)
+ example_execution_context.copy_instance_variables_from(@before_and_after_all_context_instance) unless before_all_proc(behaviour_type).nil?
+
+ befores = before_each_proc(behaviour_type) {|e| raise e}
+ afters = after_each_proc(behaviour_type)
+ example.run(reporter, befores, afters, dry_run, example_execution_context, timeout)
+ end
+ end
+
+ @before_and_after_all_context_instance.copy_instance_variables_from(example_execution_context) unless after_all_proc(behaviour_type).nil?
+ run_after_all(reporter, dry_run)
+ end
+
+ def number_of_examples
+ examples.length
+ end
+
+ def matches?(specified_examples)
+ matcher ||= ExampleMatcher.new(description)
+
+ examples.each do |example|
+ return true if example.matches?(matcher, specified_examples)
+ end
+ return false
+ end
+
+ def shared?
+ @description[:shared]
+ end
+
+ def retain_examples_matching!(specified_examples)
+ return if specified_examples.index(description)
+ matcher = ExampleMatcher.new(description)
+ examples.reject! do |example|
+ !example.matches?(matcher, specified_examples)
+ end
+ end
+
+ def methods
+ my_methods = super
+ my_methods |= @eval_module.methods
+ my_methods
+ end
+
+ # Includes modules in the Behaviour (the <tt>describe</tt> block).
+ def include(*args)
+ @eval_module.include(*args)
+ end
+
+ def behaviour_type #:nodoc:
+ @description[:behaviour_type]
+ end
+
+ # Sets the #number on each Example and returns the next number
+ def set_sequence_numbers(number, reverse) #:nodoc:
+ exs = reverse ? examples.reverse : examples
+ exs.each do |example|
+ example.number = number
+ number += 1
+ end
+ number
+ end
+
+ protected
+
+ # Messages that this class does not understand
+ # are passed directly to the @eval_module.
+ def method_missing(sym, *args, &block)
+ @eval_module.send(sym, *args, &block)
+ end
+
+ def prepare_execution_context_class
+ plugin_mock_framework
+ weave_in_included_modules
+ define_predicate_matchers #this is in behaviour_eval
+ execution_context_class
+ end
+
+ def weave_in_included_modules
+ mods = [@eval_module]
+ mods << included_modules.dup
+ mods << Spec::Runner.configuration.modules_for(behaviour_type)
+ execution_context_class.class_eval do
+ # WARNING - the following can be executed in the context of any
+ # class, and should never pass more than one module to include
+ # even though we redefine include in this class. This is NOT
+ # tested anywhere, hence this comment.
+ mods.flatten.each {|mod| include mod}
+ end
+ end
+
+ def execution_context(example)
+ execution_context_class.new(example)
+ end
+
+ def run_before_all(reporter, dry_run)
+ errors = []
+ unless dry_run
+ begin
+ @before_and_after_all_context_instance = execution_context(nil)
+ @before_and_after_all_context_instance.instance_eval(&before_all_proc(behaviour_type))
+ rescue Exception => e
+ errors << e
+ location = "before(:all)"
+ # The easiest is to report this as an example failure. We don't have an Example
+ # at this point, so we'll just create a placeholder.
+ reporter.example_finished(Example.new(location), e, location) if reporter
+ end
+ end
+ errors
+ end
+
+ def run_after_all(reporter, dry_run)
+ unless dry_run
+ begin
+ @before_and_after_all_context_instance ||= execution_context(nil)
+ @before_and_after_all_context_instance.instance_eval(&after_all_proc(behaviour_type))
+ rescue Exception => e
+ location = "after(:all)"
+ reporter.example_finished(Example.new(location), e, location) if reporter
+ end
+ end
+ end
+
+ def plugin_mock_framework
+ case mock_framework = Spec::Runner.configuration.mock_framework
+ when Module
+ include mock_framework
+ else
+ require Spec::Runner.configuration.mock_framework
+ include Spec::Plugins::MockFramework
+ end
+ end
+
+ def description
+ @description.to_s
+ end
+
+ def described_type
+ @description.described_type
+ end
+
+ end
+ end
+end
diff --git a/test/lib/spec/dsl/behaviour_callbacks.rb b/test/lib/spec/dsl/behaviour_callbacks.rb
new file mode 100644
index 000000000..8b69ad9e5
--- /dev/null
+++ b/test/lib/spec/dsl/behaviour_callbacks.rb
@@ -0,0 +1,82 @@
+module Spec
+ module DSL
+ # See http://rspec.rubyforge.org/documentation/before_and_after.html
+ module BehaviourCallbacks
+ def prepend_before(*args, &block)
+ scope, options = scope_and_options(*args)
+ add(scope, options, :before, :unshift, &block)
+ end
+ def append_before(*args, &block)
+ scope, options = scope_and_options(*args)
+ add(scope, options, :before, :<<, &block)
+ end
+ alias_method :before, :append_before
+
+ def prepend_after(*args, &block)
+ scope, options = scope_and_options(*args)
+ add(scope, options, :after, :unshift, &block)
+ end
+ alias_method :after, :prepend_after
+ def append_after(*args, &block)
+ scope, options = scope_and_options(*args)
+ add(scope, options, :after, :<<, &block)
+ end
+
+ def scope_and_options(*args)
+ args, options = args_and_options(*args)
+ scope = (args[0] || :each), options
+ end
+
+ def add(scope, options, where, how, &block)
+ scope ||= :each
+ options ||= {}
+ behaviour_type = options[:behaviour_type]
+ case scope
+ when :each; self.__send__("#{where}_each_parts", behaviour_type).__send__(how, block)
+ when :all; self.__send__("#{where}_all_parts", behaviour_type).__send__(how, block)
+ end
+ end
+
+ def remove_after(scope, &block)
+ after_each_parts.delete(block)
+ end
+
+ # Deprecated. Use before(:each)
+ def setup(&block)
+ before(:each, &block)
+ end
+
+ # Deprecated. Use after(:each)
+ def teardown(&block)
+ after(:each, &block)
+ end
+
+ def before_all_parts(behaviour_type=nil) # :nodoc:
+ @before_all_parts ||= {}
+ @before_all_parts[behaviour_type] ||= []
+ end
+
+ def after_all_parts(behaviour_type=nil) # :nodoc:
+ @after_all_parts ||= {}
+ @after_all_parts[behaviour_type] ||= []
+ end
+
+ def before_each_parts(behaviour_type=nil) # :nodoc:
+ @before_each_parts ||= {}
+ @before_each_parts[behaviour_type] ||= []
+ end
+
+ def after_each_parts(behaviour_type=nil) # :nodoc:
+ @after_each_parts ||= {}
+ @after_each_parts[behaviour_type] ||= []
+ end
+
+ def clear_before_and_after! # :nodoc:
+ @before_all_parts = nil
+ @after_all_parts = nil
+ @before_each_parts = nil
+ @after_each_parts = nil
+ end
+ end
+ end
+end
diff --git a/test/lib/spec/dsl/behaviour_eval.rb b/test/lib/spec/dsl/behaviour_eval.rb
new file mode 100644
index 000000000..9f7b8281e
--- /dev/null
+++ b/test/lib/spec/dsl/behaviour_eval.rb
@@ -0,0 +1,231 @@
+module Spec
+ module DSL
+ module BehaviourEval
+ module ModuleMethods
+ include BehaviourCallbacks
+
+ attr_writer :behaviour
+ attr_accessor :description
+
+ # RSpec runs every example in a new instance of Object, mixing in
+ # the behaviour necessary to run examples. Because this behaviour gets
+ # mixed in, it can get mixed in to an instance of any class at all.
+ #
+ # This is something that you would hardly ever use, but there is one
+ # common use case for it - inheriting from Test::Unit::TestCase. RSpec's
+ # Rails plugin uses this feature to provide access to all of the features
+ # that are available for Test::Unit within RSpec examples.
+ def inherit(klass)
+ raise ArgumentError.new("Shared behaviours cannot inherit from classes") if @behaviour.shared?
+ @behaviour_superclass = klass
+ derive_execution_context_class_from_behaviour_superclass
+ end
+
+ # You can pass this one or many modules. Each module will subsequently
+ # be included in the each object in which an example is run. Use this
+ # to provide global helper methods to your examples.
+ #
+ # == Example
+ #
+ # module HelperMethods
+ # def helper_method
+ # ...
+ # end
+ # end
+ #
+ # describe Thing do
+ # include HelperMethods
+ # it "should do stuff" do
+ # helper_method
+ # end
+ # end
+ def include(*mods)
+ mods.each do |mod|
+ included_modules << mod
+ mod.send :included, self
+ end
+ end
+
+ # Use this to pull in examples from shared behaviours.
+ # See Spec::Runner for information about shared behaviours.
+ def it_should_behave_like(behaviour_description)
+ behaviour = @behaviour.class.find_shared_behaviour(behaviour_description)
+ if behaviour.nil?
+ raise RuntimeError.new("Shared Behaviour '#{behaviour_description}' can not be found")
+ end
+ behaviour.copy_to(self)
+ end
+
+ def copy_to(eval_module) # :nodoc:
+ examples.each { |e| eval_module.examples << e; }
+ before_each_parts.each { |p| eval_module.before_each_parts << p }
+ after_each_parts.each { |p| eval_module.after_each_parts << p }
+ before_all_parts.each { |p| eval_module.before_all_parts << p }
+ after_all_parts.each { |p| eval_module.after_all_parts << p }
+ included_modules.each { |m| eval_module.included_modules << m }
+ eval_module.included_modules << self
+ end
+
+ # :call-seq:
+ # predicate_matchers[matcher_name] = method_on_object
+ # predicate_matchers[matcher_name] = [method1_on_object, method2_on_object]
+ #
+ # Dynamically generates a custom matcher that will match
+ # a predicate on your class. RSpec provides a couple of these
+ # out of the box:
+ #
+ # exist (or state expectations)
+ # File.should exist("path/to/file")
+ #
+ # an_instance_of (for mock argument constraints)
+ # mock.should_receive(:message).with(an_instance_of(String))
+ #
+ # == Examples
+ #
+ # class Fish
+ # def can_swim?
+ # true
+ # end
+ # end
+ #
+ # describe Fish do
+ # predicate_matchers[:swim] = :can_swim?
+ # it "should swim" do
+ # Fish.new.should swim
+ # end
+ # end
+ def predicate_matchers
+ @predicate_matchers ||= {:exist => :exist?, :an_instance_of => :is_a?}
+ end
+
+ def define_predicate_matchers(hash=nil) # :nodoc:
+ if hash.nil?
+ define_predicate_matchers(predicate_matchers)
+ define_predicate_matchers(Spec::Runner.configuration.predicate_matchers)
+ else
+ hash.each_pair do |matcher_method, method_on_object|
+ define_method matcher_method do |*args|
+ eval("be_#{method_on_object.to_s.gsub('?','')}(*args)")
+ end
+ end
+ end
+ end
+
+ # Creates an instance of Spec::DSL::Example and adds
+ # it to a collection of examples of the current behaviour.
+ def it(description=:__generate_description, opts={}, &block)
+ examples << Example.new(description, opts, &block)
+ end
+
+ # Alias for it.
+ def specify(description=:__generate_description, opts={}, &block)
+ it(description, opts, &block)
+ end
+
+ def methods # :nodoc:
+ my_methods = super
+ my_methods |= behaviour_superclass.methods
+ my_methods
+ end
+
+ protected
+
+ def method_missing(method_name, *args)
+ if behaviour_superclass.respond_to?(method_name)
+ return execution_context_class.send(method_name, *args)
+ end
+ super
+ end
+
+ def before_each_proc(behaviour_type, &error_handler)
+ parts = []
+ parts.push(*Behaviour.before_each_parts(nil))
+ parts.push(*Behaviour.before_each_parts(behaviour_type)) unless behaviour_type.nil?
+ parts.push(*before_each_parts(nil))
+ parts.push(*before_each_parts(behaviour_type)) unless behaviour_type.nil?
+ CompositeProcBuilder.new(parts).proc(&error_handler)
+ end
+
+ def before_all_proc(behaviour_type, &error_handler)
+ parts = []
+ parts.push(*Behaviour.before_all_parts(nil))
+ parts.push(*Behaviour.before_all_parts(behaviour_type)) unless behaviour_type.nil?
+ parts.push(*before_all_parts(nil))
+ parts.push(*before_all_parts(behaviour_type)) unless behaviour_type.nil?
+ CompositeProcBuilder.new(parts).proc(&error_handler)
+ end
+
+ def after_all_proc(behaviour_type)
+ parts = []
+ parts.push(*after_all_parts(behaviour_type)) unless behaviour_type.nil?
+ parts.push(*after_all_parts(nil))
+ parts.push(*Behaviour.after_all_parts(behaviour_type)) unless behaviour_type.nil?
+ parts.push(*Behaviour.after_all_parts(nil))
+ CompositeProcBuilder.new(parts).proc
+ end
+
+ def after_each_proc(behaviour_type)
+ parts = []
+ parts.push(*after_each_parts(behaviour_type)) unless behaviour_type.nil?
+ parts.push(*after_each_parts(nil))
+ parts.push(*Behaviour.after_each_parts(behaviour_type)) unless behaviour_type.nil?
+ parts.push(*Behaviour.after_each_parts(nil))
+ CompositeProcBuilder.new(parts).proc
+ end
+
+ private
+
+ def execution_context_class
+ @execution_context_class ||= derive_execution_context_class_from_behaviour_superclass
+ end
+
+ def derive_execution_context_class_from_behaviour_superclass
+ @execution_context_class = Class.new(behaviour_superclass)
+ behaviour_superclass.spec_inherited(self) if behaviour_superclass.respond_to?(:spec_inherited)
+ @execution_context_class
+ end
+
+ def behaviour_superclass
+ @behaviour_superclass ||= Object
+ end
+
+ protected
+ def included_modules
+ @included_modules ||= [::Spec::Matchers]
+ end
+
+ def examples
+ @examples ||= []
+ end
+ end
+
+ module InstanceMethods
+ def initialize(*args, &block) #:nodoc:
+ # TODO - inheriting from TestUnit::TestCase fails without this
+ # - let's figure out why and move this somewhere else
+ end
+
+ def violated(message="")
+ raise Spec::Expectations::ExpectationNotMetError.new(message)
+ end
+
+ def inspect
+ "[RSpec example]"
+ end
+
+ def pending(message)
+ if block_given?
+ begin
+ yield
+ rescue Exception => e
+ raise Spec::DSL::ExamplePendingError.new(message)
+ end
+ raise Spec::DSL::PendingFixedError.new("Expected pending '#{message}' to fail. No Error was raised.")
+ else
+ raise Spec::DSL::ExamplePendingError.new(message)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/test/lib/spec/dsl/behaviour_factory.rb b/test/lib/spec/dsl/behaviour_factory.rb
new file mode 100755
index 000000000..44b60c641
--- /dev/null
+++ b/test/lib/spec/dsl/behaviour_factory.rb
@@ -0,0 +1,42 @@
+module Spec
+ module DSL
+ class BehaviourFactory
+
+ class << self
+
+ BEHAVIOUR_CLASSES = {:default => Spec::DSL::Behaviour}
+
+ # Registers a behaviour class +klass+ with the symbol
+ # +behaviour_type+. For example:
+ #
+ # Spec::DSL::BehaviourFactory.add_behaviour_class(:farm, Spec::Farm::DSL::FarmBehaviour)
+ #
+ # This will cause Kernel#describe from a file living in
+ # <tt>spec/farm</tt> to create behaviour instances of type
+ # Spec::Farm::DSL::FarmBehaviour.
+ def add_behaviour_class(behaviour_type, klass)
+ BEHAVIOUR_CLASSES[behaviour_type] = klass
+ end
+
+ def remove_behaviour_class(behaviour_type)
+ BEHAVIOUR_CLASSES.delete(behaviour_type)
+ end
+
+ def create(*args, &block)
+ opts = Hash === args.last ? args.last : {}
+ if opts[:shared]
+ behaviour_type = :default
+ elsif opts[:behaviour_type]
+ behaviour_type = opts[:behaviour_type]
+ elsif opts[:spec_path] =~ /spec(\\|\/)(#{BEHAVIOUR_CLASSES.keys.join('|')})/
+ behaviour_type = $2.to_sym
+ else
+ behaviour_type = :default
+ end
+ return BEHAVIOUR_CLASSES[behaviour_type].new(*args, &block)
+ end
+
+ end
+ end
+ end
+end
diff --git a/test/lib/spec/dsl/composite_proc_builder.rb b/test/lib/spec/dsl/composite_proc_builder.rb
new file mode 100644
index 000000000..373f44953
--- /dev/null
+++ b/test/lib/spec/dsl/composite_proc_builder.rb
@@ -0,0 +1,33 @@
+module Spec
+ module DSL
+ class CompositeProcBuilder < Array
+ def initialize(callbacks=[])
+ push(*callbacks)
+ end
+
+ def proc(&error_handler)
+ parts = self
+ errors = []
+ Proc.new do
+ result = parts.collect do |part|
+ begin
+ if part.is_a?(UnboundMethod)
+ part.bind(self).call
+ else
+ instance_eval(&part)
+ end
+ rescue Exception => e
+ if error_handler
+ error_handler.call(e)
+ else
+ errors << e
+ end
+ end
+ end
+ raise errors.first unless errors.empty?
+ result
+ end
+ end
+ end
+ end
+end
diff --git a/test/lib/spec/dsl/configuration.rb b/test/lib/spec/dsl/configuration.rb
new file mode 100755
index 000000000..709574ded
--- /dev/null
+++ b/test/lib/spec/dsl/configuration.rb
@@ -0,0 +1,135 @@
+module Spec
+ module DSL
+ class Configuration
+
+ # Chooses what mock framework to use. Example:
+ #
+ # Spec::Runner.configure do |config|
+ # config.mock_with :rspec, :mocha, :flexmock, or :rr
+ # end
+ #
+ # To use any other mock framework, you'll have to provide
+ # your own adapter. This is simply a module that responds to
+ # setup_mocks_for_rspec, verify_mocks_for_rspec and teardown_mocks_for_rspec.
+ # These are your hooks into the lifecycle of a given example. RSpec will
+ # call setup_mocks_for_rspec before running anything else in each Example.
+ # After executing the #after methods, RSpec will then call verify_mocks_for_rspec
+ # and teardown_mocks_for_rspec (this is guaranteed to run even if there are
+ # failures in verify_mocks_for_rspec).
+ #
+ # Once you've defined this module, you can pass that to mock_with:
+ #
+ # Spec::Runner.configure do |config|
+ # config.mock_with MyMockFrameworkAdapter
+ # end
+ #
+ def mock_with(mock_framework)
+ @mock_framework = case mock_framework
+ when Symbol
+ mock_framework_path(mock_framework.to_s)
+ else
+ mock_framework
+ end
+ end
+
+ def mock_framework # :nodoc:
+ @mock_framework ||= mock_framework_path("rspec")
+ end
+
+ # Declares modules to be included in all behaviours (<tt>describe</tt> blocks).
+ #
+ # config.include(My::Bottle, My::Cup)
+ #
+ # If you want to restrict the inclusion to a subset of all the behaviours then
+ # specify this in a Hash as the last argument:
+ #
+ # config.include(My::Pony, My::Horse, :behaviour_type => :farm)
+ #
+ # Only behaviours that have that type will get the modules included:
+ #
+ # describe "Downtown", :behaviour_type => :city do
+ # # Will *not* get My::Pony and My::Horse included
+ # end
+ #
+ # describe "Old Mac Donald", :behaviour_type => :farm do
+ # # *Will* get My::Pony and My::Horse included
+ # end
+ #
+ def include(*args)
+ args << {} unless Hash === args.last
+ modules, options = args_and_options(*args)
+ required_behaviour_type = options[:behaviour_type]
+ required_behaviour_type = required_behaviour_type.to_sym unless required_behaviour_type.nil?
+ @modules ||= {}
+ @modules[required_behaviour_type] ||= []
+ @modules[required_behaviour_type] += modules
+ end
+
+ def modules_for(required_behaviour_type) #:nodoc:
+ @modules ||= {}
+ modules = @modules[nil] || [] # general ones
+ modules << @modules[required_behaviour_type.to_sym] unless required_behaviour_type.nil?
+ modules.uniq.compact
+ end
+
+ # This is just for cleanup in RSpec's own examples
+ def exclude(*modules) #:nodoc:
+ @modules.each do |behaviour_type, mods|
+ modules.each{|m| mods.delete(m)}
+ end
+ end
+
+ # Defines global predicate matchers. Example:
+ #
+ # config.predicate_matchers[:swim] = :can_swim?
+ #
+ # This makes it possible to say:
+ #
+ # person.should swim # passes if person.should_swim? returns true
+ #
+ def predicate_matchers
+ @predicate_matchers ||= {}
+ end
+
+ # Prepends a global <tt>before</tt> block to all behaviours.
+ # See #append_before for filtering semantics.
+ def prepend_before(*args, &proc)
+ Behaviour.prepend_before(*args, &proc)
+ end
+ # Appends a global <tt>before</tt> block to all behaviours.
+ #
+ # If you want to restrict the block to a subset of all the behaviours then
+ # specify this in a Hash as the last argument:
+ #
+ # config.prepend_before(:all, :behaviour_type => :farm)
+ #
+ # or
+ #
+ # config.prepend_before(:behaviour_type => :farm)
+ #
+ def append_before(*args, &proc)
+ Behaviour.append_before(*args, &proc)
+ end
+ alias_method :before, :append_before
+
+ # Prepends a global <tt>after</tt> block to all behaviours.
+ # See #append_before for filtering semantics.
+ def prepend_after(*args, &proc)
+ Behaviour.prepend_after(*args, &proc)
+ end
+ alias_method :after, :prepend_after
+ # Appends a global <tt>after</tt> block to all behaviours.
+ # See #append_before for filtering semantics.
+ def append_after(*args, &proc)
+ Behaviour.append_after(*args, &proc)
+ end
+
+ private
+
+ def mock_framework_path(framework_name)
+ File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "plugins", "mock_frameworks", framework_name))
+ end
+
+ end
+ end
+end
diff --git a/test/lib/spec/dsl/description.rb b/test/lib/spec/dsl/description.rb
new file mode 100755
index 000000000..fe8c9b0c9
--- /dev/null
+++ b/test/lib/spec/dsl/description.rb
@@ -0,0 +1,76 @@
+module Spec
+ module DSL
+ class Description
+ module ClassMethods
+ def generate_description(*args)
+ description = args.shift.to_s
+ unless args.empty?
+ suffix = args.shift.to_s
+ description << " " unless suffix =~ /^\s|\.|#/
+ description << suffix
+ end
+ description
+ end
+ end
+ extend ClassMethods
+
+ attr_reader :description, :described_type
+
+ def initialize(*args)
+ args, @options = args_and_options(*args)
+ init_behaviour_type(@options)
+ init_spec_path(@options)
+ init_described_type(args)
+ init_description(*args)
+ end
+
+ def [](key)
+ @options[key]
+ end
+
+ def []=(key, value)
+ @options[key] = value
+ end
+
+ def to_s; @description; end
+
+ def ==(value)
+ case value
+ when Description
+ @description == value.description
+ else
+ @description == value
+ end
+ end
+
+ private
+ def init_behaviour_type(options)
+ # NOTE - BE CAREFUL IF CHANGING THIS NEXT LINE:
+ # this line is as it is to satisfy JRuby - the original version
+ # read, simply: "if options[:behaviour_class]", which passed against ruby, but failed against jruby
+ if options[:behaviour_class] && options[:behaviour_class].ancestors.include?(Behaviour)
+ options[:behaviour_type] = parse_behaviour_type(@options[:behaviour_class])
+ end
+ end
+
+ def init_spec_path(options)
+ if options.has_key?(:spec_path)
+ options[:spec_path] = File.expand_path(@options[:spec_path])
+ end
+ end
+
+ def init_description(*args)
+ @description = self.class.generate_description(*args)
+ end
+
+ def init_described_type(args)
+ @described_type = args.first unless args.first.is_a?(String)
+ end
+
+ def parse_behaviour_type(behaviour_class)
+ behaviour_class.to_s.split("::").reverse[0].gsub!('Behaviour', '').downcase.to_sym
+ end
+
+ end
+ end
+end
diff --git a/test/lib/spec/dsl/errors.rb b/test/lib/spec/dsl/errors.rb
new file mode 100644
index 000000000..ba7046a89
--- /dev/null
+++ b/test/lib/spec/dsl/errors.rb
@@ -0,0 +1,9 @@
+module Spec
+ module DSL
+ class ExamplePendingError < StandardError
+ end
+
+ class PendingFixedError < StandardError
+ end
+ end
+end
diff --git a/test/lib/spec/dsl/example.rb b/test/lib/spec/dsl/example.rb
new file mode 100644
index 000000000..d04073f7e
--- /dev/null
+++ b/test/lib/spec/dsl/example.rb
@@ -0,0 +1,135 @@
+require 'timeout'
+
+module Spec
+ module DSL
+ class Example
+ # The global sequence number of this example
+ attr_accessor :number
+
+ def initialize(description, options={}, &example_block)
+ @from = caller(0)[3]
+ @options = options
+ @example_block = example_block
+ @description = description
+ @description_generated_proc = lambda { |desc| @generated_description = desc }
+ end
+
+ def run(reporter, before_each_block, after_each_block, dry_run, execution_context, timeout=nil)
+ @dry_run = dry_run
+ reporter.example_started(self)
+ return reporter.example_finished(self) if dry_run
+
+ errors = []
+ location = nil
+ Timeout.timeout(timeout) do
+ before_each_ok = before_example(execution_context, errors, &before_each_block)
+ example_ok = run_example(execution_context, errors) if before_each_ok
+ after_each_ok = after_example(execution_context, errors, &after_each_block)
+ location = failure_location(before_each_ok, example_ok, after_each_ok)
+ end
+
+ ExampleShouldRaiseHandler.new(@from, @options).handle(errors)
+ reporter.example_finished(self, errors.first, location, @example_block.nil?) if reporter
+ end
+
+ def matches?(matcher, specified_examples)
+ matcher.example_desc = description
+ matcher.matches?(specified_examples)
+ end
+
+ def description
+ @description == :__generate_description ? generated_description : @description
+ end
+
+ def to_s
+ description
+ end
+
+ private
+
+ def generated_description
+ return @generated_description if @generated_description
+ if @dry_run
+ "NO NAME (Because of --dry-run)"
+ else
+ if @failed
+ "NO NAME (Because of Error raised in matcher)"
+ else
+ "NO NAME (Because there were no expectations)"
+ end
+ end
+ end
+
+ def before_example(execution_context, errors, &behaviour_before_block)
+ setup_mocks(execution_context)
+ Spec::Matchers.description_generated(@description_generated_proc)
+
+ builder = CompositeProcBuilder.new
+ before_proc = builder.proc(&append_errors(errors))
+ execution_context.instance_eval(&before_proc)
+
+ execution_context.instance_eval(&behaviour_before_block) if behaviour_before_block
+ return errors.empty?
+ rescue Exception => e
+ @failed = true
+ errors << e
+ return false
+ end
+
+ def run_example(execution_context, errors)
+ begin
+ execution_context.instance_eval(&@example_block) if @example_block
+ return true
+ rescue Exception => e
+ @failed = true
+ errors << e
+ return false
+ end
+ end
+
+ def after_example(execution_context, errors, &behaviour_after_each)
+ execution_context.instance_eval(&behaviour_after_each) if behaviour_after_each
+
+ begin
+ verify_mocks(execution_context)
+ ensure
+ teardown_mocks(execution_context)
+ end
+
+ Spec::Matchers.unregister_description_generated(@description_generated_proc)
+
+ builder = CompositeProcBuilder.new
+ after_proc = builder.proc(&append_errors(errors))
+ execution_context.instance_eval(&after_proc)
+
+ return errors.empty?
+ rescue Exception => e
+ @failed = true
+ errors << e
+ return false
+ end
+
+ def setup_mocks(execution_context)
+ execution_context.setup_mocks_for_rspec if execution_context.respond_to?(:setup_mocks_for_rspec)
+ end
+
+ def verify_mocks(execution_context)
+ execution_context.verify_mocks_for_rspec if execution_context.respond_to?(:verify_mocks_for_rspec)
+ end
+
+ def teardown_mocks(execution_context)
+ execution_context.teardown_mocks_for_rspec if execution_context.respond_to?(:teardown_mocks_for_rspec)
+ end
+
+ def append_errors(errors)
+ proc {|error| errors << error}
+ end
+
+ def failure_location(before_each_ok, example_ok, after_each_ok)
+ return 'before(:each)' unless before_each_ok
+ return description unless example_ok
+ return 'after(:each)' unless after_each_ok
+ end
+ end
+ end
+end
diff --git a/test/lib/spec/dsl/example_matcher.rb b/test/lib/spec/dsl/example_matcher.rb
new file mode 100755
index 000000000..18cc47409
--- /dev/null
+++ b/test/lib/spec/dsl/example_matcher.rb
@@ -0,0 +1,40 @@
+module Spec
+ module DSL
+ class ExampleMatcher
+
+ attr_writer :example_desc
+ def initialize(behaviour_desc, example_desc=nil)
+ @behaviour_desc = behaviour_desc
+ @example_desc = example_desc
+ end
+
+ def matches?(specified_examples)
+ specified_examples.each do |specified_example|
+ return true if matches_literal_example?(specified_example) || matches_example_not_considering_modules?(specified_example)
+ end
+ false
+ end
+
+ private
+ def matches_literal_example?(specified_example)
+ specified_example =~ /(^#{context_regexp} #{example_regexp}$|^#{context_regexp}$|^#{example_regexp}$)/
+ end
+
+ def matches_example_not_considering_modules?(specified_example)
+ specified_example =~ /(^#{context_regexp_not_considering_modules} #{example_regexp}$|^#{context_regexp_not_considering_modules}$|^#{example_regexp}$)/
+ end
+
+ def context_regexp
+ Regexp.escape(@behaviour_desc)
+ end
+
+ def context_regexp_not_considering_modules
+ Regexp.escape(@behaviour_desc.split('::').last)
+ end
+
+ def example_regexp
+ Regexp.escape(@example_desc)
+ end
+ end
+ end
+end
diff --git a/test/lib/spec/dsl/example_should_raise_handler.rb b/test/lib/spec/dsl/example_should_raise_handler.rb
new file mode 100644
index 000000000..942327317
--- /dev/null
+++ b/test/lib/spec/dsl/example_should_raise_handler.rb
@@ -0,0 +1,74 @@
+module Spec
+ module DSL
+ class ExampleShouldRaiseHandler
+ 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 = "example block expected #{@expected_error_class.to_s}"
+ else
+ message = "example 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/expectations.rb b/test/lib/spec/expectations.rb
index cc58bba15..65ea47425 100644
--- a/test/lib/spec/expectations.rb
+++ b/test/lib/spec/expectations.rb
@@ -1,9 +1,6 @@
-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
@@ -56,4 +53,4 @@ module Spec
end
end
end
-end \ No newline at end of file
+end
diff --git a/test/lib/spec/expectations/differs/default.rb b/test/lib/spec/expectations/differs/default.rb
index e08325728..87e59b3a6 100644
--- a/test/lib/spec/expectations/differs/default.rb
+++ b/test/lib/spec/expectations/differs/default.rb
@@ -10,7 +10,6 @@ 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)
diff --git a/test/lib/spec/expectations/extensions.rb b/test/lib/spec/expectations/extensions.rb
index 0381dc7f3..60c9b9e7d 100644
--- a/test/lib/spec/expectations/extensions.rb
+++ b/test/lib/spec/expectations/extensions.rb
@@ -1,3 +1,2 @@
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
index dd5498fdd..f59af722e 100644
--- a/test/lib/spec/expectations/extensions/object.rb
+++ b/test/lib/spec/expectations/extensions/object.rb
@@ -7,6 +7,7 @@ module Spec
# :call-seq:
# should(matcher)
# should == expected
+ # should === expected
# should =~ expected
#
# receiver.should(matcher)
@@ -15,6 +16,9 @@ module Spec
# receiver.should == expected #any value
# => Passes if (receiver == expected)
#
+ # receiver.should === expected #any value
+ # => Passes if (receiver === expected)
+ #
# receiver.should =~ regexp
# => Passes if (receiver =~ regexp)
#
@@ -26,12 +30,13 @@ module Spec
# Instead, use receiver.should_not == expected
def should(matcher=nil, &block)
return ExpectationMatcherHandler.handle_matcher(self, matcher, &block) if matcher
- Should::Should.new(self)
+ Spec::Matchers::PositiveOperatorMatcher.new(self)
end
# :call-seq:
# should_not(matcher)
# should_not == expected
+ # should_not === expected
# should_not =~ expected
#
# receiver.should_not(matcher)
@@ -40,70 +45,22 @@ module Spec
# receiver.should_not == expected
# => Passes unless (receiver == expected)
#
+ # 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
+ Spec::Matchers::NegativeOperatorMatcher.new(self)
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/string_and_symbol.rb b/test/lib/spec/expectations/extensions/string_and_symbol.rb
index 30f60d4d0..29cfbddfa 100644
--- a/test/lib/spec/expectations/extensions/string_and_symbol.rb
+++ b/test/lib/spec/expectations/extensions/string_and_symbol.rb
@@ -2,7 +2,7 @@ module Spec
module Expectations
module StringHelpers
def starts_with?(prefix)
- to_s[0..(prefix.length - 1)] == prefix
+ to_s[0..(prefix.to_s.length - 1)] == prefix.to_s
end
end
end
@@ -14,4 +14,4 @@ end
class Symbol
include Spec::Expectations::StringHelpers
-end \ No newline at end of file
+end
diff --git a/test/lib/spec/expectations/handler.rb b/test/lib/spec/expectations/handler.rb
index 9d3fd1f88..4caa321e4 100644
--- a/test/lib/spec/expectations/handler.rb
+++ b/test/lib/spec/expectations/handler.rb
@@ -11,11 +11,9 @@ module Spec
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
+ match = matcher.matches?(actual, &block)
+ ::Spec::Matchers.generated_description = "should #{describe(matcher)}"
+ Spec::Expectations.fail_with(matcher.failure_message) unless match
end
end
end
@@ -24,20 +22,18 @@ module Spec
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
+ 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
diff --git a/test/lib/spec/extensions.rb b/test/lib/spec/extensions.rb
new file mode 100755
index 000000000..824f03bfb
--- /dev/null
+++ b/test/lib/spec/extensions.rb
@@ -0,0 +1 @@
+require 'spec/extensions/object'
diff --git a/test/lib/spec/extensions/object.rb b/test/lib/spec/extensions/object.rb
new file mode 100755
index 000000000..6218aa770
--- /dev/null
+++ b/test/lib/spec/extensions/object.rb
@@ -0,0 +1,6 @@
+class Object
+ def args_and_options(*args)
+ options = Hash === args.last ? args.pop : {}
+ return args, options
+ end
+end
diff --git a/test/lib/spec/matchers.rb b/test/lib/spec/matchers.rb
index 9db24d486..fd208d628 100644
--- a/test/lib/spec/matchers.rb
+++ b/test/lib/spec/matchers.rb
@@ -1,5 +1,3 @@
-require 'spec/deprecated'
-require 'spec/callback'
require 'spec/matchers/be'
require 'spec/matchers/be_close'
require 'spec/matchers/change'
@@ -13,6 +11,7 @@ require 'spec/matchers/raise_error'
require 'spec/matchers/respond_to'
require 'spec/matchers/satisfy'
require 'spec/matchers/throw_symbol'
+require 'spec/matchers/operator_matcher'
module Spec
@@ -62,9 +61,9 @@ module Spec
# 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
+ # == Custom Expectation Matchers
#
- # When you find that none of the stock Expression Matchers provide a natural
+ # When you find that none of the stock Expectation 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
@@ -87,15 +86,15 @@ module Spec
# def initialize(expected)
# @expected = expected
# end
- # def matches?(actual)
- # @actual = actual
- # bob.current_zone.eql?(Zone.new(@expected))
+ # def matches?(target)
+ # @target = target
+ # @target.current_zone.eql?(Zone.new(@expected))
# end
# def failure_message
- # "expected #{@actual.inspect} to be in Zone #{@expected}"
+ # "expected #{@target.inspect} to be in Zone #{@expected}"
# end
# def negative_failure_message
- # "expected #{@actual.inspect} not to be in Zone #{@expected}"
+ # "expected #{@target.inspect} not to be in Zone #{@expected}"
# end
# end
#
@@ -119,18 +118,40 @@ module Spec
# end
# end
#
- # context "Player behaviour" do
+ # describe "Player behaviour" do
# include CustomGameMatchers
# ...
# end
+ #
+ # or you can include in globally in a spec_helper.rb file <tt>require</tt>d
+ # from your spec file(s):
+ #
+ # Spec::Runner.configure do |config|
+ # config.include(CustomGameMatchers)
+ # end
+ #
module Matchers
-
- class << self
- callback_events :description_generated
+ module ModuleMethods
+ def description_generated(callback)
+ description_generated_callbacks << callback
+ end
+
+ def unregister_description_generated(callback)
+ description_generated_callbacks.delete(callback)
+ end
+
def generated_description=(name)
- notify_callbacks(:description_generated, name)
+ description_generated_callbacks.each do |callback|
+ callback.call(name)
+ end
+ end
+
+ private
+ def description_generated_callbacks
+ @description_generated_callbacks ||= []
end
end
+ extend ModuleMethods
def method_missing(sym, *args, &block) # :nodoc:
return Matchers::Be.new(sym, *args) if sym.starts_with?("be_")
@@ -138,23 +159,8 @@ module Spec
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
+end
diff --git a/test/lib/spec/matchers/be.rb b/test/lib/spec/matchers/be.rb
index 957f23de8..0eb1629a6 100644
--- a/test/lib/spec/matchers/be.rb
+++ b/test/lib/spec/matchers/be.rb
@@ -2,8 +2,8 @@ module Spec
module Matchers
class Be #:nodoc:
- def initialize(expected=nil, *args)
- @expected = parse_expected(expected)
+ def initialize(*args)
+ @expected = parse_expected(args.shift)
@args = args
@comparison = ""
end
@@ -57,8 +57,24 @@ module Spec
return @actual <= @expected if @less_than_or_equal
return @actual >= @expected if @greater_than_or_equal
return @actual > @expected if @greater_than
+ return @actual == @expected if @double_equal
+ return @actual === @expected if @triple_equal
return @actual.equal?(@expected)
end
+
+ def ==(expected)
+ @double_equal = true
+ @comparison = "== "
+ @expected = expected
+ self
+ end
+
+ def ===(expected)
+ @triple_equal = true
+ @comparison = "=== "
+ @expected = expected
+ self
+ end
def <(expected)
@less_than = true
@@ -89,19 +105,25 @@ module Spec
end
def description
- "be #{@comparison}#{@expected}"
+ "#{prefix_to_sentence}#{comparison}#{expected_to_sentence}#{args_to_sentence}"
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)
+ @handling_predicate = true
+ ["be_an_","be_a_","be_"].each do |@prefix|
+ return "#{expected.to_s.sub(@prefix,"")}".to_sym if expected.starts_with?(@prefix)
end
end
+ @prefix = "be "
return expected
end
+
+ def handling_predicate?
+ return false if [:true, :false, :nil].include?(@expected)
+ return @handling_predicate
+ end
def predicate
"#{@expected.to_s}?".to_sym
@@ -113,14 +135,37 @@ module Spec
def args_to_s
return "" if @args.empty?
- transformed_args = @args.collect{|a| a.inspect}
- return "(#{transformed_args.join(', ')})"
+ inspected_args = @args.collect{|a| a.inspect}
+ return "(#{inspected_args.join(', ')})"
end
- def handling_predicate?
- return false if [:true, :false, :nil].include?(@expected)
- return @handling_predicate
+ def comparison
+ @comparison
+ end
+
+ def expected_to_sentence
+ split_words(@expected)
+ end
+
+ def prefix_to_sentence
+ split_words(@prefix)
end
+
+ def split_words(sym)
+ sym.to_s.gsub(/_/,' ')
+ end
+
+ def args_to_sentence
+ case @args.length
+ when 0
+ ""
+ when 1
+ " #{@args[0]}"
+ else
+ " #{@args[0...-1].join(', ')} and #{@args[-1]}"
+ end
+ end
+
end
# :call-seq:
diff --git a/test/lib/spec/matchers/be_close.rb b/test/lib/spec/matchers/be_close.rb
index b09e3fd2f..7763eb97e 100644
--- a/test/lib/spec/matchers/be_close.rb
+++ b/test/lib/spec/matchers/be_close.rb
@@ -13,11 +13,11 @@ module Spec
end
def failure_message
- "expected #{@expected} +/- (<#{@delta}), got #{@actual}"
+ "expected #{@expected} +/- (< #{@delta}), got #{@actual}"
end
def description
- "be close to #{@expected} (+- #{@delta})"
+ "be close to #{@expected} (within +- #{@delta})"
end
end
@@ -34,4 +34,4 @@ module Spec
Matchers::BeClose.new(expected, delta)
end
end
-end \ No newline at end of file
+end
diff --git a/test/lib/spec/matchers/eql.rb b/test/lib/spec/matchers/eql.rb
index caca1f7c6..280ca5454 100644
--- a/test/lib/spec/matchers/eql.rb
+++ b/test/lib/spec/matchers/eql.rb
@@ -40,4 +40,4 @@ module Spec
Matchers::Eql.new(expected)
end
end
-end \ No newline at end of file
+end
diff --git a/test/lib/spec/matchers/equal.rb b/test/lib/spec/matchers/equal.rb
index e987e73cb..4bfc74951 100644
--- a/test/lib/spec/matchers/equal.rb
+++ b/test/lib/spec/matchers/equal.rb
@@ -40,4 +40,4 @@ module Spec
Matchers::Equal.new(expected)
end
end
-end \ No newline at end of file
+end
diff --git a/test/lib/spec/matchers/have.rb b/test/lib/spec/matchers/have.rb
index 81f9af3e3..f28b86ad3 100644
--- a/test/lib/spec/matchers/have.rb
+++ b/test/lib/spec/matchers/have.rb
@@ -23,54 +23,56 @@ module Spec
end
def matches?(collection_owner)
- if collection_owner.respond_to?(collection_name)
- collection = collection_owner.send(collection_name, *@args, &@block)
+ 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)
+ collection_owner.send(@collection_name, *@args, &@block)
end
- @actual = collection.length if collection.respond_to?(:length)
@actual = collection.size if collection.respond_to?(:size)
+ @actual = collection.length if collection.respond_to?(:length)
+ raise not_a_collection if @actual.nil?
return @actual >= @expected if @relativity == :at_least
return @actual <= @expected if @relativity == :at_most
return @actual == @expected
end
+
+ def not_a_collection
+ "expected #{@collection_name} to be a collection but it does not respond to #length or #size"
+ end
def failure_message
- "expected #{relative_expectation} #{collection_name}, got #{@actual}"
+ "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}"
+ 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}
+ should_not have_at_most(#{@expected}).#{@collection_name}
We recommend that you use this instead:
- should have_at_least(#{@expected + 1}).#{collection_name}
+ 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}
+ should_not have_at_least(#{@expected}).#{@collection_name}
We recommend that you use this instead:
- should have_at_most(#{@expected - 1}).#{collection_name}
+ should have_at_most(#{@expected - 1}).#{@collection_name}
EOF
end
end
def description
- "have #{relative_expectation} #{collection_name}"
+ "have #{relative_expectation} #{@collection_name}"
end
private
- def collection_name
- @collection_name
- end
def relative_expectation
"#{relativities[@relativity]}#{@expected}"
@@ -137,4 +139,4 @@ EOF
Matchers::Have.new(n, :at_most)
end
end
-end \ No newline at end of file
+end
diff --git a/test/lib/spec/matchers/include.rb b/test/lib/spec/matchers/include.rb
index 0d387f323..5476f97d8 100644
--- a/test/lib/spec/matchers/include.rb
+++ b/test/lib/spec/matchers/include.rb
@@ -3,13 +3,16 @@ module Spec
class Include #:nodoc:
- def initialize(expected)
- @expected = expected
+ def initialize(*expecteds)
+ @expecteds = expecteds
end
def matches?(actual)
@actual = actual
- actual.include?(@expected)
+ @expecteds.each do |expected|
+ return false unless actual.include?(expected)
+ end
+ true
end
def failure_message
@@ -21,12 +24,26 @@ module Spec
end
def description
- "include #{@expected.inspect}"
+ "include #{_pretty_print(@expecteds)}"
end
private
def _message(maybe_not="")
- "expected #{@actual.inspect} #{maybe_not}to include #{@expected.inspect}"
+ "expected #{@actual.inspect} #{maybe_not}to include #{_pretty_print(@expecteds)}"
+ end
+
+ def _pretty_print(array)
+ result = ""
+ array.each_with_index do |item, index|
+ if index < (array.length - 2)
+ result << "#{item.inspect}, "
+ elsif index < (array.length - 1)
+ result << "#{item.inspect} and "
+ else
+ result << "#{item.inspect}"
+ end
+ end
+ result
end
end
@@ -35,16 +52,19 @@ module Spec
# should_not include(expected)
#
# Passes if actual includes expected. This works for
- # collections and Strings
+ # collections and Strings. You can also pass in multiple args
+ # and it will only pass if all args are found in collection.
#
# == Examples
#
# [1,2,3].should include(3)
+ # [1,2,3].should include(2,3) #would pass
+ # [1,2,3].should include(2,3,4) #would fail
# [1,2,3].should_not include(4)
# "spread".should include("read")
# "spread".should_not include("red")
- def include(expected)
- Matchers::Include.new(expected)
+ def include(*expected)
+ Matchers::Include.new(*expected)
end
end
end
diff --git a/test/lib/spec/matchers/operator_matcher.rb b/test/lib/spec/matchers/operator_matcher.rb
new file mode 100755
index 000000000..2d47ea85a
--- /dev/null
+++ b/test/lib/spec/matchers/operator_matcher.rb
@@ -0,0 +1,72 @@
+module Spec
+ module Matchers
+ class BaseOperatorMatcher
+
+ def initialize(target)
+ @target = target
+ end
+
+ def ==(expected)
+ @expected = expected
+ __delegate_method_missing_to_target("==", expected)
+ end
+
+ def ===(expected)
+ @expected = expected
+ __delegate_method_missing_to_target("===", expected)
+ end
+
+ def =~(expected)
+ @expected = expected
+ __delegate_method_missing_to_target("=~", expected)
+ end
+
+ def >(expected)
+ @expected = expected
+ __delegate_method_missing_to_target(">", expected)
+ end
+
+ def >=(expected)
+ @expected = expected
+ __delegate_method_missing_to_target(">=", expected)
+ end
+
+ def <(expected)
+ @expected = expected
+ __delegate_method_missing_to_target("<", expected)
+ end
+
+ def <=(expected)
+ @expected = expected
+ __delegate_method_missing_to_target("<=", expected)
+ end
+
+ def fail_with_message(message)
+ Spec::Expectations.fail_with(message, @expected, @target)
+ end
+
+ end
+
+ class PositiveOperatorMatcher < BaseOperatorMatcher #:nodoc:
+
+ def __delegate_method_missing_to_target(operator, expected)
+ ::Spec::Matchers.generated_description = "should #{operator} #{expected.inspect}"
+ return if @target.send(operator, expected)
+ return fail_with_message("expected: #{expected.inspect},\n got: #{@target.inspect} (using #{operator})") if ['==','===', '=~'].include?(operator)
+ return fail_with_message("expected: #{operator} #{expected.inspect},\n got: #{operator.gsub(/./, ' ')} #{@target.inspect}")
+ end
+
+ end
+
+ class NegativeOperatorMatcher < BaseOperatorMatcher #:nodoc:
+
+ def __delegate_method_missing_to_target(operator, expected)
+ ::Spec::Matchers.generated_description = "should not #{operator} #{expected.inspect}"
+ return unless @target.send(operator, expected)
+ return fail_with_message("expected not: #{operator} #{expected.inspect},\n got: #{operator.gsub(/./, ' ')} #{@target.inspect}")
+ end
+
+ end
+
+ end
+end
diff --git a/test/lib/spec/matchers/raise_error.rb b/test/lib/spec/matchers/raise_error.rb
index 95e82ad5e..b45dcf65c 100644
--- a/test/lib/spec/matchers/raise_error.rb
+++ b/test/lib/spec/matchers/raise_error.rb
@@ -2,9 +2,14 @@ module Spec
module Matchers
class RaiseError #:nodoc:
- def initialize(exception=Exception, message=nil)
- @expected_error = exception
- @expected_message = message
+ def initialize(error_or_message=Exception, message=nil)
+ if String === error_or_message
+ @expected_error = Exception
+ @expected_message = error_or_message
+ else
+ @expected_error = error_or_message
+ @expected_message = message
+ end
end
def matches?(proc)
@@ -24,7 +29,7 @@ module Spec
@raised_other = true
end
else
- if @actual_error.message == @expected_message
+ if @expected_message == @actual_error.message
@raised_expected_error = true
else
@raised_other = true
diff --git a/test/lib/spec/matchers/respond_to.rb b/test/lib/spec/matchers/respond_to.rb
index 013a36f1d..3d23422aa 100644
--- a/test/lib/spec/matchers/respond_to.rb
+++ b/test/lib/spec/matchers/respond_to.rb
@@ -2,34 +2,44 @@ module Spec
module Matchers
class RespondTo #:nodoc:
- def initialize(sym)
- @sym = sym
+ def initialize(*names)
+ @names = names
+ @names_not_responded_to = []
end
def matches?(target)
- return target.respond_to?(@sym)
+ @names.each do |name|
+ unless target.respond_to?(name)
+ @names_not_responded_to << name
+ end
+ end
+ return @names_not_responded_to.empty?
end
def failure_message
- "expected target to respond to #{@sym.inspect}"
+ "expected target to respond to #{@names_not_responded_to.collect {|name| name.inspect }.join(', ')}"
end
def negative_failure_message
- "expected target not to respond to #{@sym.inspect}"
+ "expected target not to respond to #{@names.collect {|name| name.inspect }.join(', ')}"
end
def description
- "respond to ##{@sym.to_s}"
+ "respond to ##{@names.to_s}"
end
end
# :call-seq:
- # should respond_to(:sym)
- # should_not respond_to(:sym)
+ # should respond_to(*names)
+ # should_not respond_to(*names)
#
- # Matches if the target object responds to :sym
- def respond_to(sym)
- Matchers::RespondTo.new(sym)
+ # Matches if the target object responds to all of the names
+ # provided. Names can be Strings or Symbols.
+ #
+ # == Examples
+ #
+ def respond_to(*names)
+ Matchers::RespondTo.new(*names)
end
end
end
diff --git a/test/lib/spec/matchers/throw_symbol.rb b/test/lib/spec/matchers/throw_symbol.rb
index 6732f6fed..6d047bc39 100644
--- a/test/lib/spec/matchers/throw_symbol.rb
+++ b/test/lib/spec/matchers/throw_symbol.rb
@@ -10,7 +10,7 @@ module Spec
begin
proc.call
rescue NameError => e
- @actual = extract_sym_from_name_error(e)
+ @actual = e.name.to_sym
ensure
if @expected.nil?
return @actual.nil? ? false : true
@@ -46,9 +46,6 @@ module Spec
@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:
diff --git a/test/lib/spec/mocks.rb b/test/lib/spec/mocks.rb
index d0a5d0299..66cbafb3c 100644
--- a/test/lib/spec/mocks.rb
+++ b/test/lib/spec/mocks.rb
@@ -1,5 +1,7 @@
require 'spec/mocks/methods'
-require 'spec/mocks/mock_handler'
+require 'spec/mocks/argument_constraint_matchers'
+require 'spec/mocks/spec_methods'
+require 'spec/mocks/proxy'
require 'spec/mocks/mock'
require 'spec/mocks/argument_expectation'
require 'spec/mocks/message_expectation'
@@ -7,6 +9,8 @@ require 'spec/mocks/order_group'
require 'spec/mocks/errors'
require 'spec/mocks/error_generator'
require 'spec/mocks/extensions/object'
+require 'spec/mocks/space'
+
module Spec
# == Mocks and Stubs
@@ -125,13 +129,12 @@ module Spec
# 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(no_args())
+ # my_mock.should_receive(:sym).with(any_args())
+ # my_mock.should_receive(:sym).with(1, an_instance_of(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, /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, 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
#
@@ -201,32 +204,5 @@ module Spec
#
# 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
+end
diff --git a/test/lib/spec/mocks/argument_constraint_matchers.rb b/test/lib/spec/mocks/argument_constraint_matchers.rb
new file mode 100644
index 000000000..0e4777082
--- /dev/null
+++ b/test/lib/spec/mocks/argument_constraint_matchers.rb
@@ -0,0 +1,27 @@
+module Spec
+ module Mocks
+ module ArgumentConstraintMatchers
+
+ # Shortcut for creating an instance of Spec::Mocks::DuckTypeArgConstraint
+ def duck_type(*args)
+ DuckTypeArgConstraint.new(*args)
+ end
+
+ def any_args
+ AnyArgsConstraint.new
+ end
+
+ def anything
+ AnyArgConstraint.new(nil)
+ end
+
+ def boolean
+ BooleanArgConstraint.new(nil)
+ end
+
+ def no_args
+ NoArgsConstraint.new
+ end
+ end
+ end
+end
diff --git a/test/lib/spec/mocks/argument_expectation.rb b/test/lib/spec/mocks/argument_expectation.rb
index a4870e767..5da069b87 100644
--- a/test/lib/spec/mocks/argument_expectation.rb
+++ b/test/lib/spec/mocks/argument_expectation.rb
@@ -36,11 +36,32 @@ module Spec
def initialize(ignore)
end
+ def ==(other)
+ true
+ end
+
+ # TODO - need this?
def matches?(value)
true
end
end
+ class AnyArgsConstraint
+ def description
+ "any args"
+ end
+ end
+
+ class NoArgsConstraint
+ def description
+ "no args"
+ end
+
+ def ==(args)
+ args == []
+ end
+ end
+
class NumericArgConstraint
def initialize(ignore)
end
@@ -54,6 +75,10 @@ module Spec
def initialize(ignore)
end
+ def ==(value)
+ matches?(value)
+ end
+
def matches?(value)
return true if value.is_a?(TrueClass)
return true if value.is_a?(FalseClass)
@@ -71,12 +96,16 @@ module Spec
end
class DuckTypeArgConstraint
- def initialize(*methods_to_respond_do)
- @methods_to_respond_do = methods_to_respond_do
+ def initialize(*methods_to_respond_to)
+ @methods_to_respond_to = methods_to_respond_to
end
def matches?(value)
- @methods_to_respond_do.all? { |sym| value.respond_to?(sym) }
+ @methods_to_respond_to.all? { |sym| value.respond_to?(sym) }
+ end
+
+ def description
+ "duck_type"
end
end
@@ -90,8 +119,14 @@ module Spec
def initialize(args)
@args = args
- if [:any_args] == args then @expected_params = nil
- elsif [:no_args] == args then @expected_params = []
+ if [:any_args] == args
+ @expected_params = nil
+ warn_deprecated(:any_args.inspect, "any_args()")
+ elsif args.length == 1 && args[0].is_a?(AnyArgsConstraint) then @expected_params = nil
+ elsif [:no_args] == args
+ @expected_params = []
+ warn_deprecated(:no_args.inspect, "no_args()")
+ elsif args.length == 1 && args[0].is_a?(NoArgsConstraint) then @expected_params = []
else @expected_params = process_arg_constraints(args)
end
end
@@ -102,9 +137,25 @@ module Spec
end
end
+ def warn_deprecated(deprecated_method, instead)
+ STDERR.puts "The #{deprecated_method} constraint is deprecated. Use #{instead} instead."
+ end
+
def convert_constraint(constraint)
- return @@constraint_classes[constraint].new(constraint) if constraint.is_a?(Symbol)
- return constraint if constraint.is_a?(DuckTypeArgConstraint)
+ if [:anything, :numeric, :boolean, :string].include?(constraint)
+ case constraint
+ when :anything
+ instead = "anything()"
+ when :boolean
+ instead = "boolean()"
+ when :numeric
+ instead = "an_instance_of(Numeric)"
+ when :string
+ instead = "an_instance_of(String)"
+ end
+ warn_deprecated(constraint.inspect, instead)
+ return @@constraint_classes[constraint].new(constraint)
+ end
return MatcherConstraint.new(constraint) if is_matcher?(constraint)
return RegexpArgConstraint.new(constraint) if constraint.is_a?(Regexp)
return LiteralArgConstraint.new(constraint)
diff --git a/test/lib/spec/mocks/error_generator.rb b/test/lib/spec/mocks/error_generator.rb
index 950864a7a..01d8f720d 100644
--- a/test/lib/spec/mocks/error_generator.rb
+++ b/test/lib/spec/mocks/error_generator.rb
@@ -17,8 +17,7 @@ module Spec
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))
+ expected_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
@@ -45,7 +44,7 @@ module Spec
private
def intro
- @name ? "Mock '#{@name}'" : @target.to_s
+ @name ? "Mock '#{@name}'" : @target.inspect
end
def __raise(message)
@@ -82,4 +81,4 @@ module Spec
end
end
-end \ No newline at end of file
+end
diff --git a/test/lib/spec/mocks/message_expectation.rb b/test/lib/spec/mocks/message_expectation.rb
index 152e65a47..74ade3c58 100644
--- a/test/lib/spec/mocks/message_expectation.rb
+++ b/test/lib/spec/mocks/message_expectation.rb
@@ -13,7 +13,7 @@ module Spec
@return_block = lambda {}
@received_count = 0
@expected_received_count = expected_received_count
- @args_expectation = ArgumentExpectation.new([:any_args])
+ @args_expectation = ArgumentExpectation.new([AnyArgsConstraint.new])
@consecutive = false
@exception_to_raise = nil
@symbol_to_throw = nil
@@ -42,6 +42,17 @@ module Spec
@return_block = block_given? ? return_block : lambda { value }
end
+ # :call-seq:
+ # and_raise()
+ # and_raise(Exception) #any exception class
+ # and_raise(exception) #any exception object
+ #
+ # == Warning
+ #
+ # When you pass an exception class, the MessageExpectation will
+ # raise an instance of it, creating it with +new+. If the exception
+ # class initializer requires any parameters, you must pass in an
+ # instance and not the class.
def and_raise(exception=Exception)
@exception_to_raise = exception
end
diff --git a/test/lib/spec/mocks/methods.rb b/test/lib/spec/mocks/methods.rb
index a5f102fcf..3d898cf31 100644
--- a/test/lib/spec/mocks/methods.rb
+++ b/test/lib/spec/mocks/methods.rb
@@ -2,39 +2,38 @@ 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)
+ __mock_proxy.add_message_expectation(opts[:expected_from] || caller(1)[0], sym.to_sym, opts, &block)
end
def should_not_receive(sym, &block)
- __mock_handler.add_negative_message_expectation(caller(1)[0], sym, &block)
+ __mock_proxy.add_negative_message_expectation(caller(1)[0], sym.to_sym, &block)
end
def stub!(sym)
- __mock_handler.add_stub(caller(1)[0], sym)
+ __mock_proxy.add_stub(caller(1)[0], sym.to_sym)
end
def received_message?(sym, *args, &block) #:nodoc:
- __mock_handler.received_message?(sym, *args, &block)
+ __mock_proxy.received_message?(sym.to_sym, *args, &block)
end
- def __verify #:nodoc:
- __mock_handler.verify
+ def rspec_verify #:nodoc:
+ __mock_proxy.verify
end
- def __reset_mock #:nodoc:
- __mock_handler.reset
+ def rspec_reset #:nodoc:
+ __mock_proxy.reset
end
- def method_missing(sym, *args, &block) #:nodoc:
- __mock_handler.instance_eval {@messages_received << [sym, args, block]}
- super(sym, *args, &block)
- end
-
- private
+ private
- def __mock_handler
- @mock_handler ||= MockHandler.new(self, @name, @options)
+ def __mock_proxy
+ if Mock === self
+ @mock_proxy ||= Proxy.new(self, @name, @options)
+ else
+ @mock_proxy ||= Proxy.new(self, self.class.name)
+ end
end
end
end
-end \ No newline at end of file
+end
diff --git a/test/lib/spec/mocks/mock.rb b/test/lib/spec/mocks/mock.rb
index 68de11ff4..aa380e0af 100644
--- a/test/lib/spec/mocks/mock.rb
+++ b/test/lib/spec/mocks/mock.rb
@@ -12,15 +12,18 @@ module Spec
end
def method_missing(sym, *args, &block)
- __mock_handler.instance_eval {@messages_received << [sym, args, block]}
+ __mock_proxy.instance_eval {@messages_received << [sym, args, block]}
begin
- return self if __mock_handler.null_object?
+ return self if __mock_proxy.null_object?
super(sym, *args, &block)
rescue NoMethodError
- __mock_handler.raise_unexpected_message_error sym, *args
+ __mock_proxy.raise_unexpected_message_error sym, *args
end
end
+ def inspect
+ "#<#{self.class}:#{sprintf '0x%x', self.object_id} @name=#{@name.inspect}>"
+ end
end
end
-end \ No newline at end of file
+end
diff --git a/test/lib/spec/mocks/proxy.rb b/test/lib/spec/mocks/proxy.rb
new file mode 100644
index 000000000..6c79d1068
--- /dev/null
+++ b/test/lib/spec/mocks/proxy.rb
@@ -0,0 +1,167 @@
+module Spec
+ module Mocks
+ class Proxy
+ DEFAULT_OPTIONS = {
+ :null_object => false,
+ }
+
+ 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 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 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 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(sym, block)
+ $rspec_mocks.add(@target) unless $rspec_mocks.nil?
+ define_expected_method(sym)
+ end
+
+ def define_expected_method(sym)
+ if target_responds_to?(sym) && !@proxied_methods.include?(sym)
+ metaclass.__send__(:alias_method, munge(sym), sym) if metaclass.instance_methods.include?(sym.to_s)
+ @proxied_methods << sym
+ end
+
+ metaclass_eval(<<-EOF, __FILE__, __LINE__)
+ def #{sym}(*args, &block)
+ __mock_proxy.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|
+ if metaclass.instance_methods.include?(munge(sym).to_s)
+ metaclass.__send__(:alias_method, sym, munge(sym))
+ metaclass.__send__(:undef_method, munge(sym))
+ else
+ metaclass.__send__(:undef_method, sym)
+ end
+ 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
diff --git a/test/lib/spec/mocks/space.rb b/test/lib/spec/mocks/space.rb
new file mode 100644
index 000000000..e04bc5ccb
--- /dev/null
+++ b/test/lib/spec/mocks/space.rb
@@ -0,0 +1,28 @@
+module Spec
+ module Mocks
+ class Space
+ def add(obj)
+ mocks << obj unless mocks.include?(obj)
+ end
+
+ def verify_all
+ mocks.each do |mock|
+ mock.rspec_verify
+ end
+ end
+
+ def reset_all
+ mocks.each do |mock|
+ mock.rspec_reset
+ end
+ mocks.clear
+ end
+
+ private
+
+ def mocks
+ @mocks ||= []
+ end
+ end
+ end
+end
diff --git a/test/lib/spec/mocks/spec_methods.rb b/test/lib/spec/mocks/spec_methods.rb
new file mode 100644
index 000000000..fd67fd210
--- /dev/null
+++ b/test/lib/spec/mocks/spec_methods.rb
@@ -0,0 +1,30 @@
+module Spec
+ module Mocks
+ module SpecMethods
+ include Spec::Mocks::ArgumentConstraintMatchers
+
+ # 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
+
+ end
+ end
+end
diff --git a/test/lib/spec/rake/spectask.rb b/test/lib/spec/rake/spectask.rb
index 5c9b365c1..f8c6809a9 100644
--- a/test/lib/spec/rake/spectask.rb
+++ b/test/lib/spec/rake/spectask.rb
@@ -21,7 +21,38 @@ module Spec
#
# rake spec
#
+ # If rake is invoked with a "SPEC=filename" command line option,
+ # then the list of spec files will be overridden to include only the
+ # filename specified on the command line. This provides an easy way
+ # to run just one spec.
+ #
+ # If rake is invoked with a "SPEC_OPTS=options" command line option,
+ # then the given options will override the value of the +spec_opts+
+ # attribute.
+ #
+ # If rake is invoked with a "RCOV_OPTS=options" command line option,
+ # then the given options will override the value of the +rcov_opts+
+ # attribute.
+ #
+ # Examples:
+ #
+ # rake spec # run specs normally
+ # rake spec SPEC=just_one_file.rb # run just one spec file.
+ # rake spec SPEC_OPTS="--diff" # enable diffing
+ # rake spec RCOV_OPTS="--aggregate myfile.txt" # see rcov --help for details
+ #
+ # Each attribute of this task may be a proc. This allows for lazy evaluation,
+ # which is sometimes handy if you want to defer the evaluation of an attribute value
+ # until the task is run (as opposed to when it is defined).
class SpecTask < ::Rake::TaskLib
+ class << self
+ def attr_accessor(*names)
+ super(*names)
+ names.each do |name|
+ module_eval "def #{name}() evaluate(@#{name}) end" # Allows use of procs
+ end
+ end
+ end
# Name of spec task. (default is :spec)
attr_accessor :name
@@ -35,20 +66,20 @@ module Spec
attr_accessor :warning
# Glob pattern to match spec files. (default is 'spec/**/*_spec.rb')
+ # Setting the SPEC environment variable overrides this.
attr_accessor :pattern
# Array of commandline options to pass to RSpec. Defaults to [].
+ # Setting the SPEC_OPTS environment variable overrides this.
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
+ # Setting the RCOV_OPTS environment variable overrides this.
attr_accessor :rcov_opts
# Directory where the RCov report is written. Defaults to "coverage"
@@ -62,18 +93,21 @@ module Spec
# Defaults to true.
attr_accessor :fail_on_error
- # A message to print to stdout when there are failures.
+ # A message to print to stderr when there are failures.
attr_accessor :failure_message
+ # Where RSpec's output is written. Defaults to STDOUT.
+ # DEPRECATED. Use --format FORMAT:WHERE in spec_opts.
+ attr_accessor :out
+
# Explicitly define the list of spec files to be included in a
- # spec. +list+ is expected to be an array of file names (a
+ # spec. +spec_files+ 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
+ # Setting the SPEC environment variable overrides this.
+ attr_accessor :spec_files
- # Create a specing task.
+ # Defines a new task, using the name +name+.
def initialize(name=:spec)
@name = name
@libs = [File.expand_path(File.dirname(__FILE__) + '/../../../lib')]
@@ -82,61 +116,63 @@ module Spec
@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?
+ @pattern = 'spec/**/*_spec.rb' if pattern.nil? && spec_files.nil?
define
end
- def define
+ def define # :nodoc:
spec_script = File.expand_path(File.dirname(__FILE__) + '/../../../bin/spec')
- lib_path = @libs.join(File::PATH_SEPARATOR)
+ 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" : "")
+ desc "Run specs" + (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}\""
-
+ task name do
+ RakeFileUtils.verbose(verbose) do
unless spec_file_list.empty?
- # ruby [ruby_opts] -Ilib -S rcov [rcov_opts] bin/spec -- [spec_opts] examples
+ # ruby [ruby_opts] -Ilib -S rcov [rcov_opts] bin/spec -- examples [spec_opts]
# 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
+ # ruby [ruby_opts] -Ilib bin/spec examples [spec_opts]
+ cmd = "ruby "
+
+ rb_opts = ruby_opts.clone
+ rb_opts << "-I\"#{lib_path}\""
+ rb_opts << "-S rcov" if rcov
+ rb_opts << "-w" if warning
+ cmd << rb_opts.join(" ")
+ cmd << " "
+ cmd << rcov_option_list
+ cmd << %[ -o "#{rcov_dir}" ] if rcov
+ cmd << %Q|"#{spec_script}"|
+ cmd << " "
+ cmd << "-- " if rcov
+ cmd << spec_file_list.collect { |fn| %["#{fn}"] }.join(' ')
+ cmd << " "
+ cmd << spec_option_list
+ if out
+ cmd << " "
+ cmd << %Q| > "#{out}"|
+ STDERR.puts "The Spec::Rake::SpecTask#out attribute is DEPRECATED and will be removed in a future version. Use --format FORMAT:WHERE instead."
+ end
+ unless system(cmd)
+ STDERR.puts failure_message if failure_message
+ raise("Command #{cmd} failed") if fail_on_error
end
end
end
end
- if @rcov
+ if rcov
desc "Remove rcov products for #{actual_name}"
task paste("clobber_", actual_name) do
- rm_r @rcov_dir rescue nil
+ rm_r rcov_dir rescue nil
end
clobber_task = paste("clobber_", actual_name)
@@ -148,12 +184,20 @@ module Spec
end
def rcov_option_list # :nodoc:
- return "" unless @rcov
- ENV['RCOVOPTS'] || @rcov_opts.join(" ") || ""
+ return "" unless rcov
+ ENV['RCOV_OPTS'] || rcov_opts.join(" ") || ""
end
def spec_option_list # :nodoc:
- ENV['RSPECOPTS'] || @spec_opts.join(" ") || ""
+ STDERR.puts "RSPECOPTS is DEPRECATED and will be removed in a future version. Use SPEC_OPTS instead." if ENV['RSPECOPTS']
+ ENV['SPEC_OPTS'] || ENV['RSPECOPTS'] || spec_opts.join(" ") || ""
+ end
+
+ def evaluate(o) # :nodoc:
+ case o
+ when Proc then o.call
+ else o
+ end
end
def spec_file_list # :nodoc:
@@ -161,8 +205,8 @@ module Spec
FileList[ ENV['SPEC'] ]
else
result = []
- result += @spec_files.to_a if @spec_files
- result += FileList[ @pattern ].to_a if @pattern
+ result += spec_files.to_a if spec_files
+ result += FileList[ pattern ].to_a if pattern
FileList[result]
end
end
diff --git a/test/lib/spec/rake/verify_rcov.rb b/test/lib/spec/rake/verify_rcov.rb
index a05153e99..9715744e9 100644
--- a/test/lib/spec/rake/verify_rcov.rb
+++ b/test/lib/spec/rake/verify_rcov.rb
@@ -19,10 +19,14 @@ module RCov
# exception.
attr_accessor :threshold
+ # Require the threshold value be met exactly. This is the default.
+ attr_accessor :require_exact_threshold
+
def initialize(name=:verify_rcov)
@name = name
@index_html = 'coverage/index.html'
@verbose = true
+ @require_exact_threshold = true
yield self if block_given?
raise "Threshold must be set" if @threshold.nil?
define
@@ -32,6 +36,7 @@ module RCov
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)
@@ -40,8 +45,8 @@ module RCov
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
+ raise "Coverage has increased above the threshold of #{threshold}% to #{total_coverage}%. You should update your threshold value." if (total_coverage > threshold) and require_exact_threshold
end
end
end
-end \ No newline at end of file
+end
diff --git a/test/lib/spec/runner.rb b/test/lib/spec/runner.rb
index 976802bd1..9d801adc3 100644
--- a/test/lib/spec/runner.rb
+++ b/test/lib/spec/runner.rb
@@ -1,42 +1,38 @@
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/behaviour_runner'
+require 'spec/runner/options'
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
+ # == Behaviours and Examples
#
- # Rather than expressing examples in classes, RSpec uses a custom domain specific language to express
- # examples using contexts and specifications.
+ # Rather than expressing examples in classes, RSpec uses a custom domain specific language to
+ # describe Behaviours and Examples of those behaviours.
#
- # A context is the equivalent of a fixture in xUnit-speak. It is a metaphor for the context
+ # A Behaviour 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.
+ # We begin be describing
#
- # context "A new account" do
+ # describe Account do
#
- # setup do
+ # before do
# @account = Account.new
# end
#
- # specify "should have a balance of $0" do
- # @account.balance.should_eql Money.new(0, :dollars)
+ # it "should have a balance of $0" do
+ # @account.balance.should == Money.new(0, :dollars)
# end
#
# end
#
- # We use the setup block to set up the context (given), and then the specify method to
+ # We use the before block to set up the Behaviour (given), and then the #it method to
# hold the example code that expresses the event (when) and the expected outcome (then).
#
# == Helper Methods
@@ -51,51 +47,52 @@ module Spec
#
# == Setup and Teardown
#
- # You can use setup, teardown, context_setup and context_teardown within a context:
+ # You can use before and after within a Behaviour. Both methods take an optional
+ # scope argument so you can run the block before :each example or before :all examples
#
- # context "..." do
- # context_setup do
+ # describe "..." do
+ # before :all do
# ...
# end
#
- # setup do
+ # before :each do
# ...
# end
#
- # specify "number one" do
+ # it "should do something" do
# ...
# end
#
- # specify "number two" do
+ # it "should do something else" do
# ...
# end
#
- # teardown do
+ # after :each do
# ...
# end
#
- # context_teardown do
+ # after :all 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.
+ # The <tt>before :each</tt> block will run before each of the examples, once for each example. Likewise,
+ # the <tt>after :each</tt> block will run after each of the examples.
#
- # 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
+ # It is also possible to specify a <tt>before :all</tt> and <tt>after :all</tt>
+ # block that will run only once for each behaviour, respectively before the first <code>before :each</code>
+ # and after the last <code>after :each</code>. The use of these is generally discouraged, because it
+ # introduces dependencies between the examples. 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
+ # describe "..." do
#
- # specify "..." do
+ # it "..." do
# helper_method
# end
#
@@ -116,17 +113,53 @@ module Spec
# end
# end
#
- # context "A new account" do
+ # describe "A new account" do
# include AccountExampleHelperMethods
- # setup do
+ # before do
# @account = Account.new
# end
#
- # specify "should have a balance of $0" do
+ # it "should have a balance of $0" do
# helper_method
# @account.balance.should eql(Money.new(0, :dollars))
# end
# end
+ #
+ # == Shared behaviour
+ #
+ # You can define a shared behaviour, that may be used on other behaviours
+ #
+ # describe "All Editions", :shared => true do
+ # it "all editions behaviour" ...
+ # end
+ #
+ # describe SmallEdition do
+ # it_should_behave_like "All Editions"
+ #
+ # it "should do small edition stuff" do
+ # ...
+ # end
+ # end
module Runner
+ class << self
+ def configuration # :nodoc:
+ @configuration ||= Spec::DSL::Configuration.new
+ end
+
+ # Use this to configure various configurable aspects of
+ # RSpec:
+ #
+ # Spec::Runner.configure do |configuration|
+ # # Configure RSpec here
+ # end
+ #
+ # The yielded <tt>configuration</tt> object is a
+ # Spec::DSL::Configuration instance. See its RDoc
+ # for details about what you can do with it.
+ #
+ def configure
+ yield configuration if @configuration.nil?
+ end
+ end
end
end
diff --git a/test/lib/spec/runner/backtrace_tweaker.rb b/test/lib/spec/runner/backtrace_tweaker.rb
index 7300b36b8..aacc2c8b8 100644
--- a/test/lib/spec/runner/backtrace_tweaker.rb
+++ b/test/lib/spec/runner/backtrace_tweaker.rb
@@ -32,7 +32,9 @@ module Spec
# TextMate's Ruby and RSpec plugins
/Ruby\.tmbundle\/Support\/tmruby.rb:/,
/RSpec\.tmbundle\/Support\/lib/,
- /temp_textmate\./
+ /temp_textmate\./,
+ /mock_frameworks\/rspec/,
+ /spec_server/
]
end
@@ -52,4 +54,4 @@ module Spec
end
end
end
-end \ No newline at end of file
+end
diff --git a/test/lib/spec/runner/behaviour_runner.rb b/test/lib/spec/runner/behaviour_runner.rb
new file mode 100644
index 000000000..1ac891f3c
--- /dev/null
+++ b/test/lib/spec/runner/behaviour_runner.rb
@@ -0,0 +1,123 @@
+module Spec
+ module Runner
+ class BehaviourRunner
+
+ def initialize(options, arg=nil)
+ @behaviours = []
+ @options = options
+ end
+
+ def add_behaviour(behaviour)
+ if !specified_examples.nil? && !specified_examples.empty?
+ behaviour.retain_examples_matching!(specified_examples)
+ end
+ @behaviours << behaviour if behaviour.number_of_examples != 0 && !behaviour.shared?
+ end
+
+ # Runs all behaviours and returns the number of failures.
+ def run(paths, exit_when_done)
+ prepare!(paths)
+ begin
+ run_behaviours
+ rescue Interrupt
+ ensure
+ report_end
+ end
+ failure_count = report_dump
+
+ heckle if(failure_count == 0 && !@options.heckle_runner.nil?)
+
+ if(exit_when_done)
+ exit_code = (failure_count == 0) ? 0 : 1
+ exit(exit_code)
+ end
+ failure_count
+ end
+
+ def report_end
+ @options.reporter.end
+ end
+
+ def report_dump
+ @options.reporter.dump
+ end
+
+ def prepare!(paths)
+ unless paths.nil? # It's nil when running single specs with ruby
+ paths = find_paths(paths)
+ sorted_paths = sort_paths(paths)
+ load_specs(sorted_paths) # This will populate @behaviours via callbacks to add_behaviour
+ end
+ @options.reporter.start(number_of_examples)
+ @behaviours.reverse! if @options.reverse
+ set_sequence_numbers
+ end
+
+ def run_behaviours
+ @behaviours.each do |behaviour|
+ behaviour.run(@options.reporter, @options.dry_run, @options.reverse, @options.timeout)
+ end
+ end
+
+ def number_of_examples
+ @behaviours.inject(0) {|sum, behaviour| sum + behaviour.number_of_examples}
+ end
+
+ FILE_SORTERS = {
+ 'mtime' => lambda {|file_a, file_b| File.mtime(file_b) <=> File.mtime(file_a)}
+ }
+
+ def sorter(paths)
+ FILE_SORTERS[@options.loadby]
+ end
+
+ def sort_paths(paths)
+ sorter = sorter(paths)
+ paths = paths.sort(&sorter) unless sorter.nil?
+ paths
+ end
+
+ private
+
+ # Sets the #number on each Example
+ def set_sequence_numbers
+ number = 0
+ @behaviours.each do |behaviour|
+ number = behaviour.set_sequence_numbers(number, @options.reverse)
+ end
+ end
+
+ def find_paths(paths)
+ result = []
+ paths.each do |path|
+ if File.directory?(path)
+ result += Dir["#{path}/**/*.rb"]
+ elsif File.file?(path)
+ result << path
+ else
+ raise "File or directory not found: #{path}"
+ end
+ end
+ result
+ end
+
+ def load_specs(paths)
+ paths.each do |path|
+ load path
+ end
+ end
+
+ def specified_examples
+ @options.examples
+ end
+
+ def heckle
+ heckle_runner = @options.heckle_runner
+ @options.heckle_runner = nil
+ behaviour_runner = self.class.new(@options)
+ behaviour_runner.instance_variable_set(:@behaviours, @behaviours)
+ heckle_runner.heckle_with(behaviour_runner)
+ end
+ end
+ end
+end
diff --git a/test/lib/spec/runner/command_line.rb b/test/lib/spec/runner/command_line.rb
index db928ad9b..0d70337e1 100644
--- a/test/lib/spec/runner/command_line.rb
+++ b/test/lib/spec/runner/command_line.rb
@@ -10,25 +10,13 @@ module Spec
# +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
+ old_behaviour_runner = defined?($behaviour_runner) ? $behaviour_runner : nil
+ $behaviour_runner = OptionParser.new.create_behaviour_runner(argv, err, out, warn_if_no_files)
+ return if $behaviour_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
+ $behaviour_runner.run(argv, exit)
+ $behaviour_runner = old_behaviour_runner
end
end
end
-end \ No newline at end of file
+end
diff --git a/test/lib/spec/runner/drb_command_line.rb b/test/lib/spec/runner/drb_command_line.rb
index d4c7d937d..7e745fb71 100644
--- a/test/lib/spec/runner/drb_command_line.rb
+++ b/test/lib/spec/runner/drb_command_line.rb
@@ -9,8 +9,8 @@ module Spec
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)
+ spec_server = DRbObject.new_with_uri("druby://localhost:8989")
+ spec_server.run(argv, stderr, stdout)
rescue DRb::DRbConnError
stderr.puts "No server is running"
exit 1 if exit
@@ -18,4 +18,4 @@ module Spec
end
end
end
-end \ No newline at end of file
+end
diff --git a/test/lib/spec/runner/extensions/kernel.rb b/test/lib/spec/runner/extensions/kernel.rb
index f060ec859..75f2c335e 100644
--- a/test/lib/spec/runner/extensions/kernel.rb
+++ b/test/lib/spec/runner/extensions/kernel.rb
@@ -1,17 +1,50 @@
module Kernel
- def context(name, &block)
- context = Spec::Runner::Context.new(name, &block)
- context_runner.add_context(context)
+ # Creates and registers an instance of a Spec::DSL::Behaviour (or a subclass).
+ # The instantiated behaviour class depends on the directory of the file
+ # calling this method. For example, Spec::Rails will use different
+ # classes for specs living in <tt>spec/models</tt>, <tt>spec/helpers</tt>,
+ # <tt>spec/views</tt> and <tt>spec/controllers</tt>.
+ #
+ # It is also possible to override autodiscovery of the behaviour class
+ # with an options Hash as the last argument:
+ #
+ # describe "name", :behaviour_type => :something_special do ...
+ #
+ # The reason for using different behaviour classes is to have
+ # different matcher methods available from within the <tt>describe</tt>
+ # block.
+ #
+ # See Spec::DSL::BehaviourFactory#add_behaviour_class for details about
+ # how to register special Spec::DSL::Behaviour implementations.
+ #
+ def describe(*args, &block)
+ raise ArgumentError if args.empty?
+ args << {} unless Hash === args.last
+ args.last[:spec_path] = caller(0)[1]
+ register_behaviour(Spec::DSL::BehaviourFactory.create(*args, &block))
end
-
+ alias :context :describe
+
+ def respond_to(*names)
+ Spec::Matchers::RespondTo.new(*names)
+ end
+
private
- def context_runner
+ def register_behaviour(behaviour)
+ if behaviour.shared?
+ Spec::DSL::Behaviour.add_shared_behaviour(behaviour)
+ else
+ behaviour_runner.add_behaviour(behaviour)
+ end
+ end
+
+ def behaviour_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) }; \
+ unless $behaviour_runner; \
+ $behaviour_runner = ::Spec::Runner::OptionParser.new.create_behaviour_runner(ARGV.dup, STDERR, STDOUT, false); \
+ at_exit { $behaviour_runner.run(nil, false) }; \
end
- $context_runner
+ $behaviour_runner
end
end
diff --git a/test/lib/spec/runner/formatter.rb b/test/lib/spec/runner/formatter.rb
index f62e81733..17512d958 100644
--- a/test/lib/spec/runner/formatter.rb
+++ b/test/lib/spec/runner/formatter.rb
@@ -1,5 +1,9 @@
+require 'spec/runner/formatter/base_formatter'
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'
+require 'spec/runner/formatter/failing_examples_formatter'
+require 'spec/runner/formatter/failing_behaviours_formatter'
+require 'spec/runner/formatter/snippet_extractor'
diff --git a/test/lib/spec/runner/formatter/base_formatter.rb b/test/lib/spec/runner/formatter/base_formatter.rb
new file mode 100644
index 000000000..7cc43ef0e
--- /dev/null
+++ b/test/lib/spec/runner/formatter/base_formatter.rb
@@ -0,0 +1,76 @@
+module Spec
+ module Runner
+ module Formatter
+ # Baseclass for formatters that implements all required methods as no-ops.
+ class BaseFormatter
+ def initialize(where)
+ @where = where
+ end
+
+ # This method is invoked before any examples 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_behaviour
+ def start(example_count)
+ end
+
+ # This method is invoked at the beginning of the execution of each behaviour.
+ # +name+ is the name of the behaviour and +first+ is true if it is the
+ # first behaviour - otherwise it's false.
+ #
+ # The next method to be invoked after this is #example_failed or #example_finished
+ def add_behaviour(name)
+ end
+
+ # This method is invoked when an +example+ starts.
+ def example_started(example)
+ end
+
+ # This method is invoked when an +example+ passes.
+ def example_passed(example)
+ end
+
+ # This method is invoked when an +example+ fails, i.e. an exception occurred
+ # inside it (such as a failed should or other exception). +counter+ is the
+ # sequence number of the failure (starting at 1) and +failure+ is the associated
+ # Failure object.
+ def example_failed(example, counter, failure)
+ end
+
+ # This method is invoked when an example is not yet implemented (i.e. has not
+ # been provided a block), or when an ExamplePendingError is raised.
+ # +name+ is the name of the example.
+ # +message+ is the message from the ExamplePendingError, if it exists, or the
+ # default value of "Not Yet Implemented"
+ def example_pending(behaviour_name, example_name, message)
+ end
+
+ # This method is invoked after all of the examples have executed. The next method
+ # to be invoked after this one is #dump_failure (once for each failed example),
+ def start_dump
+ end
+
+ # Dumps detailed information about an example failure.
+ # This method is invoked for each failed example after all examples have run. +counter+ is the sequence number
+ # of the associated example. +failure+ is a Failure object, which contains detailed
+ # information about the failure.
+ def dump_failure(counter, failure)
+ end
+
+ # This method is invoked after the dumping of examples and failures.
+ def dump_summary(duration, example_count, failure_count, pending_count)
+ end
+
+ # This gets invoked after the summary if option is set to do so.
+ def dump_pending
+ end
+
+ # This method is invoked at the very end. Allows the formatter to clean up, like closing open streams.
+ def close
+ end
+ end
+ end
+ end
+end
diff --git a/test/lib/spec/runner/formatter/base_text_formatter.rb b/test/lib/spec/runner/formatter/base_text_formatter.rb
index 31d1c3132..c3cf01b76 100644
--- a/test/lib/spec/runner/formatter/base_text_formatter.rb
+++ b/test/lib/spec/runner/formatter/base_text_formatter.rb
@@ -4,86 +4,96 @@ module Spec
# 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)
+ class BaseTextFormatter < BaseFormatter
+ attr_writer :dry_run
+
+ # Creates a new instance that will write to +where+. If +where+ is a
+ # String, output will be written to the File with that name, otherwise
+ # +where+ is exected to be an IO (or an object that responds to #puts and #write).
+ def initialize(where)
+ super(where)
+ if where.is_a?(String)
+ @output = File.open(where, 'w')
+ elsif where == STDOUT
+ @output = Kernel
+ def @output.flush
+ STDOUT.flush
+ end
+ else
+ @output = where
+ end
+ @colour = false
+ @dry_run = false
+ @snippet_extractor = SnippetExtractor.new
+ @pending_examples = []
end
-
- # This method is invoked when a spec passes. +name+ is the name of the
- # specification.
- def spec_passed(name)
+
+ def example_pending(behaviour_name, example_name, message)
+ @pending_examples << ["#{behaviour_name} #{example_name}", message]
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
+
+ def colour=(colour)
+ @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
- # 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})"
+ @output.puts colourise("#{failure.header}\n#{failure.exception.message}", failure)
+ @output.puts format_backtrace(failure.exception.backtrace)
+ @output.flush
+ end
+
+ def colourise(s, failure)
if(failure.expectation_not_met?)
- @output.puts red(failure.header)
- @output.puts red(failure.exception.message)
+ red(s)
+ elsif(failure.pending_fixed?)
+ blue(s)
else
- @output.puts magenta(failure.header)
- @output.puts magenta(failure.exception.message)
+ magenta(s)
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)
+ def dump_summary(duration, example_count, failure_count, pending_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}"
+
+ summary = "#{example_count} example#{'s' unless example_count == 1}, #{failure_count} failure#{'s' unless failure_count == 1}"
+ summary << ", #{pending_count} pending" if pending_count > 0
+
if failure_count == 0
- @output.puts green(summary)
+ if pending_count > 0
+ @output.puts yellow(summary)
+ else
+ @output.puts green(summary)
+ end
else
@output.puts red(summary)
end
+ @output.flush
+ dump_pending
end
+ def dump_pending
+ unless @pending_examples.empty?
+ @output.puts
+ @output.puts "Pending:"
+ @pending_examples.each do |pending_example|
+ @output.puts "#{pending_example[0]} (#{pending_example[1]})"
+ end
+ end
+ @output.flush
+ end
+
+ def close
+ if IO === @output
+ @output.close
+ end
+ end
+
def format_backtrace(backtrace)
return "" if backtrace.nil?
backtrace.map { |line| backtrace_line(line) }.join("\n")
@@ -108,9 +118,11 @@ module Spec
end
end
- def red(text); colour(text, "\e[31m"); end
def green(text); colour(text, "\e[32m"); end
+ def red(text); colour(text, "\e[31m"); end
def magenta(text); colour(text, "\e[35m"); end
+ def yellow(text); colour(text, "\e[33m"); end
+ def blue(text); colour(text, "\e[34m"); end
end
end
diff --git a/test/lib/spec/runner/formatter/failing_behaviours_formatter.rb b/test/lib/spec/runner/formatter/failing_behaviours_formatter.rb
new file mode 100644
index 000000000..2b3940fd3
--- /dev/null
+++ b/test/lib/spec/runner/formatter/failing_behaviours_formatter.rb
@@ -0,0 +1,29 @@
+module Spec
+ module Runner
+ module Formatter
+ class FailingBehavioursFormatter < BaseTextFormatter
+ def add_behaviour(behaviour_name)
+ if behaviour_name =~ /(.*) \(druby.*\)$/
+ @behaviour_name = $1
+ else
+ @behaviour_name = behaviour_name
+ end
+ end
+
+ def example_failed(example, counter, failure)
+ unless @behaviour_name.nil?
+ @output.puts @behaviour_name
+ @behaviour_name = nil
+ @output.flush
+ end
+ end
+
+ def dump_failure(counter, failure)
+ end
+
+ def dump_summary(duration, example_count, failure_count, pending_count)
+ end
+ end
+ end
+ end
+end
diff --git a/test/lib/spec/runner/formatter/failing_examples_formatter.rb b/test/lib/spec/runner/formatter/failing_examples_formatter.rb
new file mode 100644
index 000000000..9728deaf0
--- /dev/null
+++ b/test/lib/spec/runner/formatter/failing_examples_formatter.rb
@@ -0,0 +1,22 @@
+module Spec
+ module Runner
+ module Formatter
+ class FailingExamplesFormatter < BaseTextFormatter
+ def add_behaviour(behaviour_name)
+ @behaviour_name = behaviour_name
+ end
+
+ def example_failed(example, counter, failure)
+ @output.puts "#{@behaviour_name} #{example.description}"
+ @output.flush
+ end
+
+ def dump_failure(counter, failure)
+ end
+
+ def dump_summary(duration, example_count, failure_count, pending_count)
+ 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
index 13b796581..d9c422e55 100644
--- a/test/lib/spec/runner/formatter/html_formatter.rb
+++ b/test/lib/spec/runner/formatter/html_formatter.rb
@@ -1,106 +1,126 @@
+require 'erb'
+
module Spec
module Runner
module Formatter
class HtmlFormatter < BaseTextFormatter
- attr_reader :current_spec_number, :current_context_number
+ include ERB::Util # for the #h method
- def initialize(output, dry_run=false, colour=false)
+ def initialize(output)
super
- @current_spec_number = 0
- @current_context_number = 0
+ @current_behaviour_number = 0
+ @current_example_number = 0
end
- def start(spec_count)
- @spec_count = spec_count
+ # The number of the currently running behaviour
+ def current_behaviour_number
+ @current_behaviour_number
+ end
+
+ # The number of the currently running example (a global counter)
+ def current_example_number
+ @current_example_number
+ end
+
+ def start(example_count)
+ @example_count = example_count
- @output.puts HEADER_1
- @output.puts extra_header_content unless extra_header_content.nil?
- @output.puts HEADER_2
- STDOUT.flush
+ @output.puts html_header
+ @output.puts report_header
+ @output.flush
end
- def add_context(name, first)
- @current_context_number += 1
- unless first
+ def add_behaviour(name)
+ @behaviour_red = false
+ @behaviour_red = false
+ @current_behaviour_number += 1
+ unless current_behaviour_number == 1
@output.puts " </dl>"
@output.puts "</div>"
end
- @output.puts "<div class=\"context\">"
+ @output.puts "<div class=\"behaviour\">"
@output.puts " <dl>"
- @output.puts " <dt id=\"context_#{@current_context_number}\">#{name}</dt>"
- STDOUT.flush
+ @output.puts " <dt id=\"behaviour_#{current_behaviour_number}\">#{h(name)}</dt>"
+ @output.flush
end
def start_dump
@output.puts " </dl>"
@output.puts "</div>"
- STDOUT.flush
+ @output.flush
end
- def spec_started(name)
- @current_spec_number += 1
- STDOUT.flush
+ def example_started(example)
+ @current_example_number = example.number
end
- def spec_passed(name)
+ def example_passed(example)
move_progress
- @output.puts " <dd class=\"spec passed\"><span class=\"passed_spec_name\">#{escape(name)}</span></dd>"
- STDOUT.flush
+ @output.puts " <dd class=\"spec passed\"><span class=\"passed_spec_name\">#{h(example.description)}</span></dd>"
+ @output.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>"
+ def example_failed(example, counter, failure)
+ extra = extra_failure_content(failure)
+ failure_style = failure.pending_fixed? ? 'pending_fixed' : 'failed'
+ @output.puts " <script type=\"text/javascript\">makeRed('rspec-header');</script>" unless @header_red
+ @header_red = true
+ @output.puts " <script type=\"text/javascript\">makeRed('behaviour_#{current_behaviour_number}');</script>" unless @behaviour_red
+ @behaviour_red = true
move_progress
- @output.puts " <dd class=\"spec failed\">"
- @output.puts " <span class=\"failed_spec_name\">#{escape(name)}</span>"
+ @output.puts " <dd class=\"spec #{failure_style}\">"
+ @output.puts " <span class=\"failed_spec_name\">#{h(example.description)}</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=\"message\"><pre>#{h(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 extra unless extra == ""
@output.puts " </div>"
@output.puts " </dd>"
- STDOUT.flush
+ @output.flush
end
-
- # Override this method if you wish to output extra HTML in the header
- #
- def extra_header_content
+
+ def example_pending(behaviour_name, example_name, message)
+ @output.puts " <script type=\"text/javascript\">makeYellow('rspec-header');</script>" unless @header_red
+ @output.puts " <script type=\"text/javascript\">makeYellow('behaviour_#{current_behaviour_number}');</script>" unless @behaviour_red
+ move_progress
+ @output.puts " <dd class=\"spec not_implemented\"><span class=\"not_implemented_spec_name\">#{h(example_name)}</span></dd>"
+ @output.flush
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
+ def extra_failure_content(failure)
+ " <pre class=\"ruby\"><code>#{@snippet_extractor.snippet(failure.exception)}</code></pre>"
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
+ percent_done = @example_count == 0 ? 100.0 : ((current_example_number + 1).to_f / @example_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;')
+ @output.flush
end
def dump_failure(counter, failure)
end
- def dump_summary(duration, spec_count, failure_count)
+ def dump_summary(duration, example_count, failure_count, pending_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}"
+ totals = "#{example_count} example#{'s' unless example_count == 1}, #{failure_count} failure#{'s' unless failure_count == 1}"
+ totals << ", #{pending_count} pending" if pending_count > 0
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 "</div>"
@output.puts "</body>"
@output.puts "</html>"
- STDOUT.flush
+ @output.flush
end
- HEADER_1 = <<-EOF
+ def html_header
+ <<-EOF
<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
@@ -112,107 +132,191 @@ module Spec
<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" />
+ <style type="text/css">
+ body {
+ margin: 0;
+ padding: 0;
+ background: #fff;
+ font-size: 80%;
+ }
+ </style>
+</head>
+<body>
EOF
+ end
- HEADER_2 = <<-EOF
+ def report_header
+ <<-EOF
+<div class="rspec-report">
<script type="text/javascript">
- function moveProgressBar(percentDone) {
- document.getElementById("header").style.width = percentDone +"%";
- }
- function makeRed(element_id) {
- document.getElementById(element_id).style.background = '#C40D0D';
- }
+ // <![CDATA[
+#{global_scripts}
+ // ]]>
</script>
<style type="text/css">
- body {
- margin: 0; padding: 0;
- background: #fff;
- }
+#{global_styles}
+ </style>
- #header {
- background: #65C400; color: #fff;
- }
+<div id="rspec-header">
+ <h1>RSpec Results</h1>
- h1 {
- margin: 0 0 10px;
- padding: 10px;
- font: bold 18px "Lucida Grande", Helvetica, sans-serif;
- }
+ <div id="summary">
+ <p id="totals">&nbsp;</p>
+ <p id="duration">&nbsp;</p>
+ </div>
+</div>
- #summary {
- margin: 0; padding: 5px 10px;
- font: bold 10px "Lucida Grande", Helvetica, sans-serif;
- text-align: right;
- position: absolute;
- top: 0px;
- right: 0px;
- }
+<div class="results">
+EOF
+ end
- #summary p {
- margin: 0 0 2px;
- }
+ def global_scripts
+ <<-EOF
+function moveProgressBar(percentDone) {
+ document.getElementById("rspec-header").style.width = percentDone +"%";
+}
+function makeRed(element_id) {
+ document.getElementById(element_id).style.background = '#C40D0D';
+ document.getElementById(element_id).style.color = '#FFFFFF';
+}
- #summary #totals {
- font-size: 14px;
+function makeYellow(element_id) {
+ if (element_id == "rspec-header" && document.getElementById(element_id).style.background != '#C40D0D')
+ {
+ document.getElementById(element_id).style.background = '#FAF834';
+ document.getElementById(element_id).style.color = '#000000';
}
-
- .context {
- margin: 0 10px 5px;
- background: #fff;
+ else
+ {
+ document.getElementById(element_id).style.background = '#FAF834';
+ document.getElementById(element_id).style.color = '#000000';
}
+}
+EOF
+ end
+
+ def global_styles
+ <<-EOF
+#rspec-header {
+ background: #65C400; color: #fff;
+}
- dl {
- margin: 0; padding: 0 0 5px;
- font: normal 11px "Lucida Grande", Helvetica, sans-serif;
- }
+.rspec-report h1 {
+ margin: 0px 10px 0px 10px;
+ padding: 10px;
+ font-family: "Lucida Grande", Helvetica, sans-serif;
+ font-size: 1.8em;
+}
- dt {
- padding: 3px;
- background: #65C400;
- color: #fff;
- font-weight: bold;
- }
+#summary {
+ margin: 0; padding: 5px 10px;
+ font-family: "Lucida Grande", Helvetica, sans-serif;
+ text-align: right;
+ position: absolute;
+ top: 0px;
+ right: 0px;
+}
- dd {
- margin: 5px 0 5px 5px;
- padding: 3px 3px 3px 18px;
- }
+#summary p {
+ margin: 0 0 0 2px;
+}
- dd.spec.passed {
- border-left: 5px solid #65C400;
- border-bottom: 1px solid #65C400;
- background: #DBFFB4; color: #3D7700;
- }
+#summary #totals {
+ font-size: 1.2em;
+}
- dd.spec.failed {
- border-left: 5px solid #C20000;
- border-bottom: 1px solid #C20000;
- color: #C20000; background: #FFFBD3;
- }
+.behaviour {
+ margin: 0 10px 5px;
+ background: #fff;
+}
- div.backtrace {
- color: #000;
- font-size: 12px;
- }
+dl {
+ margin: 0; padding: 0 0 5px;
+ font: normal 11px "Lucida Grande", Helvetica, sans-serif;
+}
- a {
- color: #BE5C00;
- }
- </style>
-</head>
-<body>
+dt {
+ padding: 3px;
+ background: #65C400;
+ color: #fff;
+ font-weight: bold;
+}
-<div id="header">
- <h1>RSpec Results</h1>
+dd {
+ margin: 5px 0 5px 5px;
+ padding: 3px 3px 3px 18px;
+}
- <div id="summary">
- <p id="duration">&nbsp;</p>
- <p id="totals">&nbsp;</p>
- </div>
-</div>
+dd.spec.passed {
+ border-left: 5px solid #65C400;
+ border-bottom: 1px solid #65C400;
+ background: #DBFFB4; color: #3D7700;
+}
-<div id="results">
+dd.spec.failed {
+ border-left: 5px solid #C20000;
+ border-bottom: 1px solid #C20000;
+ color: #C20000; background: #FFFBD3;
+}
+
+dd.spec.not_implemented {
+ border-left: 5px solid #FAF834;
+ border-bottom: 1px solid #FAF834;
+ background: #FCFB98; color: #131313;
+}
+
+dd.spec.pending_fixed {
+ border-left: 5px solid #0000C2;
+ border-bottom: 1px solid #0000C2;
+ color: #0000C2; background: #D3FBFF;
+}
+
+.backtrace {
+ color: #000;
+ font-size: 12px;
+}
+
+a {
+ color: #BE5C00;
+}
+
+/* Ruby code, style similar to vibrant ink */
+.ruby {
+ font-size: 12px;
+ font-family: monospace;
+ color: white;
+ background-color: black;
+ padding: 0.1em 0 0.2em 0;
+}
+
+.ruby .keyword { color: #FF6600; }
+.ruby .constant { color: #339999; }
+.ruby .attribute { color: white; }
+.ruby .global { color: white; }
+.ruby .module { color: white; }
+.ruby .class { color: white; }
+.ruby .string { color: #66FF00; }
+.ruby .ident { color: white; }
+.ruby .method { color: #FFCC00; }
+.ruby .number { color: white; }
+.ruby .char { color: white; }
+.ruby .comment { color: #9933CC; }
+.ruby .symbol { color: white; }
+.ruby .regex { color: #44B4CC; }
+.ruby .punct { color: white; }
+.ruby .escape { color: white; }
+.ruby .interp { color: white; }
+.ruby .expr { color: white; }
+
+.ruby .offending { background-color: gray; }
+.ruby .linenum {
+ width: 75px;
+ padding: 0.1em 1em 0.2em 0;
+ color: #000000;
+ background-color: #FFFBD3;
+}
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
index fe519d4d8..624f06e7c 100644
--- a/test/lib/spec/runner/formatter/progress_bar_formatter.rb
+++ b/test/lib/spec/runner/formatter/progress_bar_formatter.rb
@@ -2,26 +2,30 @@ module Spec
module Runner
module Formatter
class ProgressBarFormatter < BaseTextFormatter
- def add_context(name, first)
- @output.puts if first
- STDOUT.flush
+ def add_behaviour(name)
end
- def spec_failed(name, counter, failure)
- @output.print failure.expectation_not_met? ? red('F') : magenta('F')
- STDOUT.flush
+ def example_failed(example, counter, failure)
+ @output.print colourise('F', failure)
+ @output.flush
end
- def spec_passed(name)
+ def example_passed(example)
@output.print green('.')
- STDOUT.flush
+ @output.flush
end
+ def example_pending(behaviour_name, example_name, message)
+ super
+ @output.print yellow('P')
+ @output.flush
+ end
+
def start_dump
@output.puts
- STDOUT.flush
+ @output.flush
end
end
end
end
-end \ No newline at end of file
+end
diff --git a/test/lib/spec/runner/formatter/rdoc_formatter.rb b/test/lib/spec/runner/formatter/rdoc_formatter.rb
index eae55c3ea..0fd22ba6c 100644
--- a/test/lib/spec/runner/formatter/rdoc_formatter.rb
+++ b/test/lib/spec/runner/formatter/rdoc_formatter.rb
@@ -2,21 +2,23 @@ module Spec
module Runner
module Formatter
class RdocFormatter < BaseTextFormatter
- def add_context(name, first)
- @output.print "# #{name}\n"
- STDOUT.flush
+ def add_behaviour(name)
+ @output.puts "# #{name}"
end
- def spec_passed(name)
- @output.print "# * #{name}\n"
- STDOUT.flush
+ def example_passed(example)
+ @output.puts "# * #{example.description}"
+ @output.flush
end
- def spec_failed(name, counter, failure)
- @output.print "# * #{name} [#{counter} - FAILED]\n"
- STDOUT.flush
+ def example_failed(example, counter, failure)
+ @output.puts "# * #{example.description} [#{counter} - FAILED]"
+ end
+
+ def example_pending(behaviour_name, example_name, message)
+ @output.puts "# * #{behaviour_name} #{example_name} [PENDING: #{message}]"
end
end
end
end
-end \ No newline at end of file
+end
diff --git a/test/lib/spec/runner/formatter/snippet_extractor.rb b/test/lib/spec/runner/formatter/snippet_extractor.rb
new file mode 100644
index 000000000..41119fe46
--- /dev/null
+++ b/test/lib/spec/runner/formatter/snippet_extractor.rb
@@ -0,0 +1,52 @@
+module Spec
+ module Runner
+ module Formatter
+ # This class extracts code snippets by looking at the backtrace of the passed error
+ class SnippetExtractor #:nodoc:
+ class NullConverter; def convert(code, pre); code; end; end #:nodoc:
+ begin; require 'rubygems'; require 'syntax/convertors/html'; @@converter = Syntax::Convertors::HTML.for_syntax "ruby"; rescue LoadError => e; @@converter = NullConverter.new; end
+
+ def snippet(error)
+ raw_code, line = snippet_for(error.backtrace[0])
+ highlighted = @@converter.convert(raw_code, false)
+ highlighted << "\n<span class=\"comment\"># gem install syntax to get syntax highlighting</span>" if @@converter.is_a?(NullConverter)
+ post_process(highlighted, line)
+ end
+
+ def snippet_for(error_line)
+ if error_line =~ /(.*):(\d+)/
+ file = $1
+ line = $2.to_i
+ [lines_around(file, line), line]
+ else
+ ["# Couldn't get snippet for #{error_line}", 1]
+ end
+ end
+
+ def lines_around(file, line)
+ if File.file?(file)
+ lines = File.open(file).read.split("\n")
+ min = [0, line-3].max
+ max = [line+1, lines.length-1].min
+ selected_lines = []
+ selected_lines.join("\n")
+ lines[min..max].join("\n")
+ else
+ "# Couldn't get snippet for #{file}"
+ end
+ end
+
+ def post_process(highlighted, offending_line)
+ new_lines = []
+ highlighted.split("\n").each_with_index do |line, i|
+ new_line = "<span class=\"linenum\">#{offending_line+i-2}</span>#{line}"
+ new_line = "<span class=\"offending\">#{new_line}</span>" if i == 2
+ new_lines << new_line
+ end
+ new_lines.join("\n")
+ end
+
+ end
+ end
+ end
+end
diff --git a/test/lib/spec/runner/formatter/specdoc_formatter.rb b/test/lib/spec/runner/formatter/specdoc_formatter.rb
index 67b4312bf..ad794b238 100644
--- a/test/lib/spec/runner/formatter/specdoc_formatter.rb
+++ b/test/lib/spec/runner/formatter/specdoc_formatter.rb
@@ -2,22 +2,28 @@ module Spec
module Runner
module Formatter
class SpecdocFormatter < BaseTextFormatter
- def add_context(name, first)
+ def add_behaviour(name)
@output.puts
@output.puts name
- STDOUT.flush
+ @output.flush
end
- def spec_failed(name, counter, failure)
- @output.puts failure.expectation_not_met? ? red("- #{name} (FAILED - #{counter})") : magenta("- #{name} (ERROR - #{counter})")
- STDOUT.flush
+ def example_failed(example, counter, failure)
+ @output.puts failure.expectation_not_met? ? red("- #{example.description} (FAILED - #{counter})") : magenta("- #{example.description} (ERROR - #{counter})")
+ @output.flush
end
- def spec_passed(name)
- @output.print green("- #{name}\n")
- STDOUT.flush
+ def example_passed(example)
+ @output.puts green("- #{example.description}")
+ @output.flush
+ end
+
+ def example_pending(behaviour_name, example_name, message)
+ super
+ @output.puts yellow("- #{example_name} (PENDING: #{message})")
+ @output.flush
end
end
end
end
-end \ No newline at end of file
+end
diff --git a/test/lib/spec/runner/heckle_runner.rb b/test/lib/spec/runner/heckle_runner.rb
index fd36389de..b6de4ef73 100644
--- a/test/lib/spec/runner/heckle_runner.rb
+++ b/test/lib/spec/runner/heckle_runner.rb
@@ -13,9 +13,9 @@ module Spec
@heckle_class = heckle_class
end
- # Runs all the contexts held by +context_runner+ once for each of the
+ # Runs all the contexts held by +behaviour_runner+ once for each of the
# methods in the matched classes.
- def heckle_with(context_runner)
+ def heckle_with(behaviour_runner)
if @filter =~ /(.*)[#\.](.*)/
heckle_method($1, $2)
else
@@ -25,7 +25,7 @@ module Spec
def heckle_method(class_name, method_name)
verify_constant(class_name)
- heckle = @heckle_class.new(class_name, method_name, context_runner)
+ heckle = @heckle_class.new(class_name, method_name, behaviour_runner)
heckle.validate
end
@@ -39,7 +39,7 @@ module Spec
classes.each do |klass|
klass.instance_methods(false).each do |method_name|
- heckle = @heckle_class.new(klass.name, method_name, context_runner)
+ heckle = @heckle_class.new(klass.name, method_name, behaviour_runner)
heckle.validate
end
end
@@ -57,13 +57,14 @@ module Spec
#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)
+ def initialize(klass_name, method_name, behaviour_runner)
super(klass_name, method_name)
- @context_runner = context_runner
+ @behaviour_runner = behaviour_runner
end
def tests_pass?
- failure_count = @context_runner.run(false)
+ paths = [] # We can pass an empty array of paths - our specs are already loaded.
+ failure_count = @behaviour_runner.run(paths, false)
failure_count == 0
end
end
diff --git a/test/lib/spec/runner/heckle_runner_unsupported.rb b/test/lib/spec/runner/heckle_runner_unsupported.rb
new file mode 100644
index 000000000..02aa37953
--- /dev/null
+++ b/test/lib/spec/runner/heckle_runner_unsupported.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
index 38725d848..1facb85a8 100644
--- a/test/lib/spec/runner/option_parser.rb
+++ b/test/lib/spec/runner/option_parser.rb
@@ -1,186 +1,156 @@
-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'
+require 'stringio'
module Spec
module Runner
class OptionParser
+ BUILT_IN_FORMATTERS = {
+ 'specdoc' => Formatter::SpecdocFormatter,
+ 's' => Formatter::SpecdocFormatter,
+ 'html' => Formatter::HtmlFormatter,
+ 'h' => Formatter::HtmlFormatter,
+ 'rdoc' => Formatter::RdocFormatter,
+ 'r' => Formatter::RdocFormatter,
+ 'progress' => Formatter::ProgressBarFormatter,
+ 'p' => Formatter::ProgressBarFormatter,
+ 'failing_examples' => Formatter::FailingExamplesFormatter,
+ 'e' => Formatter::FailingExamplesFormatter,
+ 'failing_behaviours' => Formatter::FailingBehavioursFormatter,
+ 'b' => Formatter::FailingBehavioursFormatter
+ }
+
+ COMMAND_LINE = {
+ :diff => ["-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)"],
+ :colour => ["-c", "--colour", "--color", "Show coloured (red/green) output"],
+ :example => ["-e", "--example [NAME|FILE_NAME]", "Execute example(s) with matching name(s). If the argument is",
+ "the path to an existing file (typically generated by a previous",
+ "run using --format failing_examples:file.txt), then the examples",
+ "on each line of thatfile will be executed. If the file is empty,",
+ "all examples will be run (as if --example was not specified).",
+ " ",
+ "If the argument is not an existing file, then it is treated as",
+ "an example name directly, causing RSpec to run just the example",
+ "matching that name"],
+ :specification => ["-s", "--specification [NAME]", "DEPRECATED - use -e instead", "(This will be removed when autotest works with -e)"],
+ :line => ["-l", "--line LINE_NUMBER", Integer, "Execute behaviout or specification at given line.",
+ "(does not work for dynamically generated specs)"],
+ :format => ["-f", "--format FORMAT[:WHERE]", "Specifies what format to use for output. Specify WHERE to tell",
+ "the formatter where to write the output. All built-in formats",
+ "expect WHERE to be a file name, and will write to STDOUT if it's",
+ "not specified. The --format option may be specified several times",
+ "if you want several outputs",
+ " ",
+ "Builtin formats: ",
+ "progress|p : Text progress",
+ "specdoc|s : Behaviour doc as text",
+ "rdoc|r : Behaviour doc as RDoc",
+ "html|h : A nice HTML report",
+ "failing_examples|e : Write all failing examples - input for --example",
+ "failing_behaviours|b : Write all failing behaviours - input for --example",
+ " ",
+ "FORMAT can also be the name of a custom formatter class",
+ "(in which case you should also specify --require to load it)"],
+ :require => ["-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"],
+ :backtrace => ["-b", "--backtrace", "Output full backtrace"],
+ :loadby => ["-L", "--loadby STRATEGY", "Specify the strategy by which spec files should be loaded.",
+ "STRATEGY can currently only be 'mtime' (File modification time)",
+ "By default, spec files are loaded in alphabetical order if --loadby",
+ "is not specified."],
+ :reverse => ["-R", "--reverse", "Run examples in reverse order"],
+ :timeout => ["-t", "--timeout FLOAT", "Interrupt and fail each example that doesn't complete in the",
+ "specified time"],
+ :heckle => ["-H", "--heckle CODE", "If all examples pass, this will mutate the classes and methods",
+ "identified by CODE little by little and run all the examples again",
+ "for each mutation. The intent is that for each mutation, at least",
+ "one example *should* fail, and RSpec will tell you if this is not the",
+ "case. CODE should be either Some::Module, Some::Class or",
+ "Some::Fabulous#method}"],
+ :dry_run => ["-d", "--dry-run", "Invokes formatters without executing the examples."],
+ :options_file => ["-O", "--options PATH", "Read options from a file"],
+ :generate_options => ["-G", "--generate-options PATH", "Generate an options file for --options"],
+ :runner => ["-U", "--runner RUNNER", "Use a custom BehaviourRunner."],
+ :drb => ["-X", "--drb", "Run examples via DRb. (For example against script/spec_server)"],
+ :version => ["-v", "--version", "Show version"],
+ :help => ["-h", "--help", "You're looking at it"]
+ }
+
def initialize
@spec_parser = SpecParser.new
@file_factory = File
end
- def create_context_runner(args, err, out, warn_if_no_files)
+ def create_behaviour_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
+ return nil unless options.is_a?(Options)
+ options.configure
+ options.behaviour_runner
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
+ options = Options.new(err, out)
opts = ::OptionParser.new do |opts|
- opts.banner = "Usage: spec [options] (FILE|DIRECTORY|GLOB)+"
+ opts.banner = "Usage: spec (FILE|DIRECTORY|GLOB)+ [options]"
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|
+ def opts.rspec_on(name, &block)
+ on(*COMMAND_LINE[name], &block)
+ end
- # TODO make context_lines settable
- options.context_lines = 3
+ opts.rspec_on(:diff) {|diff| options.parse_diff(diff)}
- case format
- when 'context', 'c'
- options.diff_format = :context
- when 'unified', 'u', '', nil
- options.diff_format = :unified
- end
+ opts.rspec_on(:colour) {options.colour = true}
- 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
+ opts.rspec_on(:example) {|example| options.parse_example(example)}
- 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.rspec_on(:specification) {|example| options.parse_example(example)}
- 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.rspec_on(:line) {|line_number| options.line_number = line_number.to_i}
- 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.rspec_on(:format) {|format| options.parse_format(format)}
- 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.rspec_on(:require) {|req| options.parse_require(req)}
- 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)
+ opts.rspec_on(:backtrace) {options.backtrace_tweaker = NoisyBacktraceTweaker.new}
- 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.rspec_on(:loadby) {|loadby| options.loadby = loadby}
+
+ opts.rspec_on(:reverse) {options.reverse = true}
+
+ opts.rspec_on(:timeout) {|timeout| options.timeout = timeout.to_f}
+
+ opts.rspec_on(:heckle) {|heckle| options.parse_heckle(heckle)}
- 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)
+ opts.rspec_on(:dry_run) {options.dry_run = true}
- return DrbCommandLine.run(args_copy, err, out, true, warn_if_no_files)
+ opts.rspec_on(:options_file) do |options_file|
+ return parse_options_file(options_file, out, err, args_copy, warn_if_no_files)
end
- opts.on("-v", "--version", "Show version") do
- out.puts ::Spec::VERSION::DESCRIPTION
- exit if out == $stdout
+ opts.rspec_on(:generate_options) do |options_file|
+ options.parse_generate_options(options_file, args_copy, out)
end
- opts.on_tail("-h", "--help", "You're looking at it") do
- out.puts opts
- exit if out == $stdout
+ opts.rspec_on(:runner) do |runner|
+ options.runner_arg = runner
end
-
+
+ opts.rspec_on(:drb) do
+ return parse_drb(args_copy, out, err, warn_if_no_files)
+ end
+
+ opts.rspec_on(:version) {parse_version(out)}
+
+ opts.on_tail(*COMMAND_LINE[:help]) {parse_help(opts, out)}
end
opts.parse!(args)
@@ -194,15 +164,48 @@ module Spec
set_spec_from_line_number(options, args, err)
end
+ if options.formatters.empty?
+ options.formatters << Formatter::ProgressBarFormatter.new(out)
+ end
+
options
end
-
+
+ def parse_options_file(options_file, out_stream, error_stream, args_copy, warn_if_no_files)
+ # 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).map {|l| l.chomp.split " "}.flatten
+ return CommandLine.run(new_args, error_stream, out_stream, true, warn_if_no_files)
+ end
+
+ def parse_drb(args_copy, out_stream, error_stream, warn_if_no_files)
+ # Remove the --drb option
+ index = args_copy.index("-X") || args_copy.index("--drb")
+ args_copy.delete_at(index)
+
+ return DrbCommandLine.run(args_copy, error_stream, out_stream, true, warn_if_no_files)
+ end
+
+ def parse_version(out_stream)
+ out_stream.puts ::Spec::VERSION::DESCRIPTION
+ exit if out_stream == $stdout
+ end
+
+ def parse_help(opts, out_stream)
+ out_stream.puts opts
+ exit if out_stream == $stdout
+ end
+
def set_spec_from_line_number(options, args, err)
- unless options.spec_name
+ if options.examples.empty?
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)
+ example = @spec_parser.spec_name_for(source, options.line_number)
+ options.parse_example(example)
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
@@ -215,7 +218,7 @@ module Spec
exit(3) if err == $stderr
end
else
- err.puts "You cannot use both --line and --spec"
+ err.puts "You cannot use both --line and --example"
exit(4) if err == $stderr
end
end
diff --git a/test/lib/spec/runner/options.rb b/test/lib/spec/runner/options.rb
new file mode 100644
index 000000000..a940133eb
--- /dev/null
+++ b/test/lib/spec/runner/options.rb
@@ -0,0 +1,175 @@
+module Spec
+ module Runner
+ class Options
+ BUILT_IN_FORMATTERS = {
+ 'specdoc' => Formatter::SpecdocFormatter,
+ 's' => Formatter::SpecdocFormatter,
+ 'html' => Formatter::HtmlFormatter,
+ 'h' => Formatter::HtmlFormatter,
+ 'rdoc' => Formatter::RdocFormatter,
+ 'r' => Formatter::RdocFormatter,
+ 'progress' => Formatter::ProgressBarFormatter,
+ 'p' => Formatter::ProgressBarFormatter,
+ 'failing_examples' => Formatter::FailingExamplesFormatter,
+ 'e' => Formatter::FailingExamplesFormatter,
+ 'failing_behaviours' => Formatter::FailingBehavioursFormatter,
+ 'b' => Formatter::FailingBehavioursFormatter
+ }
+
+ attr_accessor(
+ :backtrace_tweaker,
+ :colour,
+ :context_lines,
+ :diff_format,
+ :differ_class,
+ :dry_run,
+ :examples,
+ :failure_file,
+ :formatters,
+ :generate,
+ :heckle_runner,
+ :line_number,
+ :loadby,
+ :reporter,
+ :reverse,
+ :timeout,
+ :verbose,
+ :runner_arg,
+ :behaviour_runner
+ )
+
+ def initialize(err, out)
+ @err, @out = err, out
+ @backtrace_tweaker = QuietBacktraceTweaker.new
+ @examples = []
+ @formatters = []
+ @colour = false
+ @dry_run = false
+ end
+
+ def configure
+ configure_formatters
+ create_reporter
+ configure_differ
+ create_behaviour_runner
+ end
+
+ def create_behaviour_runner
+ return nil if @generate
+ @behaviour_runner = if @runner_arg
+ klass_name, arg = split_at_colon(@runner_arg)
+ runner_type = load_class(klass_name, 'behaviour runner', '--runner')
+ runner_type.new(self, arg)
+ else
+ BehaviourRunner.new(self)
+ end
+ end
+
+ def configure_formatters
+ @formatters.each do |formatter|
+ formatter.colour = @colour if formatter.respond_to?(:colour=)
+ formatter.dry_run = @dry_run if formatter.respond_to?(:dry_run=)
+ end
+ end
+
+ def create_reporter
+ @reporter = Reporter.new(@formatters, @backtrace_tweaker)
+ end
+
+ def configure_differ
+ if @differ_class
+ Spec::Expectations.differ = @differ_class.new(@diff_format, @context_lines, @colour)
+ end
+ end
+
+ def parse_diff(format)
+ @context_lines = 3
+ case format
+ when :context, 'context', 'c'
+ @diff_format = :context
+ when :unified, 'unified', 'u', '', nil
+ @diff_format = :unified
+ end
+
+ if [:context,:unified].include? @diff_format
+ require 'spec/expectations/differs/default'
+ @differ_class = Spec::Expectations::Differs::Default
+ else
+ @diff_format = :custom
+ @differ_class = load_class(format, 'differ', '--diff')
+ end
+ end
+
+ def parse_example(example)
+ if(File.file?(example))
+ @examples = File.open(example).read.split("\n")
+ else
+ @examples = [example]
+ end
+ end
+
+ def parse_format(format_arg)
+ format, where = split_at_colon(format_arg)
+ # This funky regexp checks whether we have a FILE_NAME or not
+ if where.nil?
+ raise "When using several --format options only one of them can be without a file" if @out_used
+ where = @out
+ @out_used = true
+ end
+
+ formatter_type = BUILT_IN_FORMATTERS[format] || load_class(format, 'formatter', '--format')
+ @formatters << formatter_type.new(where)
+ end
+
+ def parse_require(req)
+ req.split(",").each{|file| require file}
+ end
+
+ def parse_heckle(heckle)
+ heckle_require = [/mswin/, /java/].detect{|p| p =~ RUBY_PLATFORM} ? 'spec/runner/heckle_runner_unsupported' : 'spec/runner/heckle_runner'
+ require heckle_require
+ @heckle_runner = HeckleRunner.new(heckle)
+ end
+
+ def parse_generate_options(options_file, args_copy, out_stream)
+ # 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_stream.puts "\nOptions written to #{options_file}. You can now use these options with:"
+ out_stream.puts "spec --options #{options_file}"
+ @generate = true
+ end
+
+ def split_at_colon(s)
+ if s =~ /([a-zA-Z_]+(?:::[a-zA-Z_]+)*):?(.*)/
+ arg = $2 == "" ? nil : $2
+ [$1, arg]
+ else
+ raise "Couldn't parse #{s.inspect}"
+ end
+ end
+
+ def load_class(name, kind, option)
+ if name =~ /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/
+ arg = $2 == "" ? nil : $2
+ [$1, arg]
+ else
+ m = "#{name.inspect} is not a valid class name"
+ @err.puts m
+ raise m
+ end
+ begin
+ eval(name, binding, __FILE__, __LINE__)
+ rescue NameError => e
+ @err.puts "Couldn't find #{kind} class #{name}"
+ @err.puts "Make sure the --require option is specified *before* #{option}"
+ if $_spec_spec ; raise e ; else exit(1) ; end
+ end
+ end
+ end
+ end
+end
diff --git a/test/lib/spec/runner/reporter.rb b/test/lib/spec/runner/reporter.rb
index e4fb1cb0e..b1dc2a27a 100644
--- a/test/lib/spec/runner/reporter.rb
+++ b/test/lib/spec/runner/reporter.rb
@@ -2,36 +2,39 @@ module Spec
module Runner
class Reporter
- def initialize(formatter, backtrace_tweaker)
- @formatter = formatter
+ def initialize(formatters, backtrace_tweaker)
+ @formatters = formatters
@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
+ def add_behaviour(name)
+ @formatters.each{|f| f.add_behaviour(name)}
+ @behaviour_names << name
end
- def spec_started(name)
- @spec_names << name
- @formatter.spec_started(name)
+ def example_started(name)
+ @formatters.each{|f| f.example_started(name)}
end
- def spec_finished(name, error=nil, failure_location=nil)
- if error.nil?
- spec_passed(name)
+ def example_finished(name, error=nil, failure_location=nil, not_implemented = false)
+ @example_names << name
+
+ if not_implemented
+ example_pending(@behaviour_names.last, name)
+ elsif error.nil?
+ example_passed(name)
+ elsif Spec::DSL::ExamplePendingError === error
+ example_pending(@behaviour_names.last, name, error.message)
else
- @backtrace_tweaker.tweak_backtrace(error, failure_location)
- spec_failed(name, Failure.new(@context_names.last, name, error))
+ example_failed(name, error, failure_location)
end
end
- def start(number_of_specs)
+ def start(number_of_examples)
clear!
@start_time = Time.new
- @formatter.start(number_of_specs)
+ @formatters.each{|f| f.start(number_of_examples)}
end
def end
@@ -40,18 +43,22 @@ module Spec
# Dumps the summary and returns the total number of failures
def dump
- @formatter.start_dump
+ @formatters.each{|f| f.start_dump}
dump_failures
- @formatter.dump_summary(duration, @spec_names.length, @failures.length)
+ @formatters.each do |f|
+ f.dump_summary(duration, @example_names.length, @failures.length, @pending_count)
+ f.close
+ end
@failures.length
end
- private
+ private
def clear!
- @context_names = []
+ @behaviour_names = []
@failures = []
- @spec_names = []
+ @pending_count = 0
+ @example_names = []
@start_time = nil
@end_time = nil
end
@@ -59,7 +66,7 @@ module Spec
def dump_failures
return if @failures.empty?
@failures.inject(1) do |index, failure|
- @formatter.dump_failure(index, failure)
+ @formatters.each{|f| f.dump_failure(index, failure)}
index + 1
end
end
@@ -68,33 +75,46 @@ module Spec
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)
+
+ def example_passed(name)
+ @formatters.each{|f| f.example_passed(name)}
end
- def spec_failed(name, failure)
+ def example_failed(name, error, failure_location)
+ @backtrace_tweaker.tweak_backtrace(error, failure_location)
+ example_name = "#{@behaviour_names.last} #{name}"
+ failure = Failure.new(example_name, error)
@failures << failure
- @formatter.spec_failed(name, @failures.length, failure)
+ @formatters.each{|f| f.example_failed(name, @failures.length, failure)}
end
-
+
+ def example_pending(behaviour_name, example_name, message="Not Yet Implemented")
+ @pending_count += 1
+ @formatters.each{|f| f.example_pending(behaviour_name, example_name, message)}
+ end
+
class Failure
attr_reader :exception
- def initialize(context_name, spec_name, exception)
- @context_name = context_name
- @spec_name = spec_name
+ def initialize(example_name, exception)
+ @example_name = example_name
@exception = exception
end
def header
if expectation_not_met?
- "'#{@context_name} #{@spec_name}' FAILED"
+ "'#{@example_name}' FAILED"
+ elsif pending_fixed?
+ "'#{@example_name}' FIXED"
else
- "#{@exception.class.name} in '#{@context_name} #{@spec_name}'"
+ "#{@exception.class.name} in '#{@example_name}'"
end
end
+ def pending_fixed?
+ @exception.is_a?(Spec::DSL::PendingFixedError)
+ end
+
def expectation_not_met?
@exception.is_a?(Spec::Expectations::ExpectationNotMetError)
end
diff --git a/test/lib/spec/runner/spec_parser.rb b/test/lib/spec/runner/spec_parser.rb
index 2cb8518fc..bc9170065 100644
--- a/test/lib/spec/runner/spec_parser.rb
+++ b/test/lib/spec/runner/spec_parser.rb
@@ -1,15 +1,15 @@
module Spec
module Runner
- # Parses a spec file and finds the nearest spec for a given line number.
+ # Parses a spec file and finds the nearest example 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
+ behaviour, behaviour_line = behaviour_at_line(source, line_number)
+ example, example_line = example_at_line(source, line_number)
+ if behaviour && example && (behaviour_line < example_line)
+ "#{behaviour} #{example}"
+ elsif behaviour
+ behaviour
else
nil
end
@@ -17,25 +17,34 @@ module Spec
protected
- def context_at_line(source, line_number)
- find_above(source, line_number, /^\s*context\s+['|"](.*)['|"]/)
+ def behaviour_at_line(source, line_number)
+ find_above(source, line_number, /^\s*(context|describe)\s+(.*)\s+do/)
end
- def spec_at_line(source, line_number)
- find_above(source, line_number, /^\s*specify\s+['|"](.*)['|"]/)
+ def example_at_line(source, line_number)
+ find_above(source, line_number, /^\s*(specify|it)\s+(.*)\s+do/)
end
+ # Returns the context/describe or specify/it name and the line number
def find_above(source, line_number, pattern)
- lines_above_reversed(source, line_number).each do |line|
- return $1 if line =~ pattern
+ lines_above_reversed(source, line_number).each_with_index do |line, n|
+ return [parse_description($2), line_number-n] if line =~ pattern
end
nil
end
def lines_above_reversed(source, line_number)
lines = source.split("\n")
- lines[0...line_number].reverse
+ lines[0...line_number].reverse
+ end
+
+ def parse_description(str)
+ return str[1..-2] if str =~ /^['"].*['"]$/
+ if matches = /^(.*)\s*,\s*['"](.*)['"]$/.match(str)
+ return ::Spec::DSL::Description.generate_description(matches[1], matches[2])
+ end
+ return str
end
end
end
-end \ No newline at end of file
+end
diff --git a/test/lib/spec/test_case_adapter.rb b/test/lib/spec/test_case_adapter.rb
new file mode 100755
index 000000000..992e098fd
--- /dev/null
+++ b/test/lib/spec/test_case_adapter.rb
@@ -0,0 +1,10 @@
+require 'spec/expectations'
+require 'spec/matchers'
+
+module Test
+ module Unit
+ class TestCase
+ include Spec::Matchers
+ end
+ end
+end
diff --git a/test/lib/spec/translator.rb b/test/lib/spec/translator.rb
index 970c8ca00..c1e07eda4 100644
--- a/test/lib/spec/translator.rb
+++ b/test/lib/spec/translator.rb
@@ -2,26 +2,30 @@ require 'fileutils'
module Spec
class Translator
- def translate_dir(from, to)
+ def translate(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_dir(from, to)
+ elsif(from =~ /\.rb$/)
translate_file(from, to)
end
end
+ def translate_dir(from, to)
+ 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(sub_from, sub_to)
+ end
+ end
+
def translate_file(from, to)
translation = ""
File.open(from) do |io|
io.each_line do |line|
- translation << translate(line)
+ translation << translate_line(line)
end
end
File.open(to, "w") do |io|
@@ -29,9 +33,24 @@ module Spec
end
end
- def translate(line)
+ def translate_line(line)
+ # Translate deprecated mock constraints
+ line.gsub!(/:any_args/, 'any_args')
+ line.gsub!(/:anything/, 'anything')
+ line.gsub!(/:boolean/, 'boolean')
+ line.gsub!(/:no_args/, 'no_args')
+ line.gsub!(/:numeric/, 'an_instance_of(Numeric)')
+ line.gsub!(/:string/, 'an_instance_of(String)')
+
return line if line =~ /(should_not|should)_receive/
+ line.gsub!(/(^\s*)context([\s*|\(]['|"|A-Z])/, '\1describe\2')
+ line.gsub!(/(^\s*)specify([\s*|\(]['|"|A-Z])/, '\1it\2')
+ line.gsub!(/(^\s*)context_setup(\s*[do|\{])/, '\1before(:all)\2')
+ line.gsub!(/(^\s*)context_teardown(\s*[do|\{])/, '\1after(:all)\2')
+ line.gsub!(/(^\s*)setup(\s*[do|\{])/, '\1before(:each)\2')
+ line.gsub!(/(^\s*)teardown(\s*[do|\{])/, '\1after(:each)\2')
+
if line =~ /(.*\.)(should_not|should)(?:_be)(?!_)(.*)/m
pre = $1
should = $2
@@ -41,7 +60,7 @@ module Spec
return "#{pre}#{should} #{be_or_equal}#{post}"
end
- if line =~ /(.*\.)(should_not|should)_(?!not)(.*)/m
+ if line =~ /(.*\.)(should_not|should)_(?!not)\s*(.*)/m
pre = $1
should = $2
post = $3
@@ -53,6 +72,13 @@ module Spec
post = "be_#{post}"
end
+ # Add parenthesis
+ post.gsub!(/^(\w+)\s+([\w|\.|\,|\(.*\)|\'|\"|\:|@| ]+)(\})/, '\1(\2)\3') # inside a block
+ post.gsub!(/^(redirect_to)\s+(.*)/, '\1(\2)') # redirect_to, which often has http:
+ post.gsub!(/^(\w+)\s+([\w|\.|\,|\(.*\)|\{.*\}|\'|\"|\:|@| ]+)/, '\1(\2)')
+ post.gsub!(/(\s+\))/, ')')
+ post.gsub!(/\)\}/, ') }')
+ post.gsub!(/^(\w+)\s+(\/.*\/)/, '\1(\2)') #regexps
line = "#{pre}#{should} #{post}"
end
@@ -72,6 +98,7 @@ module Spec
/^match/,
/^raise_error/,
/^respond_to/,
+ /^redirect_to/,
/^satisfy/,
/^throw_symbol/,
# Extra ones that we use in spec_helper
@@ -84,4 +111,4 @@ module Spec
end
end
-end \ No newline at end of file
+end
diff --git a/test/lib/spec/version.rb b/test/lib/spec/version.rb
index e692a87ee..5b1db9b37 100644
--- a/test/lib/spec/version.rb
+++ b/test/lib/spec/version.rb
@@ -1,25 +1,17 @@
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: 2283$".match(/LastChangedRevision: (\d+)/)[1]
+ MAJOR = 1
+ MINOR = 0
+ TINY = 8
+ RELEASE_CANDIDATE = nil
+
+ # RANDOM_TOKEN: 0.510454315029681
+ REV = "$LastChangedRevision: 2338 $".match(/LastChangedRevision: (\d+)/)[1]
STRING = [MAJOR, MINOR, TINY].join('.')
- FULL_VERSION = "#{STRING} (r#{REV})"
- TAG = build_tag
+ TAG = "REL_#{[MAJOR, MINOR, TINY, RELEASE_CANDIDATE].compact.join('_')}".upcase.gsub(/\.|-/, '_')
+ FULL_VERSION = "#{[MAJOR, MINOR, TINY, RELEASE_CANDIDATE].compact.join('.')} (r#{REV})"
NAME = "RSpec"
URL = "http://rspec.rubyforge.org/"
@@ -28,3 +20,4 @@ module Spec
end
end
end
+