Added Rspec and Webrat plugins and started porting Selenium on Rails tests to Rspec Plain Text Stories driving Webrat driving Selenium.

This commit is contained in:
Luke Melia 2008-06-18 02:57:57 -04:00
parent 0600756bbf
commit 0f7d6f7a1d
602 changed files with 47788 additions and 29 deletions

View file

@ -0,0 +1,3 @@
Autotest.add_discovery do
"rspec" if File.exist?('spec')
end

View file

@ -0,0 +1,72 @@
require 'autotest'
Autotest.add_hook :initialize do |at|
at.clear_mappings
# watch out: Ruby bug (1.8.6):
# %r(/) != /\//
at.add_mapping(%r%^spec/.*\.rb$%) { |filename, _|
filename
}
at.add_mapping(%r%^lib/(.*)\.rb$%) { |_, m|
["spec/#{m[1]}_spec.rb"]
}
at.add_mapping(%r%^spec/(spec_helper|shared/.*)\.rb$%) {
at.files_matching %r%^spec/.*_spec\.rb$%
}
end
class RspecCommandError < StandardError; end
class Autotest::Rspec < Autotest
def initialize
super
self.failed_results_re = /^\d+\)\n(?:\e\[\d*m)?(?:.*?Error in )?'([^\n]*)'(?: FAILED)?(?:\e\[\d*m)?\n(.*?)\n\n/m
self.completed_re = /\n(?:\e\[\d*m)?\d* examples?/m
end
def consolidate_failures(failed)
filters = new_hash_of_arrays
failed.each do |spec, trace|
if trace =~ /\n(\.\/)?(.*spec\.rb):[\d]+:\Z?/
filters[$2] << spec
end
end
return filters
end
def make_test_cmd(files_to_test)
return "#{ruby} -S #{spec_command} #{add_options_if_present} #{files_to_test.keys.flatten.join(' ')}"
end
def add_options_if_present # :nodoc:
File.exist?("spec/spec.opts") ? "-O spec/spec.opts " : ""
end
# Finds the proper spec command to use. Precendence is set in the
# lazily-evaluated method spec_commands. Alias + Override that in
# ~/.autotest to provide a different spec command then the default
# paths provided.
def spec_command(separator=File::ALT_SEPARATOR)
unless defined? @spec_command then
@spec_command = spec_commands.find { |cmd| File.exists? cmd }
raise RspecCommandError, "No spec command could be found!" unless @spec_command
@spec_command.gsub! File::SEPARATOR, separator if separator
end
@spec_command
end
# Autotest will look for spec commands in the following
# locations, in this order:
#
# * bin/spec
# * default spec bin/loader installed in Rubygems
def spec_commands
[
File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'bin', 'spec')),
File.join(Config::CONFIG['bindir'], 'spec')
]
end
end

31
vendor/plugins/rspec/lib/spec.rb vendored Normal file
View file

@ -0,0 +1,31 @@
require 'spec/version'
require 'spec/matchers'
require 'spec/expectations'
require 'spec/example'
require 'spec/extensions'
require 'spec/runner'
require 'spec/adapters'
if Object.const_defined?(:Test)
require 'spec/interop/test'
end
module Spec
class << self
def run?
@run || rspec_options.examples_run?
end
def run
return true if run?
result = rspec_options.run_examples
@run = true
result
end
attr_writer :run
def exit?
!Object.const_defined?(:Test) || Test::Unit.run?
end
end
end

View file

@ -0,0 +1 @@
require 'spec/adapters/ruby_engine'

View file

@ -0,0 +1,26 @@
require 'spec/adapters/ruby_engine/mri'
require 'spec/adapters/ruby_engine/rubinius'
module Spec
module Adapters
module RubyEngine
ENGINES = {
'mri' => MRI.new,
'rbx' => Rubinius.new
}
def self.engine
if const_defined?(:RUBY_ENGINE)
return RUBY_ENGINE
else
return 'mri'
end
end
def self.adapter
return ENGINES[engine]
end
end
end
end

View file

@ -0,0 +1,8 @@
module Spec
module Adapters
module RubyEngine
class MRI
end
end
end
end

View file

@ -0,0 +1,8 @@
module Spec
module Adapters
module RubyEngine
class Rubinius
end
end
end
end

View file

@ -0,0 +1,12 @@
require 'timeout'
require 'spec/example/pending'
require 'spec/example/module_reopening_fix'
require 'spec/example/module_inclusion_warnings'
require 'spec/example/example_group_methods'
require 'spec/example/example_methods'
require 'spec/example/example_group'
require 'spec/example/shared_example_group'
require 'spec/example/example_group_factory'
require 'spec/example/errors'
require 'spec/example/configuration'
require 'spec/example/example_matcher'

View file

@ -0,0 +1,158 @@
module Spec
module Example
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 the following
# methods:
#
# setup_mocks_for_rspec
# verify_mocks_for_rspec
# 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
# :call-seq:
# include(Some::Helpers)
# include(Some::Helpers, More::Helpers)
# include(My::Helpers, :type => :key)
#
# Declares modules to be included in multiple example groups
# (<tt>describe</tt> blocks). With no :type, the modules listed will be
# included in all example groups. Use :type to restrict the inclusion to
# a subset of example groups. The value assigned to :type should be a
# key that maps to a class that is either a subclass of
# Spec::Example::ExampleGroup or extends Spec::Example::ExampleGroupMethods
# and includes Spec::Example::ExampleMethods
#
# config.include(My::Pony, My::Horse, :type => :farm)
#
# Only example groups that have that type will get the modules included:
#
# describe "Downtown", :type => :city do
# # Will *not* get My::Pony and My::Horse included
# end
#
# describe "Old Mac Donald", :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_example_group = get_type_from_options(options)
required_example_group = required_example_group.to_sym if required_example_group
modules.each do |mod|
ExampleGroupFactory.get(required_example_group).send(:include, mod)
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.can_swim? returns true
#
def predicate_matchers
@predicate_matchers ||= {}
end
# Prepends a global <tt>before</tt> block to all example groups.
# See #append_before for filtering semantics.
def prepend_before(*args, &proc)
scope, options = scope_and_options(*args)
example_group = ExampleGroupFactory.get(
get_type_from_options(options)
)
example_group.prepend_before(scope, &proc)
end
# Appends a global <tt>before</tt> block to all example groups.
#
# If you want to restrict the block to a subset of all the example
# groups then specify this in a Hash as the last argument:
#
# config.prepend_before(:all, :type => :farm)
#
# or
#
# config.prepend_before(:type => :farm)
#
def append_before(*args, &proc)
scope, options = scope_and_options(*args)
example_group = ExampleGroupFactory.get(
get_type_from_options(options)
)
example_group.append_before(scope, &proc)
end
alias_method :before, :append_before
# Prepends a global <tt>after</tt> block to all example groups.
# See #append_before for filtering semantics.
def prepend_after(*args, &proc)
scope, options = scope_and_options(*args)
example_group = ExampleGroupFactory.get(
get_type_from_options(options)
)
example_group.prepend_after(scope, &proc)
end
alias_method :after, :prepend_after
# Appends a global <tt>after</tt> block to all example groups.
# See #append_before for filtering semantics.
def append_after(*args, &proc)
scope, options = scope_and_options(*args)
example_group = ExampleGroupFactory.get(
get_type_from_options(options)
)
example_group.append_after(scope, &proc)
end
private
def scope_and_options(*args)
args, options = args_and_options(*args)
scope = (args[0] || :each), options
end
def get_type_from_options(options)
options[:type] || options[:behaviour_type]
end
def mock_framework_path(framework_name)
File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "plugins", "mock_frameworks", framework_name))
end
end
end
end

View file

@ -0,0 +1,9 @@
module Spec
module Example
class ExamplePendingError < StandardError
end
class PendingExampleFixedError < StandardError
end
end
end

View file

@ -0,0 +1,17 @@
module Spec
module Example
# Base class for customized example groups. Use this if you
# want to make a custom example group.
class ExampleGroup
extend Spec::Example::ExampleGroupMethods
include Spec::Example::ExampleMethods
def initialize(defined_description, &implementation)
@_defined_description = defined_description
@_implementation = implementation
end
end
end
end
Spec::ExampleGroup = Spec::Example::ExampleGroup

View file

@ -0,0 +1,64 @@
module Spec
module Example
class ExampleGroupFactory
class << self
def reset
@example_group_types = nil
default(ExampleGroup)
end
# Registers an example group class +klass+ with the symbol +type+. For
# example:
#
# Spec::Example::ExampleGroupFactory.register(:farm, FarmExampleGroup)
#
# With that you can append a hash with :type => :farm to the describe
# method and it will load an instance of FarmExampleGroup.
#
# describe Pig, :type => :farm do
# ...
#
# If you don't use the hash explicitly, <tt>describe</tt> will
# implicitly use an instance of FarmExampleGroup for any file loaded
# from the <tt>./spec/farm</tt> directory.
def register(key, example_group_class)
@example_group_types[key] = example_group_class
end
# Sets the default ExampleGroup class
def default(example_group_class)
old = @example_group_types
@example_group_types = Hash.new(example_group_class)
@example_group_types.merge!(old) if old
end
def get(key=nil)
if @example_group_types.values.include?(key)
key
else
@example_group_types[key]
end
end
def create_example_group(*args, &block)
opts = Hash === args.last ? args.last : {}
superclass = determine_superclass(opts)
superclass.describe(*args, &block)
end
protected
def determine_superclass(opts)
key = if opts[:type]
opts[:type]
elsif opts[:spec_path] =~ /spec(\\|\/)(#{@example_group_types.keys.join('|')})/
$2 == '' ? nil : $2.to_sym
end
get(key)
end
end
self.reset
end
end
end

View file

@ -0,0 +1,440 @@
module Spec
module Example
module ExampleGroupMethods
class << self
attr_accessor :matcher_class
def description_text(*args)
args.inject("") do |result, arg|
result << " " unless (result == "" || arg.to_s =~ /^(\s|\.|#)/)
result << arg.to_s
end
end
end
attr_reader :description_text, :description_args, :description_options, :spec_path, :registration_binding_block
def inherited(klass)
super
klass.register {}
Spec::Runner.register_at_exit_hook
end
# Makes the describe/it syntax available from a class. For example:
#
# class StackSpec < Spec::ExampleGroup
# describe Stack, "with no elements"
#
# before
# @stack = Stack.new
# end
#
# it "should raise on pop" do
# lambda{ @stack.pop }.should raise_error
# end
# end
#
def describe(*args, &example_group_block)
args << {} unless Hash === args.last
if example_group_block
params = args.last
params[:spec_path] = eval("caller(0)[1]", example_group_block) unless params[:spec_path]
if params[:shared]
SharedExampleGroup.new(*args, &example_group_block)
else
self.subclass("Subclass") do
describe(*args)
module_eval(&example_group_block)
end
end
else
set_description(*args)
before_eval
self
end
end
alias :context :describe
# Use this to pull in examples from shared example groups.
# See Spec::Runner for information about shared example groups.
def it_should_behave_like(shared_example_group)
case shared_example_group
when SharedExampleGroup
include shared_example_group
else
example_group = SharedExampleGroup.find_shared_example_group(shared_example_group)
unless example_group
raise RuntimeError.new("Shared Example Group '#{shared_example_group}' can not be found")
end
include(example_group)
end
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 ||= {:an_instance_of => :is_a?}
end
# Creates an instance of Spec::Example::Example and adds
# it to a collection of examples of the current example group.
def it(description=nil, &implementation)
e = new(description, &implementation)
example_objects << e
e
end
alias_method :specify, :it
# Use this to temporarily disable an example.
def xit(description=nil, opts={}, &block)
Kernel.warn("Example disabled: #{description}")
end
alias_method :xspecify, :xit
def run
examples = examples_to_run
reporter.add_example_group(self) unless examples_to_run.empty?
return true if examples.empty?
return dry_run(examples) if dry_run?
plugin_mock_framework
define_methods_from_predicate_matchers
success, before_all_instance_variables = run_before_all
success, after_all_instance_variables = execute_examples(success, before_all_instance_variables, examples)
success = run_after_all(success, after_all_instance_variables)
end
def description
result = ExampleGroupMethods.description_text(*description_parts)
if result.nil? || result == ""
return to_s
else
result
end
end
def described_type
description_parts.find {|part| part.is_a?(Module)}
end
def description_parts #:nodoc:
parts = []
execute_in_class_hierarchy do |example_group|
parts << example_group.description_args
end
parts.flatten.compact
end
def set_description(*args)
args, options = args_and_options(*args)
@description_args = args
@description_options = options
@description_text = ExampleGroupMethods.description_text(*args)
@spec_path = File.expand_path(options[:spec_path]) if options[:spec_path]
if described_type.class == Module
@described_module = described_type
end
self
end
attr_reader :described_module
def examples #:nodoc:
examples = example_objects.dup
add_method_examples(examples)
rspec_options.reverse ? examples.reverse : examples
end
def number_of_examples #:nodoc:
examples.length
end
# Registers a block to be executed before each example.
# This method prepends +block+ to existing before blocks.
def prepend_before(*args, &block)
scope, options = scope_and_options(*args)
parts = before_parts_from_scope(scope)
parts.unshift(block)
end
# Registers a block to be executed before each example.
# This method appends +block+ to existing before blocks.
def append_before(*args, &block)
scope, options = scope_and_options(*args)
parts = before_parts_from_scope(scope)
parts << block
end
alias_method :before, :append_before
# Registers a block to be executed after each example.
# This method prepends +block+ to existing after blocks.
def prepend_after(*args, &block)
scope, options = scope_and_options(*args)
parts = after_parts_from_scope(scope)
parts.unshift(block)
end
alias_method :after, :prepend_after
# Registers a block to be executed after each example.
# This method appends +block+ to existing after blocks.
def append_after(*args, &block)
scope, options = scope_and_options(*args)
parts = after_parts_from_scope(scope)
parts << block
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 # :nodoc:
@before_all_parts ||= []
end
def after_all_parts # :nodoc:
@after_all_parts ||= []
end
def before_each_parts # :nodoc:
@before_each_parts ||= []
end
def after_each_parts # :nodoc:
@after_each_parts ||= []
end
# Only used from RSpec's own examples
def reset # :nodoc:
@before_all_parts = nil
@after_all_parts = nil
@before_each_parts = nil
@after_each_parts = nil
end
def register(&registration_binding_block)
@registration_binding_block = registration_binding_block
rspec_options.add_example_group self
end
def unregister #:nodoc:
rspec_options.remove_example_group self
end
def registration_backtrace
eval("caller", registration_binding_block)
end
def run_before_each(example)
execute_in_class_hierarchy do |example_group|
example.eval_each_fail_fast(example_group.before_each_parts)
end
end
def run_after_each(example)
execute_in_class_hierarchy(:superclass_first) do |example_group|
example.eval_each_fail_slow(example_group.after_each_parts)
end
end
private
def dry_run(examples)
examples.each do |example|
rspec_options.reporter.example_started(example)
rspec_options.reporter.example_finished(example)
end
return true
end
def run_before_all
before_all = new("before(:all)")
begin
execute_in_class_hierarchy do |example_group|
before_all.eval_each_fail_fast(example_group.before_all_parts)
end
return [true, before_all.instance_variable_hash]
rescue Exception => e
reporter.failure(before_all, e)
return [false, before_all.instance_variable_hash]
end
end
def execute_examples(success, instance_variables, examples)
return [success, instance_variables] unless success
after_all_instance_variables = instance_variables
examples.each do |example_group_instance|
success &= example_group_instance.execute(rspec_options, instance_variables)
after_all_instance_variables = example_group_instance.instance_variable_hash
end
return [success, after_all_instance_variables]
end
def run_after_all(success, instance_variables)
after_all = new("after(:all)")
after_all.set_instance_variables_from_hash(instance_variables)
execute_in_class_hierarchy(:superclass_first) do |example_group|
after_all.eval_each_fail_slow(example_group.after_all_parts)
end
return success
rescue Exception => e
reporter.failure(after_all, e)
return false
end
def examples_to_run
all_examples = examples
return all_examples unless specified_examples?
all_examples.reject do |example|
matcher = ExampleGroupMethods.matcher_class.
new(description.to_s, example.description)
!matcher.matches?(specified_examples)
end
end
def specified_examples?
specified_examples && !specified_examples.empty?
end
def specified_examples
rspec_options.examples
end
def reporter
rspec_options.reporter
end
def dry_run?
rspec_options.dry_run
end
def example_objects
@example_objects ||= []
end
def execute_in_class_hierarchy(superclass_last=false)
classes = []
current_class = self
while is_example_group?(current_class)
superclass_last ? classes << current_class : classes.unshift(current_class)
current_class = current_class.superclass
end
superclass_last ? classes << ExampleMethods : classes.unshift(ExampleMethods)
classes.each do |example_group|
yield example_group
end
end
def is_example_group?(klass)
Module === klass && klass.kind_of?(ExampleGroupMethods)
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 define_methods_from_predicate_matchers # :nodoc:
all_predicate_matchers = predicate_matchers.merge(
Spec::Runner.configuration.predicate_matchers
)
all_predicate_matchers.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
def scope_and_options(*args)
args, options = args_and_options(*args)
scope = (args[0] || :each), options
end
def before_parts_from_scope(scope)
case scope
when :each; before_each_parts
when :all; before_all_parts
when :suite; rspec_options.before_suite_parts
end
end
def after_parts_from_scope(scope)
case scope
when :each; after_each_parts
when :all; after_all_parts
when :suite; rspec_options.after_suite_parts
end
end
def before_eval
end
def add_method_examples(examples)
instance_methods.sort.each do |method_name|
if example_method?(method_name)
examples << new(method_name) do
__send__(method_name)
end
end
end
end
def example_method?(method_name)
should_method?(method_name)
end
def should_method?(method_name)
!(method_name =~ /^should(_not)?$/) &&
method_name =~ /^should/ && (
instance_method(method_name).arity == 0 ||
instance_method(method_name).arity == -1
)
end
end
end
end

View file

@ -0,0 +1,44 @@
module Spec
module Example
class ExampleMatcher
def initialize(example_group_description, example_name)
@example_group_description = example_group_description
@example_name = example_name
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
protected
def matches_literal_example?(specified_example)
specified_example =~ /(^#{example_group_regex} #{example_regexp}$|^#{example_group_regex}$|^#{example_group_with_before_all_regexp}$|^#{example_regexp}$)/
end
def matches_example_not_considering_modules?(specified_example)
specified_example =~ /(^#{example_group_regex_not_considering_modules} #{example_regexp}$|^#{example_group_regex_not_considering_modules}$|^#{example_regexp}$)/
end
def example_group_regex
Regexp.escape(@example_group_description)
end
def example_group_with_before_all_regexp
Regexp.escape("#{@example_group_description} before(:all)")
end
def example_group_regex_not_considering_modules
Regexp.escape(@example_group_description.split('::').last)
end
def example_regexp
Regexp.escape(@example_name)
end
end
ExampleGroupMethods.matcher_class = ExampleMatcher
end
end

View file

@ -0,0 +1,112 @@
module Spec
module Example
module ExampleMethods
extend ExampleGroupMethods
extend ModuleReopeningFix
include ModuleInclusionWarnings
PENDING_EXAMPLE_BLOCK = lambda {
raise Spec::Example::ExamplePendingError.new("Not Yet Implemented")
}
def execute(options, instance_variables)
options.reporter.example_started(self)
set_instance_variables_from_hash(instance_variables)
execution_error = nil
Timeout.timeout(options.timeout) do
begin
before_example
run_with_description_capturing
rescue Exception => e
execution_error ||= e
end
begin
after_example
rescue Exception => e
execution_error ||= e
end
end
options.reporter.example_finished(self, execution_error)
success = execution_error.nil? || ExamplePendingError === execution_error
end
def instance_variable_hash
instance_variables.inject({}) do |variable_hash, variable_name|
variable_hash[variable_name] = instance_variable_get(variable_name)
variable_hash
end
end
def violated(message="")
raise Spec::Expectations::ExpectationNotMetError.new(message)
end
def eval_each_fail_fast(procs) #:nodoc:
procs.each do |proc|
instance_eval(&proc)
end
end
def eval_each_fail_slow(procs) #:nodoc:
first_exception = nil
procs.each do |proc|
begin
instance_eval(&proc)
rescue Exception => e
first_exception ||= e
end
end
raise first_exception if first_exception
end
def description
@_defined_description || @_matcher_description || "NO NAME"
end
def __full_description
"#{self.class.description} #{self.description}"
end
def set_instance_variables_from_hash(ivars)
ivars.each do |variable_name, value|
# Ruby 1.9 requires variable.to_s on the next line
unless ['@_implementation', '@_defined_description', '@_matcher_description', '@method_name'].include?(variable_name.to_s)
instance_variable_set variable_name, value
end
end
end
def run_with_description_capturing
begin
return instance_eval(&(@_implementation || PENDING_EXAMPLE_BLOCK))
ensure
@_matcher_description = Spec::Matchers.generated_description
Spec::Matchers.clear_generated_description
end
end
def implementation_backtrace
eval("caller", @_implementation)
end
protected
include Matchers
include Pending
def before_example
setup_mocks_for_rspec
self.class.run_before_each(self)
end
def after_example
self.class.run_after_each(self)
verify_mocks_for_rspec
ensure
teardown_mocks_for_rspec
end
end
end
end

View file

@ -0,0 +1,37 @@
module Spec
module Example
# In the future, modules will no longer be automatically included
# in the Example Group (based on the description name); when that
# time comes, this code should be removed.
module ModuleInclusionWarnings
# Thanks, Francis Hwang.
class MethodDispatcher
def initialize(mod, target=nil)
@mod = mod
@target = target
end
def respond_to?(sym)
@mod && @mod.instance_methods.include?(sym.to_s)
end
def call(sym, *args, &blk)
Kernel.warn("Modules will no longer be automatically included in RSpec version 1.1.4. Called from #{caller[2]}")
@target.extend @mod
@target.send(sym, *args, &blk)
end
end
def respond_to?(sym)
MethodDispatcher.new(self.class.described_module).respond_to?(sym) ? true : super
end
private
def method_missing(sym, *args, &blk)
md = MethodDispatcher.new(self.class.described_module, self)
self.respond_to?(sym) ? md.call(sym, *args, &blk) : super
end
end
end
end

View file

@ -0,0 +1,21 @@
module Spec
module Example
# This is a fix for ...Something in Ruby 1.8.6??... (Someone fill in here please - Aslak)
module ModuleReopeningFix
def child_modules
@child_modules ||= []
end
def included(mod)
child_modules << mod
end
def include(mod)
super
child_modules.each do |child_module|
child_module.__send__(:include, mod)
end
end
end
end
end

View file

@ -0,0 +1,18 @@
module Spec
module Example
module Pending
def pending(message = "TODO")
if block_given?
begin
yield
rescue Exception => e
raise Spec::Example::ExamplePendingError.new(message)
end
raise Spec::Example::PendingExampleFixedError.new("Expected pending '#{message}' to fail. No Error was raised.")
else
raise Spec::Example::ExamplePendingError.new(message)
end
end
end
end
end

View file

@ -0,0 +1,58 @@
module Spec
module Example
class SharedExampleGroup < Module
class << self
def add_shared_example_group(new_example_group)
guard_against_redefining_existing_example_group(new_example_group)
shared_example_groups << new_example_group
end
def find_shared_example_group(example_group_description)
shared_example_groups.find do |b|
b.description == example_group_description
end
end
def shared_example_groups
# TODO - this needs to be global, or at least accessible from
# from subclasses of Example in a centralized place. I'm not loving
# this as a solution, but it works for now.
$shared_example_groups ||= []
end
private
def guard_against_redefining_existing_example_group(new_example_group)
existing_example_group = find_shared_example_group(new_example_group.description)
return unless existing_example_group
return if new_example_group.equal?(existing_example_group)
return if spec_path(new_example_group) == spec_path(existing_example_group)
raise ArgumentError.new("Shared Example '#{existing_example_group.description}' already exists")
end
def spec_path(example_group)
File.expand_path(example_group.spec_path)
end
end
include ExampleGroupMethods
public :include
def initialize(*args, &example_group_block)
describe(*args)
@example_group_block = example_group_block
self.class.add_shared_example_group(self)
end
def included(mod) # :nodoc:
mod.module_eval(&@example_group_block)
end
def execute_in_class_hierarchy(superclass_last=false)
classes = [self]
superclass_last ? classes << ExampleMethods : classes.unshift(ExampleMethods)
classes.each do |example_group|
yield example_group
end
end
end
end
end

View file

@ -0,0 +1,56 @@
require 'spec/matchers'
require 'spec/expectations/errors'
require 'spec/expectations/extensions'
require 'spec/expectations/handler'
module Spec
# Spec::Expectations lets you set expectations on your objects.
#
# result.should == 37
# team.should have(11).players_on_the_field
#
# == How Expectations work.
#
# Spec::Expectations adds two methods to Object:
#
# should(matcher=nil)
# should_not(matcher=nil)
#
# Both methods take an optional Expression Matcher (See Spec::Matchers).
#
# When +should+ receives an Expression Matcher, it calls <tt>matches?(self)</tt>. If
# it returns +true+, the spec passes and execution continues. If it returns
# +false+, then the spec fails with the message returned by <tt>matcher.failure_message</tt>.
#
# Similarly, when +should_not+ receives a matcher, it calls <tt>matches?(self)</tt>. If
# it returns +false+, the spec passes and execution continues. If it returns
# +true+, then the spec fails with the message returned by <tt>matcher.negative_failure_message</tt>.
#
# RSpec ships with a standard set of useful matchers, and writing your own
# matchers is quite simple. See Spec::Matchers for details.
module Expectations
class << self
attr_accessor :differ
# raises a Spec::Expectations::ExpectationNotMetError with message
#
# When a differ has been assigned and fail_with is passed
# <code>expected</code> and <code>target</code>, passes them
# to the differ to append a diff message to the failure message.
def fail_with(message, expected=nil, target=nil) # :nodoc:
if Array === message && message.length == 3
message, expected, target = message[0], message[1], message[2]
end
unless (differ.nil? || expected.nil? || target.nil?)
if expected.is_a?(String)
message << "\nDiff:" << self.differ.diff_as_string(target.to_s, expected)
elsif !target.is_a?(Proc)
message << "\nDiff:" << self.differ.diff_as_object(target, expected)
end
end
Kernel::raise(Spec::Expectations::ExpectationNotMetError.new(message))
end
end
end
end

View file

@ -0,0 +1,66 @@
begin
require 'rubygems'
require 'diff/lcs' #necessary due to loading bug on some machines - not sure why - DaC
require 'diff/lcs/hunk'
rescue LoadError ; raise "You must gem install diff-lcs to use diffing" ; end
require 'pp'
module Spec
module Expectations
module Differs
# TODO add some rdoc
class Default
def initialize(options)
@options = options
end
# This is snagged from diff/lcs/ldiff.rb (which is a commandline tool)
def diff_as_string(data_new, data_old)
data_old = data_old.split(/\n/).map! { |e| e.chomp }
data_new = data_new.split(/\n/).map! { |e| e.chomp }
output = ""
diffs = Diff::LCS.diff(data_old, data_new)
return output if diffs.empty?
oldhunk = hunk = nil
file_length_difference = 0
diffs.each do |piece|
begin
hunk = Diff::LCS::Hunk.new(data_old, data_new, piece, context_lines,
file_length_difference)
file_length_difference = hunk.file_length_difference
next unless oldhunk
# Hunks may overlap, which is why we need to be careful when our
# diff includes lines of context. Otherwise, we might print
# redundant lines.
if (context_lines > 0) and hunk.overlaps?(oldhunk)
hunk.unshift(oldhunk)
else
output << oldhunk.diff(format)
end
ensure
oldhunk = hunk
output << "\n"
end
end
#Handle the last remaining hunk
output << oldhunk.diff(format) << "\n"
end
def diff_as_object(target,expected)
diff_as_string(PP.pp(target,""), PP.pp(expected,""))
end
protected
def format
@options.diff_format
end
def context_lines
@options.context_lines
end
end
end
end
end

View file

@ -0,0 +1,12 @@
module Spec
module Expectations
# If Test::Unit is loaed, we'll use its error as baseclass, so that Test::Unit
# will report unmet RSpec expectations as failures rather than errors.
superclass = ['Test::Unit::AssertionFailedError', '::StandardError'].map do |c|
eval(c) rescue nil
end.compact.first
class ExpectationNotMetError < superclass
end
end
end

View file

@ -0,0 +1,2 @@
require 'spec/expectations/extensions/object'
require 'spec/expectations/extensions/string_and_symbol'

View file

@ -0,0 +1,63 @@
module Spec
module Expectations
# rspec adds #should and #should_not to every Object (and,
# implicitly, every Class).
module ObjectExpectations
# :call-seq:
# should(matcher)
# should == expected
# should === expected
# should =~ expected
#
# receiver.should(matcher)
# => Passes if matcher.matches?(receiver)
#
# 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)
#
# See Spec::Matchers for more information about matchers
#
# == Warning
#
# NOTE that this does NOT support receiver.should != expected.
# Instead, use receiver.should_not == expected
def should(matcher=:use_operator_matcher, &block)
ExpectationMatcherHandler.handle_matcher(self, matcher, &block)
end
# :call-seq:
# should_not(matcher)
# should_not == expected
# should_not === expected
# should_not =~ expected
#
# receiver.should_not(matcher)
# => Passes unless matcher.matches?(receiver)
#
# receiver.should_not == expected
# => Passes unless (receiver == expected)
#
# receiver.should_not === 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=:use_operator_matcher, &block)
NegativeExpectationMatcherHandler.handle_matcher(self, matcher, &block)
end
end
end
end
class Object
include Spec::Expectations::ObjectExpectations
end

View file

@ -0,0 +1,17 @@
module Spec
module Expectations
module StringHelpers
def starts_with?(prefix)
to_s[0..(prefix.to_s.length - 1)] == prefix.to_s
end
end
end
end
class String
include Spec::Expectations::StringHelpers
end
class Symbol
include Spec::Expectations::StringHelpers
end

View file

@ -0,0 +1,60 @@
module Spec
module Expectations
class InvalidMatcherError < ArgumentError; end
module MatcherHandlerHelper
def describe_matcher(matcher)
matcher.respond_to?(:description) ? matcher.description : "[#{matcher.class.name} does not provide a description]"
end
end
class ExpectationMatcherHandler
class << self
include MatcherHandlerHelper
def handle_matcher(actual, matcher, &block)
if :use_operator_matcher == matcher
return Spec::Matchers::PositiveOperatorMatcher.new(actual)
end
unless matcher.respond_to?(:matches?)
raise InvalidMatcherError, "Expected a matcher, got #{matcher.inspect}."
end
match = matcher.matches?(actual, &block)
::Spec::Matchers.generated_description = "should #{describe_matcher(matcher)}"
Spec::Expectations.fail_with(matcher.failure_message) unless match
end
end
end
class NegativeExpectationMatcherHandler
class << self
include MatcherHandlerHelper
def handle_matcher(actual, matcher, &block)
if :use_operator_matcher == matcher
return Spec::Matchers::NegativeOperatorMatcher.new(actual)
end
unless matcher.respond_to?(:matches?)
raise InvalidMatcherError, "Expected a matcher, got #{matcher.inspect}."
end
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(matcher)}"
Spec::Expectations.fail_with(matcher.negative_failure_message) if match
end
end
end
end
end

4
vendor/plugins/rspec/lib/spec/extensions.rb vendored Executable file
View file

@ -0,0 +1,4 @@
require 'spec/extensions/object'
require 'spec/extensions/class'
require 'spec/extensions/main'
require 'spec/extensions/metaclass'

View file

@ -0,0 +1,24 @@
class Class
# Creates a new subclass of self, with a name "under" our own name.
# Example:
#
# x = Foo::Bar.subclass('Zap'){}
# x.name # => Foo::Bar::Zap_1
# x.superclass.name # => Foo::Bar
def subclass(base_name, &body)
klass = Class.new(self)
class_name = "#{base_name}_#{class_count!}"
instance_eval do
const_set(class_name, klass)
end
klass.instance_eval(&body)
klass
end
private
def class_count!
@class_count ||= 0
@class_count += 1
@class_count
end
end

View file

@ -0,0 +1,102 @@
module Spec
module Extensions
module Main
# Creates and returns a class that includes the ExampleGroupMethods
# module. Which ExampleGroup type is created 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 example group
# type with an options Hash as the last argument:
#
# describe "name", :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::Example::ExampleFactory#register for details about how to
# register special implementations.
#
def describe(*args, &block)
raise ArgumentError if args.empty?
raise ArgumentError unless block
args << {} unless Hash === args.last
args.last[:spec_path] = caller(0)[1]
Spec::Example::ExampleGroupFactory.create_example_group(*args, &block)
end
alias :context :describe
# Creates an example group that can be shared by other example groups
#
# == Examples
#
# share_examples_for "All Editions" do
# it "all editions behaviour" ...
# end
#
# describe SmallEdition do
# it_should_behave_like "All Editions"
#
# it "should do small edition stuff" do
# ...
# end
# end
def share_examples_for(name, &block)
describe(name, :shared => true, &block)
end
alias :shared_examples_for :share_examples_for
# Creates a Shared Example Group and assigns it to a constant
#
# share_as :AllEditions do
# it "should do all editions stuff" ...
# end
#
# describe SmallEdition do
# it_should_behave_like AllEditions
#
# it "should do small edition stuff" do
# ...
# end
# end
#
# And, for those of you who prefer to use something more like Ruby, you
# can just include the module directly
#
# describe SmallEdition do
# include AllEditions
#
# it "should do small edition stuff" do
# ...
# end
# end
def share_as(name, &block)
begin
Object.const_set(name, share_examples_for(name, &block))
rescue NameError => e
raise NameError.new(e.message + "\nThe first argument to share_as must be a legal name for a constant\n")
end
end
private
def rspec_options
$rspec_options ||= begin; \
parser = ::Spec::Runner::OptionParser.new(STDERR, STDOUT); \
parser.order!(ARGV); \
$rspec_options = parser.options; \
end
$rspec_options
end
def init_rspec_options(options)
$rspec_options = options if $rspec_options.nil?
end
end
end
end
include Spec::Extensions::Main

View file

@ -0,0 +1,7 @@
module Spec
module MetaClass
def metaclass
class << self; self; end
end
end
end

View file

@ -0,0 +1,6 @@
class Object
def args_and_options(*args)
options = Hash === args.last ? args.pop : {}
return args, options
end
end

View file

@ -0,0 +1,12 @@
require 'test/unit'
require 'test/unit/testresult'
require 'spec/interop/test/unit/testcase'
require 'spec/interop/test/unit/testsuite_adapter'
require 'spec/interop/test/unit/autorunner'
require 'spec/interop/test/unit/testresult'
require 'spec/interop/test/unit/ui/console/testrunner'
Spec::Example::ExampleGroupFactory.default(Test::Unit::TestCase)
Test::Unit.run = true

View file

@ -0,0 +1,6 @@
class Test::Unit::AutoRunner
remove_method :process_args
def process_args(argv)
true
end
end

View file

@ -0,0 +1,61 @@
require 'test/unit/testcase'
module Test
module Unit
# This extension of the standard Test::Unit::TestCase makes RSpec
# available from within, so that you can do things like:
#
# require 'test/unit'
# require 'spec'
#
# class MyTest < Test::Unit::TestCase
# it "should work with Test::Unit assertions" do
# assert_equal 4, 2+1
# end
#
# def test_should_work_with_rspec_expectations
# (3+1).should == 5
# end
# end
#
# See also Spec::Example::ExampleGroup
class TestCase
extend Spec::Example::ExampleGroupMethods
include Spec::Example::ExampleMethods
before(:each) {setup}
after(:each) {teardown}
class << self
def suite
Test::Unit::TestSuiteAdapter.new(self)
end
def example_method?(method_name)
should_method?(method_name) || test_method?(method_name)
end
def test_method?(method_name)
method_name =~ /^test[_A-Z]./ && (
instance_method(method_name).arity == 0 ||
instance_method(method_name).arity == -1
)
end
end
def initialize(defined_description, &implementation)
@_defined_description = defined_description
@_implementation = implementation
@_result = ::Test::Unit::TestResult.new
# @method_name is important to set here because it "complies" with Test::Unit's interface.
# Some Test::Unit extensions depend on @method_name being present.
@method_name = @_defined_description
end
def run(ignore_this_argument=nil)
super()
end
end
end
end

View file

@ -0,0 +1,6 @@
class Test::Unit::TestResult
alias_method :tu_passed?, :passed?
def passed?
return tu_passed? & ::Spec.run
end
end

View file

@ -0,0 +1,34 @@
module Test
module Unit
class TestSuiteAdapter < TestSuite
attr_reader :example_group, :examples
alias_method :tests, :examples
def initialize(example_group)
@example_group = example_group
@examples = example_group.examples
end
def name
example_group.description
end
def run(*args)
return true unless args.empty?
example_group.run
end
def size
example_group.number_of_examples
end
def delete(example)
examples.delete example
end
def empty?
examples.empty?
end
end
end
end

View file

@ -0,0 +1,61 @@
require 'test/unit/ui/console/testrunner'
module Test
module Unit
module UI
module Console
class TestRunner
alias_method :started_without_rspec, :started
def started_with_rspec(result)
@result = result
@need_to_output_started = true
end
alias_method :started, :started_with_rspec
alias_method :test_started_without_rspec, :test_started
def test_started_with_rspec(name)
if @need_to_output_started
if @rspec_io
@rspec_io.rewind
output(@rspec_io.read)
end
output("Started")
@need_to_output_started = false
end
test_started_without_rspec(name)
end
alias_method :test_started, :test_started_with_rspec
alias_method :test_finished_without_rspec, :test_finished
def test_finished_with_rspec(name)
test_finished_without_rspec(name)
@ran_test = true
end
alias_method :test_finished, :test_finished_with_rspec
alias_method :finished_without_rspec, :finished
def finished_with_rspec(elapsed_time)
@ran_test ||= false
if @ran_test
finished_without_rspec(elapsed_time)
end
end
alias_method :finished, :finished_with_rspec
alias_method :setup_mediator_without_rspec, :setup_mediator
def setup_mediator_with_rspec
orig_io = @io
@io = StringIO.new
setup_mediator_without_rspec
ensure
@rspec_io = @io
@io = orig_io
end
alias_method :setup_mediator, :setup_mediator_with_rspec
end
end
end
end
end

View file

@ -0,0 +1,156 @@
require 'spec/matchers/simple_matcher'
require 'spec/matchers/be'
require 'spec/matchers/be_close'
require 'spec/matchers/change'
require 'spec/matchers/eql'
require 'spec/matchers/equal'
require 'spec/matchers/exist'
require 'spec/matchers/has'
require 'spec/matchers/have'
require 'spec/matchers/include'
require 'spec/matchers/match'
require 'spec/matchers/raise_error'
require 'spec/matchers/respond_to'
require 'spec/matchers/satisfy'
require 'spec/matchers/throw_symbol'
require 'spec/matchers/operator_matcher'
module Spec
# RSpec ships with a number of useful Expression Matchers. An Expression Matcher
# is any object that responds to the following methods:
#
# matches?(actual)
# failure_message
# negative_failure_message #optional
# description #optional
#
# See Spec::Expectations to learn how to use these as Expectation Matchers.
# See Spec::Mocks to learn how to use them as Mock Argument Constraints.
#
# == Predicates
#
# In addition to those Expression Matchers that are defined explicitly, RSpec will
# create custom Matchers on the fly for any arbitrary predicate, giving your specs
# a much more natural language feel.
#
# A Ruby predicate is a method that ends with a "?" and returns true or false.
# Common examples are +empty?+, +nil?+, and +instance_of?+.
#
# All you need to do is write +should be_+ followed by the predicate without
# the question mark, and RSpec will figure it out from there. For example:
#
# [].should be_empty => [].empty? #passes
# [].should_not be_empty => [].empty? #fails
#
# In addtion to prefixing the predicate matchers with "be_", you can also use "be_a_"
# and "be_an_", making your specs read much more naturally:
#
# "a string".should be_an_instance_of(String) =>"a string".instance_of?(String) #passes
#
# 3.should be_a_kind_of(Fixnum) => 3.kind_of?(Numeric) #passes
# 3.should be_a_kind_of(Numeric) => 3.kind_of?(Numeric) #passes
# 3.should be_an_instance_of(Fixnum) => 3.instance_of?(Fixnum) #passes
# 3.should_not be_instance_of(Numeric) => 3.instance_of?(Numeric) #fails
#
# RSpec will also create custom matchers for predicates like +has_key?+. To
# use this feature, just state that the object should have_key(:key) and RSpec will
# call has_key?(:key) on the target. For example:
#
# {:a => "A"}.should have_key(:a) => {:a => "A"}.has_key?(:a) #passes
# {:a => "A"}.should have_key(:b) => {:a => "A"}.has_key?(:b) #fails
#
# You can use this feature to invoke any predicate that begins with "has_", whether it is
# part of the Ruby libraries (like +Hash#has_key?+) or a method you wrote on your own class.
#
# == Custom Expectation Matchers
#
# 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
# be in various zones on a virtual board. To specify that bob should
# be in zone 4, you could say:
#
# bob.current_zone.should eql(Zone.new("4"))
#
# But you might find it more expressive to say:
#
# bob.should be_in_zone("4")
#
# and/or
#
# bob.should_not be_in_zone("3")
#
# To do this, you would need to write a class like this:
#
# class BeInZone
# def initialize(expected)
# @expected = expected
# end
# def matches?(target)
# @target = target
# @target.current_zone.eql?(Zone.new(@expected))
# end
# def failure_message
# "expected #{@target.inspect} to be in Zone #{@expected}"
# end
# def negative_failure_message
# "expected #{@target.inspect} not to be in Zone #{@expected}"
# end
# end
#
# ... and a method like this:
#
# def be_in_zone(expected)
# BeInZone.new(expected)
# end
#
# And then expose the method to your specs. This is normally done
# by including the method and the class in a module, which is then
# included in your spec:
#
# module CustomGameMatchers
# class BeInZone
# ...
# end
#
# def be_in_zone(expected)
# ...
# end
# end
#
# 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
module ModuleMethods
attr_accessor :generated_description
def clear_generated_description
self.generated_description = nil
end
end
extend ModuleMethods
def method_missing(sym, *args, &block) # :nodoc:
return Matchers::Be.new(sym, *args) if sym.starts_with?("be_")
return Matchers::Has.new(sym, *args) if sym.starts_with?("have_")
super
end
class MatcherError < StandardError
end
end
end

View file

@ -0,0 +1,224 @@
module Spec
module Matchers
class Be #:nodoc:
def initialize(*args)
if args.empty?
@expected = :satisfy_if
else
@expected = parse_expected(args.shift)
end
@args = args
@comparison = ""
end
def matches?(actual)
@actual = actual
if handling_predicate?
begin
return @result = actual.__send__(predicate, *@args)
rescue => predicate_error
# This clause should be empty, but rcov will not report it as covered
# unless something (anything) is executed within the clause
rcov_error_report = "http://eigenclass.org/hiki.rb?rcov-0.8.0"
end
# This supports should_exist > target.exists? in the old world.
# We should consider deprecating that ability as in the new world
# you can't write "should exist" unless you have your own custom matcher.
begin
return @result = actual.__send__(present_tense_predicate, *@args)
rescue
raise predicate_error
end
else
return match_or_compare
end
end
def failure_message
return "expected #{@comparison}#{expected}, got #{@actual.inspect}" unless handling_predicate?
return "expected #{predicate}#{args_to_s} to return true, got #{@result.inspect}"
end
def negative_failure_message
return "expected not #{expected}, got #{@actual.inspect}" unless handling_predicate?
return "expected #{predicate}#{args_to_s} to return false, got #{@result.inspect}"
end
def expected
return "if to be satisfied" if @expected == :satisfy_if
return true if @expected == :true
return false if @expected == :false
return "nil" if @expected == :nil
return @expected.inspect
end
def match_or_compare
return @actual ? true : false if @expected == :satisfy_if
return @actual == true if @expected == :true
return @actual == false if @expected == :false
return @actual.nil? if @expected == :nil
return @actual < @expected if @less_than
return @actual <= @expected if @less_than_or_equal
return @actual >= @expected if @greater_than_or_equal
return @actual > @expected if @greater_than
return @actual == @expected if @double_equal
return @actual === @expected if @triple_equal
return @actual.equal?(@expected)
end
def ==(expected)
@prefix = "be "
@double_equal = true
@comparison = "== "
@expected = expected
self
end
def ===(expected)
@prefix = "be "
@triple_equal = true
@comparison = "=== "
@expected = expected
self
end
def <(expected)
@prefix = "be "
@less_than = true
@comparison = "< "
@expected = expected
self
end
def <=(expected)
@prefix = "be "
@less_than_or_equal = true
@comparison = "<= "
@expected = expected
self
end
def >=(expected)
@prefix = "be "
@greater_than_or_equal = true
@comparison = ">= "
@expected = expected
self
end
def >(expected)
@prefix = "be "
@greater_than = true
@comparison = "> "
@expected = expected
self
end
def description
"#{prefix_to_sentence}#{comparison}#{expected_to_sentence}#{args_to_sentence}"
end
private
def parse_expected(expected)
if Symbol === expected
@handling_predicate = true
["be_an_","be_a_","be_"].each do |prefix|
if expected.starts_with?(prefix)
@prefix = prefix
return "#{expected.to_s.sub(@prefix,"")}".to_sym
end
end
end
@prefix = ""
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
end
def present_tense_predicate
"#{@expected.to_s}s?".to_sym
end
def args_to_s
return "" if @args.empty?
inspected_args = @args.collect{|a| a.inspect}
return "(#{inspected_args.join(', ')})"
end
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:
# should be
# should be_true
# should be_false
# should be_nil
# should be_arbitrary_predicate(*args)
# should_not be_nil
# should_not be_arbitrary_predicate(*args)
#
# Given true, false, or nil, will pass if actual is
# true, false or nil (respectively). Given no args means
# the caller should satisfy an if condition (to be or not to be).
#
# Predicates are any Ruby method that ends in a "?" and returns true or false.
# Given be_ followed by arbitrary_predicate (without the "?"), RSpec will match
# convert that into a query against the target object.
#
# The arbitrary_predicate feature will handle any predicate
# prefixed with "be_an_" (e.g. be_an_instance_of), "be_a_" (e.g. be_a_kind_of)
# or "be_" (e.g. be_empty), letting you choose the prefix that best suits the predicate.
#
# == Examples
#
# target.should be
# target.should be_true
# target.should be_false
# target.should be_nil
# target.should_not be_nil
#
# collection.should be_empty #passes if target.empty?
# "this string".should be_an_intance_of(String)
#
# target.should_not be_empty #passes unless target.empty?
# target.should_not be_old_enough(16) #passes unless target.old_enough?(16)
def be(*args)
Matchers::Be.new(*args)
end
end
end

View file

@ -0,0 +1,37 @@
module Spec
module Matchers
class BeClose #:nodoc:
def initialize(expected, delta)
@expected = expected
@delta = delta
end
def matches?(actual)
@actual = actual
(@actual - @expected).abs < @delta
end
def failure_message
"expected #{@expected} +/- (< #{@delta}), got #{@actual}"
end
def description
"be close to #{@expected} (within +- #{@delta})"
end
end
# :call-seq:
# should be_close(expected, delta)
# should_not be_close(expected, delta)
#
# Passes if actual == expected +/- delta
#
# == Example
#
# result.should be_close(3.0, 0.5)
def be_close(expected, delta)
Matchers::BeClose.new(expected, delta)
end
end
end

View file

@ -0,0 +1,144 @@
module Spec
module Matchers
#Based on patch from Wilson Bilkovich
class Change #:nodoc:
def initialize(receiver=nil, message=nil, &block)
@receiver = receiver
@message = message
@block = block
end
def matches?(target, &block)
if block
raise MatcherError.new(<<-EOF
block passed to should or should_not change must use {} instead of do/end
EOF
)
end
@target = target
execute_change
return false if @from && (@from != @before)
return false if @to && (@to != @after)
return (@before + @amount == @after) if @amount
return ((@after - @before) >= @minimum) if @minimum
return ((@after - @before) <= @maximum) if @maximum
return @before != @after
end
def execute_change
@before = @block.nil? ? @receiver.send(@message) : @block.call
@target.call
@after = @block.nil? ? @receiver.send(@message) : @block.call
end
def failure_message
if @to
"#{result} should have been changed to #{@to.inspect}, but is now #{@after.inspect}"
elsif @from
"#{result} should have initially been #{@from.inspect}, but was #{@before.inspect}"
elsif @amount
"#{result} should have been changed by #{@amount.inspect}, but was changed by #{actual_delta.inspect}"
elsif @minimum
"#{result} should have been changed by at least #{@minimum.inspect}, but was changed by #{actual_delta.inspect}"
elsif @maximum
"#{result} should have been changed by at most #{@maximum.inspect}, but was changed by #{actual_delta.inspect}"
else
"#{result} should have changed, but is still #{@before.inspect}"
end
end
def result
@message || "result"
end
def actual_delta
@after - @before
end
def negative_failure_message
"#{result} should not have changed, but did change from #{@before.inspect} to #{@after.inspect}"
end
def by(amount)
@amount = amount
self
end
def by_at_least(minimum)
@minimum = minimum
self
end
def by_at_most(maximum)
@maximum = maximum
self
end
def to(to)
@to = to
self
end
def from (from)
@from = from
self
end
end
# :call-seq:
# should change(receiver, message, &block)
# should change(receiver, message, &block).by(value)
# should change(receiver, message, &block).from(old).to(new)
# should_not change(receiver, message, &block)
#
# Allows you to specify that a Proc will cause some value to change.
#
# == Examples
#
# lambda {
# team.add_player(player)
# }.should change(roster, :count)
#
# lambda {
# team.add_player(player)
# }.should change(roster, :count).by(1)
#
# lambda {
# team.add_player(player)
# }.should change(roster, :count).by_at_least(1)
#
# lambda {
# team.add_player(player)
# }.should change(roster, :count).by_at_most(1)
#
# string = "string"
# lambda {
# string.reverse!
# }.should change { string }.from("string").to("gnirts")
#
# lambda {
# person.happy_birthday
# }.should change(person, :birthday).from(32).to(33)
#
# lambda {
# employee.develop_great_new_social_networking_app
# }.should change(employee, :title).from("Mail Clerk").to("CEO")
#
# Evaluates +receiver.message+ or +block+ before and
# after it evaluates the c object (generated by the lambdas in the examples above).
#
# Then compares the values before and after the +receiver.message+ and
# evaluates the difference compared to the expected difference.
#
# == Warning
# +should_not+ +change+ only supports the form with no subsequent calls to
# +by+, +by_at_least+, +by_at_most+, +to+ or +from+.
#
# blocks passed to +should+ +change+ and +should_not+ +change+
# must use the <tt>{}</tt> form (<tt>do/end</tt> is not supported)
def change(target=nil, message=nil, &block)
Matchers::Change.new(target, message, &block)
end
end
end

View file

@ -0,0 +1,43 @@
module Spec
module Matchers
class Eql #:nodoc:
def initialize(expected)
@expected = expected
end
def matches?(actual)
@actual = actual
@actual.eql?(@expected)
end
def failure_message
return "expected #{@expected.inspect}, got #{@actual.inspect} (using .eql?)", @expected, @actual
end
def negative_failure_message
return "expected #{@actual.inspect} not to equal #{@expected.inspect} (using .eql?)", @expected, @actual
end
def description
"eql #{@expected.inspect}"
end
end
# :call-seq:
# should eql(expected)
# should_not eql(expected)
#
# Passes if actual and expected are of equal value, but not necessarily the same object.
#
# See http://www.ruby-doc.org/core/classes/Object.html#M001057 for more information about equality in Ruby.
#
# == Examples
#
# 5.should eql(5)
# 5.should_not eql(3)
def eql(expected)
Matchers::Eql.new(expected)
end
end
end

View file

@ -0,0 +1,43 @@
module Spec
module Matchers
class Equal #:nodoc:
def initialize(expected)
@expected = expected
end
def matches?(actual)
@actual = actual
@actual.equal?(@expected)
end
def failure_message
return "expected #{@expected.inspect}, got #{@actual.inspect} (using .equal?)", @expected, @actual
end
def negative_failure_message
return "expected #{@actual.inspect} not to equal #{@expected.inspect} (using .equal?)", @expected, @actual
end
def description
"equal #{@expected.inspect}"
end
end
# :call-seq:
# should equal(expected)
# should_not equal(expected)
#
# Passes if actual and expected are the same object (object identity).
#
# See http://www.ruby-doc.org/core/classes/Object.html#M001057 for more information about equality in Ruby.
#
# == Examples
#
# 5.should equal(5) #Fixnums are equal
# "5".should_not equal("5") #Strings that look the same are not the same object
def equal(expected)
Matchers::Equal.new(expected)
end
end
end

View file

@ -0,0 +1,17 @@
module Spec
module Matchers
class Exist
def matches? actual
@actual = actual
@actual.exist?
end
def failure_message
"expected #{@actual.inspect} to exist, but it doesn't."
end
def negative_failure_message
"expected #{@actual.inspect} to not exist, but it does."
end
end
def exist; Exist.new; end
end
end

View file

@ -0,0 +1,34 @@
module Spec
module Matchers
class Has #:nodoc:
def initialize(sym, *args)
@sym = sym
@args = args
end
def matches?(target)
target.send(predicate, *@args)
end
def failure_message
"expected ##{predicate}(#{@args[0].inspect}) to return true, got false"
end
def negative_failure_message
"expected ##{predicate}(#{@args[0].inspect}) to return false, got true"
end
def description
"have key #{@args[0].inspect}"
end
private
def predicate
"#{@sym.to_s.sub("have_","has_")}?".to_sym
end
end
end
end

View file

@ -0,0 +1,149 @@
module Spec
module Matchers
class Have #:nodoc:
def initialize(expected, relativity=:exactly)
@expected = (expected == :no ? 0 : expected)
@relativity = relativity
end
def relativities
@relativities ||= {
:exactly => "",
:at_least => "at least ",
:at_most => "at most "
}
end
def method_missing(sym, *args, &block)
@collection_name = sym
if defined?(ActiveSupport::Inflector)
@plural_collection_name = ActiveSupport::Inflector.pluralize(sym.to_s)
elsif Object.const_defined?(:Inflector)
@plural_collection_name = Inflector.pluralize(sym.to_s)
end
@args = args
@block = block
self
end
def matches?(collection_owner)
if collection_owner.respond_to?(@collection_name)
collection = collection_owner.send(@collection_name, *@args, &@block)
elsif (@plural_collection_name && collection_owner.respond_to?(@plural_collection_name))
collection = collection_owner.send(@plural_collection_name, *@args, &@block)
elsif (collection_owner.respond_to?(:length) || collection_owner.respond_to?(:size))
collection = collection_owner
else
collection_owner.send(@collection_name, *@args, &@block)
end
@actual = collection.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}"
end
def negative_failure_message
if @relativity == :exactly
return "expected target not to have #{@expected} #{@collection_name}, got #{@actual}"
elsif @relativity == :at_most
return <<-EOF
Isn't life confusing enough?
Instead of having to figure out the meaning of this:
should_not have_at_most(#{@expected}).#{@collection_name}
We recommend that you use this instead:
should have_at_least(#{@expected + 1}).#{@collection_name}
EOF
elsif @relativity == :at_least
return <<-EOF
Isn't life confusing enough?
Instead of having to figure out the meaning of this:
should_not have_at_least(#{@expected}).#{@collection_name}
We recommend that you use this instead:
should have_at_most(#{@expected - 1}).#{@collection_name}
EOF
end
end
def description
"have #{relative_expectation} #{@collection_name}"
end
private
def relative_expectation
"#{relativities[@relativity]}#{@expected}"
end
end
# :call-seq:
# should have(number).named_collection__or__sugar
# should_not have(number).named_collection__or__sugar
#
# Passes if receiver is a collection with the submitted
# number of items OR if the receiver OWNS a collection
# with the submitted number of items.
#
# If the receiver OWNS the collection, you must use the name
# of the collection. So if a <tt>Team</tt> instance has a
# collection named <tt>#players</tt>, you must use that name
# to set the expectation.
#
# If the receiver IS the collection, you can use any name
# you like for <tt>named_collection</tt>. We'd recommend using
# either "elements", "members", or "items" as these are all
# standard ways of describing the things IN a collection.
#
# This also works for Strings, letting you set an expectation
# about its length
#
# == Examples
#
# # Passes if team.players.size == 11
# team.should have(11).players
#
# # Passes if [1,2,3].length == 3
# [1,2,3].should have(3).items #"items" is pure sugar
#
# # Passes if "this string".length == 11
# "this string".should have(11).characters #"characters" is pure sugar
def have(n)
Matchers::Have.new(n)
end
alias :have_exactly :have
# :call-seq:
# should have_at_least(number).items
#
# Exactly like have() with >=.
#
# == Warning
#
# +should_not+ +have_at_least+ is not supported
def have_at_least(n)
Matchers::Have.new(n, :at_least)
end
# :call-seq:
# should have_at_most(number).items
#
# Exactly like have() with <=.
#
# == Warning
#
# +should_not+ +have_at_most+ is not supported
def have_at_most(n)
Matchers::Have.new(n, :at_most)
end
end
end

View file

@ -0,0 +1,70 @@
module Spec
module Matchers
class Include #:nodoc:
def initialize(*expecteds)
@expecteds = expecteds
end
def matches?(actual)
@actual = actual
@expecteds.each do |expected|
return false unless actual.include?(expected)
end
true
end
def failure_message
_message
end
def negative_failure_message
_message("not ")
end
def description
"include #{_pretty_print(@expecteds)}"
end
private
def _message(maybe_not="")
"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
# :call-seq:
# should include(expected)
# should_not include(expected)
#
# Passes if actual includes expected. This works for
# 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)
end
end
end

View file

@ -0,0 +1,41 @@
module Spec
module Matchers
class Match #:nodoc:
def initialize(expected)
@expected = expected
end
def matches?(actual)
@actual = actual
return true if actual =~ @expected
return false
end
def failure_message
return "expected #{@actual.inspect} to match #{@expected.inspect}", @expected, @actual
end
def negative_failure_message
return "expected #{@actual.inspect} not to match #{@expected.inspect}", @expected, @actual
end
def description
"match #{@expected.inspect}"
end
end
# :call-seq:
# should match(regexp)
# should_not match(regexp)
#
# Given a Regexp, passes if actual =~ regexp
#
# == Examples
#
# email.should match(/^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i)
def match(regexp)
Matchers::Match.new(regexp)
end
end
end

View file

@ -0,0 +1,73 @@
module Spec
module Matchers
class BaseOperatorMatcher
attr_reader :generated_description
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

View file

@ -0,0 +1,132 @@
module Spec
module Matchers
class RaiseError #:nodoc:
def initialize(error_or_message=Exception, message=nil, &block)
@block = block
case error_or_message
when String, Regexp
@expected_error, @expected_message = Exception, error_or_message
else
@expected_error, @expected_message = error_or_message, message
end
end
def matches?(proc)
@raised_expected_error = false
@with_expected_message = false
@eval_block = false
@eval_block_passed = false
begin
proc.call
rescue @expected_error => @actual_error
@raised_expected_error = true
@with_expected_message = verify_message
rescue Exception => @actual_error
# This clause should be empty, but rcov will not report it as covered
# unless something (anything) is executed within the clause
rcov_error_report = "http://eigenclass.org/hiki.rb?rcov-0.8.0"
end
unless negative_expectation?
eval_block if @raised_expected_error && @with_expected_message && @block
end
ensure
return (@raised_expected_error && @with_expected_message) ? (@eval_block ? @eval_block_passed : true) : false
end
def eval_block
@eval_block = true
begin
@block[@actual_error]
@eval_block_passed = true
rescue Exception => err
@actual_error = err
end
end
def verify_message
case @expected_message
when nil
return true
when Regexp
return @expected_message =~ @actual_error.message
else
return @expected_message == @actual_error.message
end
end
def failure_message
if @eval_block
return @actual_error.message
else
return "expected #{expected_error}#{actual_error}"
end
end
def negative_failure_message
"expected no #{expected_error}#{actual_error}"
end
def description
"raise #{expected_error}"
end
private
def expected_error
case @expected_message
when nil
@expected_error
when Regexp
"#{@expected_error} with message matching #{@expected_message.inspect}"
else
"#{@expected_error} with #{@expected_message.inspect}"
end
end
def actual_error
@actual_error.nil? ? " but nothing was raised" : ", got #{@actual_error.inspect}"
end
def negative_expectation?
# YES - I'm a bad person... help me find a better way - ryand
caller.first(3).find { |s| s =~ /should_not/ }
end
end
# :call-seq:
# should raise_error()
# should raise_error(NamedError)
# should raise_error(NamedError, String)
# should raise_error(NamedError, Regexp)
# should raise_error() { |error| ... }
# should raise_error(NamedError) { |error| ... }
# should raise_error(NamedError, String) { |error| ... }
# should raise_error(NamedError, Regexp) { |error| ... }
# should_not raise_error()
# should_not raise_error(NamedError)
# should_not raise_error(NamedError, String)
# should_not raise_error(NamedError, Regexp)
#
# With no args, matches if any error is raised.
# With a named error, matches only if that specific error is raised.
# With a named error and messsage specified as a String, matches only if both match.
# With a named error and messsage specified as a Regexp, matches only if both match.
# Pass an optional block to perform extra verifications on the exception matched
#
# == Examples
#
# lambda { do_something_risky }.should raise_error
# lambda { do_something_risky }.should raise_error(PoorRiskDecisionError)
# lambda { do_something_risky }.should raise_error(PoorRiskDecisionError) { |error| error.data.should == 42 }
# lambda { do_something_risky }.should raise_error(PoorRiskDecisionError, "that was too risky")
# lambda { do_something_risky }.should raise_error(PoorRiskDecisionError, /oo ri/)
#
# lambda { do_something_risky }.should_not raise_error
# lambda { do_something_risky }.should_not raise_error(PoorRiskDecisionError)
# lambda { do_something_risky }.should_not raise_error(PoorRiskDecisionError, "that was too risky")
# lambda { do_something_risky }.should_not raise_error(PoorRiskDecisionError, /oo ri/)
def raise_error(error=Exception, message=nil, &block)
Matchers::RaiseError.new(error, message, &block)
end
end
end

View file

@ -0,0 +1,45 @@
module Spec
module Matchers
class RespondTo #:nodoc:
def initialize(*names)
@names = names
@names_not_responded_to = []
end
def matches?(target)
@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 #{@names_not_responded_to.collect {|name| name.inspect }.join(', ')}"
end
def negative_failure_message
"expected target not to respond to #{@names.collect {|name| name.inspect }.join(', ')}"
end
def description
"respond to ##{@names.to_s}"
end
end
# :call-seq:
# should respond_to(*names)
# should_not respond_to(*names)
#
# 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

View file

@ -0,0 +1,47 @@
module Spec
module Matchers
class Satisfy #:nodoc:
def initialize(&block)
@block = block
end
def matches?(actual, &block)
@block = block if block
@actual = actual
@block.call(actual)
end
def failure_message
"expected #{@actual} to satisfy block"
end
def negative_failure_message
"expected #{@actual} not to satisfy block"
end
end
# :call-seq:
# should satisfy {}
# should_not satisfy {}
#
# Passes if the submitted block returns true. Yields target to the
# block.
#
# Generally speaking, this should be thought of as a last resort when
# you can't find any other way to specify the behaviour you wish to
# specify.
#
# If you do find yourself in such a situation, you could always write
# a custom matcher, which would likely make your specs more expressive.
#
# == Examples
#
# 5.should satisfy { |n|
# n > 3
# }
def satisfy(&block)
Matchers::Satisfy.new(&block)
end
end
end

View file

@ -0,0 +1,29 @@
module Spec
module Matchers
class SimpleMatcher
attr_reader :description
def initialize(description, &match_block)
@description = description
@match_block = match_block
end
def matches?(actual)
@actual = actual
return @match_block.call(@actual)
end
def failure_message()
return %[expected #{@description.inspect} but got #{@actual.inspect}]
end
def negative_failure_message()
return %[expected not to get #{@description.inspect}, but got #{@actual.inspect}]
end
end
def simple_matcher(message, &match_block)
SimpleMatcher.new(message, &match_block)
end
end
end

View file

@ -0,0 +1,74 @@
module Spec
module Matchers
class ThrowSymbol #:nodoc:
def initialize(expected=nil)
@expected = expected
@actual = nil
end
def matches?(proc)
begin
proc.call
rescue NameError => e
raise e unless e.message =~ /uncaught throw/
@actual = e.name.to_sym
ensure
if @expected.nil?
return @actual.nil? ? false : true
else
return @actual == @expected
end
end
end
def failure_message
if @actual
"expected #{expected}, got #{@actual.inspect}"
else
"expected #{expected} but nothing was thrown"
end
end
def negative_failure_message
if @expected
"expected #{expected} not to be thrown"
else
"expected no Symbol, got :#{@actual}"
end
end
def description
"throw #{expected}"
end
private
def expected
@expected.nil? ? "a Symbol" : @expected.inspect
end
end
# :call-seq:
# should throw_symbol()
# should throw_symbol(:sym)
# should_not throw_symbol()
# should_not throw_symbol(:sym)
#
# Given a Symbol argument, matches if a proc throws the specified Symbol.
#
# Given no argument, matches if a proc throws any Symbol.
#
# == Examples
#
# lambda { do_something_risky }.should throw_symbol
# lambda { do_something_risky }.should throw_symbol(:that_was_risky)
#
# lambda { do_something_risky }.should_not throw_symbol
# lambda { do_something_risky }.should_not throw_symbol(:that_was_risky)
def throw_symbol(sym=nil)
Matchers::ThrowSymbol.new(sym)
end
end
end

211
vendor/plugins/rspec/lib/spec/mocks.rb vendored Normal file
View file

@ -0,0 +1,211 @@
require 'spec/mocks/framework'
require 'spec/mocks/methods'
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'
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
#
# RSpec will create Mock Objects and Stubs for you at runtime, or attach stub/mock behaviour
# to any of your real objects (Partial Mock/Stub). Because the underlying implementation
# for mocks and stubs is the same, you can intermingle mock and stub
# behaviour in either dynamically generated mocks or your pre-existing classes.
# There is a semantic difference in how they are created, however,
# which can help clarify the role it is playing within a given spec.
#
# == Mock Objects
#
# Mocks are objects that allow you to set and verify expectations that they will
# receive specific messages during run time. They are very useful for specifying how the subject of
# the spec interacts with its collaborators. This approach is widely known as "interaction
# testing".
#
# Mocks are also very powerful as a design tool. As you are
# driving the implementation of a given class, Mocks provide an anonymous
# collaborator that can change in behaviour as quickly as you can write an expectation in your
# spec. This flexibility allows you to design the interface of a collaborator that often
# does not yet exist. As the shape of the class being specified becomes more clear, so do the
# requirements for its collaborators - often leading to the discovery of new types that are
# needed in your system.
#
# Read Endo-Testing[http://www.mockobjects.com/files/endotesting.pdf] for a much
# more in depth description of this process.
#
# == Stubs
#
# Stubs are objects that allow you to set "stub" responses to
# messages. As Martin Fowler points out on his site,
# mocks_arent_stubs[http://www.martinfowler.com/articles/mocksArentStubs.html].
# Paraphrasing Fowler's paraphrasing
# of Gerard Meszaros: Stubs provide canned responses to messages they might receive in a test, while
# mocks allow you to specify and, subsquently, verify that certain messages should be received during
# the execution of a test.
#
# == Partial Mocks/Stubs
#
# RSpec also supports partial mocking/stubbing, allowing you to add stub/mock behaviour
# to instances of your existing classes. This is generally
# something to be avoided, because changes to the class can have ripple effects on
# seemingly unrelated specs. When specs fail due to these ripple effects, the fact
# that some methods are being mocked can make it difficult to understand why a
# failure is occurring.
#
# That said, partials do allow you to expect and
# verify interactions with class methods such as +#find+ and +#create+
# on Ruby on Rails model classes.
#
# == Further Reading
#
# There are many different viewpoints about the meaning of mocks and stubs. If you are interested
# in learning more, here is some recommended reading:
#
# * Mock Objects: http://www.mockobjects.com/
# * Endo-Testing: http://www.mockobjects.com/files/endotesting.pdf
# * Mock Roles, Not Objects: http://www.mockobjects.com/files/mockrolesnotobjects.pdf
# * Test Double Patterns: http://xunitpatterns.com/Test%20Double%20Patterns.html
# * Mocks aren't stubs: http://www.martinfowler.com/articles/mocksArentStubs.html
#
# == Creating a Mock
#
# You can create a mock in any specification (or setup) using:
#
# mock(name, options={})
#
# The optional +options+ argument is a +Hash+. Currently the only supported
# option is +:null_object+. Setting this to true instructs the mock to ignore
# any messages it hasnt been told to expect and quietly return itself. For example:
#
# mock("person", :null_object => true)
#
# == Creating a Stub
#
# You can create a stub in any specification (or setup) using:
#
# stub(name, stub_methods_and_values_hash)
#
# For example, if you wanted to create an object that always returns
# "More?!?!?!" to "please_sir_may_i_have_some_more" you would do this:
#
# stub("Mr Sykes", :please_sir_may_i_have_some_more => "More?!?!?!")
#
# == Creating a Partial Mock
#
# You don't really "create" a partial mock, you simply add method stubs and/or
# mock expectations to existing classes and objects:
#
# Factory.should_receive(:find).with(id).and_return(value)
# obj.stub!(:to_i).and_return(3)
# etc ...
#
# == Expecting Messages
#
# my_mock.should_receive(:sym)
# my_mock.should_not_receive(:sym)
#
# == Expecting Arguments
#
# my_mock.should_receive(:sym).with(*args)
# my_mock.should_not_receive(:sym).with(*args)
#
# == Argument Constraints using Expression Matchers
#
# Arguments that are passed to #with are compared with actual arguments received
# using == by default. In cases in which you want to specify things about the arguments
# rather than the arguments themselves, you can use any of the Expression Matchers.
# They don't all make syntactic sense (they were primarily designed for use with
# Spec::Expectations), but you are free to create your own custom Spec::Matchers.
#
# Spec::Mocks does provide one additional Matcher method named #ducktype.
#
# In addition, Spec::Mocks adds some keyword Symbols that you can use to
# specify certain kinds of arguments:
#
# my_mock.should_receive(:sym).with(no_args())
# my_mock.should_receive(:sym).with(any_args())
# my_mock.should_receive(:sym).with(1, 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, ducktype(:abs, :div), "b")
# #2nd argument can be object that responds to #abs and #div
#
# == Receive Counts
#
# my_mock.should_receive(:sym).once
# my_mock.should_receive(:sym).twice
# my_mock.should_receive(:sym).exactly(n).times
# my_mock.should_receive(:sym).at_least(:once)
# my_mock.should_receive(:sym).at_least(:twice)
# my_mock.should_receive(:sym).at_least(n).times
# my_mock.should_receive(:sym).at_most(:once)
# my_mock.should_receive(:sym).at_most(:twice)
# my_mock.should_receive(:sym).at_most(n).times
# my_mock.should_receive(:sym).any_number_of_times
#
# == Ordering
#
# my_mock.should_receive(:sym).ordered
# my_mock.should_receive(:other_sym).ordered
# #This will fail if the messages are received out of order
#
# == Setting Reponses
#
# Whether you are setting a mock expectation or a simple stub, you can tell the
# object precisely how to respond:
#
# my_mock.should_receive(:sym).and_return(value)
# my_mock.should_receive(:sym).exactly(3).times.and_return(value1, value2, value3)
# # returns value1 the first time, value2 the second, etc
# my_mock.should_receive(:sym).and_return { ... } #returns value returned by the block
# my_mock.should_receive(:sym).and_raise(error)
# #error can be an instantiated object or a class
# #if it is a class, it must be instantiable with no args
# my_mock.should_receive(:sym).and_throw(:sym)
# my_mock.should_receive(:sym).and_yield(values,to,yield)
# my_mock.should_receive(:sym).and_yield(values,to,yield).and_yield(some,other,values,this,time)
# # for methods that yield to a block multiple times
#
# Any of these responses can be applied to a stub as well, but stubs do
# not support any qualifiers about the message received (i.e. you can't specify arguments
# or receive counts):
#
# my_mock.stub!(:sym).and_return(value)
# my_mock.stub!(:sym).and_return(value1, value2, value3)
# my_mock.stub!(:sym).and_raise(error)
# my_mock.stub!(:sym).and_throw(:sym)
# my_mock.stub!(:sym).and_yield(values,to,yield)
# my_mock.stub!(:sym).and_yield(values,to,yield).and_yield(some,other,values,this,time)
#
# == Arbitrary Handling
#
# Once in a while you'll find that the available expectations don't solve the
# particular problem you are trying to solve. Imagine that you expect the message
# to come with an Array argument that has a specific length, but you don't care
# what is in it. You could do this:
#
# my_mock.should_receive(:sym) do |arg|
# arg.should be_an_istance_of(Array)
# arg.length.should == 7
# end
#
# Note that this would fail if the number of arguments received was different from
# the number of block arguments (in this case 1).
#
# == Combining Expectation Details
#
# Combining the message name with specific arguments, receive counts and responses
# you can get quite a bit of detail in your expectations:
#
# my_mock.should_receive(:<<).with("illegal value").once.and_raise(ArgumentError)
module Mocks
end
end

View file

@ -0,0 +1,31 @@
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 hash_including(expected={})
HashIncludingConstraint.new(expected)
end
def no_args
NoArgsConstraint.new
end
end
end
end

View file

@ -0,0 +1,216 @@
module Spec
module Mocks
class MatcherConstraint
def initialize(matcher)
@matcher = matcher
end
def matches?(value)
@matcher.matches?(value)
end
end
class LiteralArgConstraint
def initialize(literal)
@literal_value = literal
end
def matches?(value)
@literal_value == value
end
end
class RegexpArgConstraint
def initialize(regexp)
@regexp = regexp
end
def matches?(value)
return value =~ @regexp unless value.is_a?(Regexp)
value == @regexp
end
end
class AnyArgConstraint
def initialize(ignore)
end
def ==(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
def matches?(value)
value.is_a?(Numeric)
end
end
class BooleanArgConstraint
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)
false
end
end
class StringArgConstraint
def initialize(ignore)
end
def matches?(value)
value.is_a?(String)
end
end
class DuckTypeArgConstraint
def initialize(*methods_to_respond_to)
@methods_to_respond_to = methods_to_respond_to
end
def matches?(value)
@methods_to_respond_to.all? { |sym| value.respond_to?(sym) }
end
def description
"duck_type"
end
end
class HashIncludingConstraint
def initialize(expected)
@expected = expected
end
def ==(actual)
@expected.each do | key, value |
# check key for case that value evaluates to nil
return false unless actual.has_key?(key) && actual[key] == value
end
true
rescue NoMethodError => ex
return false
end
def matches?(value)
self == value
end
def description
"hash_including(#{@expected.inspect.sub(/^\{/,"").sub(/\}$/,"")})"
end
end
class ArgumentExpectation
attr_reader :args
@@constraint_classes = Hash.new { |hash, key| LiteralArgConstraint}
@@constraint_classes[:anything] = AnyArgConstraint
@@constraint_classes[:numeric] = NumericArgConstraint
@@constraint_classes[:boolean] = BooleanArgConstraint
@@constraint_classes[:string] = StringArgConstraint
def initialize(args, &block)
@args = args
@constraints_block = block
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
def process_arg_constraints(constraints)
constraints.collect do |constraint|
convert_constraint(constraint)
end
end
def warn_deprecated(deprecated_method, instead)
Kernel.warn "The #{deprecated_method} constraint is deprecated. Use #{instead} instead."
end
def convert_constraint(constraint)
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)
end
def is_matcher?(obj)
return obj.respond_to?(:matches?) && obj.respond_to?(:description)
end
def check_args(args)
if @constraints_block
@constraints_block.call(*args)
return true
end
return true if @expected_params.nil?
return true if @expected_params == args
return constraints_match?(args)
end
def constraints_match?(args)
return false if args.length != @expected_params.length
@expected_params.each_index { |i| return false unless @expected_params[i].matches?(args[i]) }
return true
end
end
end
end

View file

@ -0,0 +1,84 @@
module Spec
module Mocks
class ErrorGenerator
attr_writer :opts
def initialize(target, name)
@target = target
@name = name
end
def opts
@opts ||= {}
end
def raise_unexpected_message_error(sym, *args)
__raise "#{intro} received unexpected message :#{sym}#{arg_message(*args)}"
end
def raise_unexpected_message_args_error(expectation, *args)
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
def raise_expectation_error(sym, expected_received_count, actual_received_count, *args)
__raise "#{intro} expected :#{sym}#{arg_message(*args)} #{count_message(expected_received_count)}, but received it #{count_message(actual_received_count)}"
end
def raise_out_of_order_error(sym)
__raise "#{intro} received :#{sym} out of order"
end
def raise_block_failed_error(sym, detail)
__raise "#{intro} received :#{sym} but passed block failed with: #{detail}"
end
def raise_missing_block_error(args_to_yield)
__raise "#{intro} asked to yield |#{arg_list(*args_to_yield)}| but no block was passed"
end
def raise_wrong_arity_error(args_to_yield, arity)
__raise "#{intro} yielded |#{arg_list(*args_to_yield)}| to block with arity of #{arity}"
end
private
def intro
@name ? "Mock '#{@name}'" : @target.inspect
end
def __raise(message)
message = opts[:message] unless opts[:message].nil?
Kernel::raise(Spec::Mocks::MockExpectationError, message)
end
def arg_message(*args)
" with " + format_args(*args)
end
def format_args(*args)
return "(no args)" if args.empty? || args == [:no_args]
return "(any args)" if args == [:any_args]
"(" + arg_list(*args) + ")"
end
def arg_list(*args)
args.collect do |arg|
arg.respond_to?(:description) ? arg.description : arg.inspect
end.join(", ")
end
def count_message(count)
return "at least #{pretty_print(count.abs)}" if count < 0
return pretty_print(count)
end
def pretty_print(count)
return "once" if count == 1
return "twice" if count == 2
return "#{count} times"
end
end
end
end

View file

@ -0,0 +1,10 @@
module Spec
module Mocks
class MockExpectationError < StandardError
end
class AmbiguousReturnError < StandardError
end
end
end

View file

@ -0,0 +1 @@
require 'spec/mocks/extensions/object'

View file

@ -0,0 +1,3 @@
class Object
include Spec::Mocks::Methods
end

View file

@ -0,0 +1,15 @@
# Require everything except the global extensions of class and object. This
# supports wrapping rspec's mocking functionality without invading every
# object in the system.
require 'spec/mocks/methods'
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'
require 'spec/mocks/order_group'
require 'spec/mocks/errors'
require 'spec/mocks/error_generator'
require 'spec/mocks/space'

View file

@ -0,0 +1,290 @@
module Spec
module Mocks
class BaseExpectation
attr_reader :sym
def initialize(error_generator, expectation_ordering, expected_from, sym, method_block, expected_received_count=1, opts={})
@error_generator = error_generator
@error_generator.opts = opts
@expected_from = expected_from
@sym = sym
@method_block = method_block
@return_block = nil
@actual_received_count = 0
@expected_received_count = expected_received_count
@args_expectation = ArgumentExpectation.new([AnyArgsConstraint.new])
@consecutive = false
@exception_to_raise = nil
@symbol_to_throw = nil
@order_group = expectation_ordering
@at_least = nil
@at_most = nil
@args_to_yield = []
end
def expected_args
@args_expectation.args
end
def and_return(*values, &return_block)
Kernel::raise AmbiguousReturnError unless @method_block.nil?
case values.size
when 0 then value = nil
when 1 then value = values[0]
else
value = values
@consecutive = true
@expected_received_count = values.size if !ignoring_args? &&
@expected_received_count < values.size
end
@return_block = block_given? ? return_block : lambda { value }
# Ruby 1.9 - see where this is used below
@ignore_args = !block_given?
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
def and_throw(symbol)
@symbol_to_throw = symbol
end
def and_yield(*args)
@args_to_yield << args
self
end
def matches(sym, args)
@sym == sym and @args_expectation.check_args(args)
end
def invoke(args, block)
if @expected_received_count == 0
@actual_received_count += 1
@error_generator.raise_expectation_error @sym, @expected_received_count, @actual_received_count, *args
end
@order_group.handle_order_constraint self
begin
Kernel::raise @exception_to_raise unless @exception_to_raise.nil?
Kernel::throw @symbol_to_throw unless @symbol_to_throw.nil?
if !@method_block.nil?
default_return_val = invoke_method_block(args)
elsif @args_to_yield.size > 0
default_return_val = invoke_with_yield(block)
else
default_return_val = nil
end
if @consecutive
return invoke_consecutive_return_block(args, block)
elsif @return_block
return invoke_return_block(args, block)
else
return default_return_val
end
ensure
@actual_received_count += 1
end
end
protected
def invoke_method_block(args)
begin
@method_block.call(*args)
rescue => detail
@error_generator.raise_block_failed_error @sym, detail.message
end
end
def invoke_with_yield(block)
if block.nil?
@error_generator.raise_missing_block_error @args_to_yield
end
value = nil
@args_to_yield.each do |args_to_yield_this_time|
if block.arity > -1 && args_to_yield_this_time.length != block.arity
@error_generator.raise_wrong_arity_error args_to_yield_this_time, block.arity
end
value = block.call(*args_to_yield_this_time)
end
value
end
def invoke_consecutive_return_block(args, block)
args << block unless block.nil?
value = @return_block.call(*args)
index = [@actual_received_count, value.size-1].min
value[index]
end
def invoke_return_block(args, block)
args << block unless block.nil?
# Ruby 1.9 - when we set @return_block to return values
# regardless of arguments, any arguments will result in
# a "wrong number of arguments" error
if @ignore_args
@return_block.call()
else
@return_block.call(*args)
end
end
end
class MessageExpectation < BaseExpectation
def matches_name_but_not_args(sym, args)
@sym == sym and not @args_expectation.check_args(args)
end
def verify_messages_received
return if expected_messages_received?
generate_error
rescue Spec::Mocks::MockExpectationError => error
error.backtrace.insert(0, @expected_from)
Kernel::raise error
end
def expected_messages_received?
ignoring_args? || matches_exact_count? ||
matches_at_least_count? || matches_at_most_count?
end
def ignoring_args?
@expected_received_count == :any
end
def matches_at_least_count?
@at_least && @actual_received_count >= @expected_received_count
end
def matches_at_most_count?
@at_most && @actual_received_count <= @expected_received_count
end
def matches_exact_count?
@expected_received_count == @actual_received_count
end
def similar_messages
@similar_messages ||= []
end
def advise(args, block)
similar_messages << args
end
def generate_error
if similar_messages.empty?
@error_generator.raise_expectation_error(@sym, @expected_received_count, @actual_received_count, *@args_expectation.args)
else
@error_generator.raise_unexpected_message_args_error(self, *@similar_messages.first)
end
end
def with(*args, &block)
@args_expectation = ArgumentExpectation.new(args, &block)
self
end
def exactly(n)
set_expected_received_count :exactly, n
self
end
def at_least(n)
set_expected_received_count :at_least, n
self
end
def at_most(n)
set_expected_received_count :at_most, n
self
end
def times(&block)
@method_block = block if block
self
end
def any_number_of_times(&block)
@method_block = block if block
@expected_received_count = :any
self
end
def never
@expected_received_count = 0
self
end
def once(&block)
@method_block = block if block
@expected_received_count = 1
self
end
def twice(&block)
@method_block = block if block
@expected_received_count = 2
self
end
def ordered(&block)
@method_block = block if block
@order_group.register(self)
@ordered = true
self
end
def negative_expectation_for?(sym)
return false
end
protected
def set_expected_received_count(relativity, n)
@at_least = (relativity == :at_least)
@at_most = (relativity == :at_most)
@expected_received_count = case n
when Numeric
n
when :once
1
when :twice
2
end
end
end
class NegativeMessageExpectation < MessageExpectation
def initialize(message, expectation_ordering, expected_from, sym, method_block)
super(message, expectation_ordering, expected_from, sym, method_block, 0)
end
def negative_expectation_for?(sym)
return @sym == sym
end
end
end
end

View file

@ -0,0 +1,39 @@
module Spec
module Mocks
module Methods
def should_receive(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_proxy.add_negative_message_expectation(caller(1)[0], sym.to_sym, &block)
end
def stub!(sym, opts={})
__mock_proxy.add_stub(caller(1)[0], sym.to_sym, opts)
end
def received_message?(sym, *args, &block) #:nodoc:
__mock_proxy.received_message?(sym.to_sym, *args, &block)
end
def rspec_verify #:nodoc:
__mock_proxy.verify
end
def rspec_reset #:nodoc:
__mock_proxy.reset
end
private
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

View file

@ -0,0 +1,56 @@
module Spec
module Mocks
class Mock
include Methods
# Creates a new mock with a +name+ (that will be used in error messages
# only) == Options:
# * <tt>:null_object</tt> - if true, the mock object acts as a forgiving
# null object allowing any message to be sent to it.
def initialize(name, stubs_and_options={})
@name = name
@options = parse_options(stubs_and_options)
assign_stubs(stubs_and_options)
end
# This allows for comparing the mock to other objects that proxy such as
# ActiveRecords belongs_to proxy objects By making the other object run
# the comparison, we're sure the call gets delegated to the proxy target
# This is an unfortunate side effect from ActiveRecord, but this should
# be safe unless the RHS redefines == in a nonsensical manner
def ==(other)
other == __mock_proxy
end
def method_missing(sym, *args, &block)
__mock_proxy.instance_eval {@messages_received << [sym, args, block]}
begin
return self if __mock_proxy.null_object?
super(sym, *args, &block)
rescue NameError
__mock_proxy.raise_unexpected_message_error sym, *args
end
end
def inspect
"#<#{self.class}:#{sprintf '0x%x', self.object_id} @name=#{@name.inspect}>"
end
def to_s
inspect.gsub('<','[').gsub('>',']')
end
private
def parse_options(options)
options.has_key?(:null_object) ? {:null_object => options.delete(:null_object)} : {}
end
def assign_stubs(stubs)
stubs.each_pair do |message, response|
stub!(message).and_return(response)
end
end
end
end
end

View file

@ -0,0 +1,29 @@
module Spec
module Mocks
class OrderGroup
def initialize error_generator
@error_generator = error_generator
@ordering = Array.new
end
def register(expectation)
@ordering << expectation
end
def ready_for?(expectation)
return @ordering.first == expectation
end
def consume
@ordering.shift
end
def handle_order_constraint expectation
return unless @ordering.include? expectation
return consume if ready_for?(expectation)
@error_generator.raise_out_of_order_error expectation.sym
end
end
end
end

View file

@ -0,0 +1,184 @@
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
@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
@expectations << NegativeMessageExpectation.new(@error_generator, @expectation_ordering, expected_from, sym, block_given? ? block : nil)
@expectations.last
end
def add_stub(expected_from, sym, opts={})
__add sym
@stubs.unshift MessageExpectation.new(@error_generator, @expectation_ordering, expected_from, sym, nil, :any, opts)
@stubs.first
end
def verify #:nodoc:
verify_expectations
ensure
reset
end
def reset
clear_expectations
clear_stubs
reset_proxied_methods
clear_proxied_methods
end
def received_message?(sym, *args, &block)
@messages_received.any? {|array| array == [sym, args, block]}
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, *args))
if expectation = find_almost_matching_expectation(sym, *args)
expectation.advise(args, block) unless expectation.expected_messages_received?
end
stub.invoke([], block)
elsif expectation = find_almost_matching_expectation(sym, *args)
expectation.advise(args, block) if null_object? unless expectation.expected_messages_received?
raise_unexpected_message_args_error(expectation, *args) unless (has_negative_expectation?(sym) or 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)
$rspec_mocks.add(@target) unless $rspec_mocks.nil?
define_expected_method(sym)
end
def define_expected_method(sym)
visibility_string = "#{visibility(sym)} :#{sym}"
if target_responds_to?(sym) && !target_metaclass.method_defined?(munge(sym))
munged_sym = munge(sym)
target_metaclass.instance_eval do
alias_method munged_sym, sym if method_defined?(sym.to_s)
end
@proxied_methods << sym
end
target_metaclass.class_eval(<<-EOF, __FILE__, __LINE__)
def #{sym}(*args, &block)
__mock_proxy.message_received :#{sym}, *args, &block
end
#{visibility_string}
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 visibility(sym)
if Mock === @target
'public'
elsif target_metaclass.private_method_defined?(sym)
'private'
elsif target_metaclass.protected_method_defined?(sym)
'protected'
else
'public'
end
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 target_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|
munged_sym = munge(sym)
target_metaclass.instance_eval do
if method_defined?(munged_sym.to_s)
alias_method sym, munged_sym
undef_method munged_sym
else
undef_method sym
end
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, *args)
@stubs.find {|stub| stub.matches(sym, args)}
end
end
end
end

View file

@ -0,0 +1,28 @@
module Spec
module Mocks
class Space
def add(obj)
mocks << obj unless mocks.detect {|m| m.equal? 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

View file

@ -0,0 +1,38 @@
module Spec
module Mocks
module ExampleMethods
include Spec::Mocks::ArgumentConstraintMatchers
# Shortcut for creating an instance of Spec::Mocks::Mock.
#
# +name+ is used for failure reporting, so you should use the
# role that the mock is playing in the example.
#
# +stubs_and_options+ lets you assign options and stub values
# at the same time. The only option available is :null_object.
# Anything else is treated as a stub value.
#
# == Examples
#
# stub_thing = mock("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 mock(name, stubs_and_options={})
Spec::Mocks::Mock.new(name, stubs_and_options)
end
alias :stub :mock
# Shortcut for creating a mock object that will return itself in response
# to any message it receives that it hasn't been explicitly instructed
# to respond to.
def stub_everything(name = 'stub')
mock(name, :null_object => true)
end
end
end
end

View file

@ -0,0 +1,227 @@
#!/usr/bin/env ruby
# Define a task library for running RSpec contexts.
require 'rake'
require 'rake/tasklib'
module Spec
module Rake
# A Rake task that runs a set of specs.
#
# Example:
#
# Spec::Rake::SpecTask.new do |t|
# t.warning = true
# t.rcov = true
# end
#
# This will create a task that can be run with:
#
# rake spec
#
# 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).
#
# This task can also be used to run existing Test::Unit tests and get RSpec
# output, for example like this:
#
# require 'rubygems'
# require 'spec/rake/spectask'
# Spec::Rake::SpecTask.new do |t|
# t.ruby_opts = ['-rtest/unit']
# t.spec_files = FileList['test/**/*_test.rb']
# end
#
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
# Array of directories to be added to $LOAD_PATH before running the
# specs. Defaults to ['<the absolute path to RSpec's lib directory>']
attr_accessor :libs
# If true, requests that the specs be run with the warning flag set.
# E.g. warning=true implies "ruby -w" used to run the specs. Defaults to false.
attr_accessor :warning
# Glob pattern to match spec files. (default is 'spec/**/*_spec.rb')
# 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
# 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"
# Ignored if rcov=false
attr_accessor :rcov_dir
# Array of commandline options to pass to ruby. Defaults to [].
attr_accessor :ruby_opts
# Whether or not to fail Rake when an error occurs (typically when specs fail).
# Defaults to true.
attr_accessor :fail_on_error
# A message to print to 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. +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.
# Setting the SPEC environment variable overrides this.
attr_accessor :spec_files
# Use verbose output. If this is set to true, the task will print
# the executed spec command to stdout. Defaults to false.
attr_accessor :verbose
# Defines a new task, using the name +name+.
def initialize(name=:spec)
@name = name
@libs = [File.expand_path(File.dirname(__FILE__) + '/../../../lib')]
@pattern = nil
@spec_files = nil
@spec_opts = []
@warning = false
@ruby_opts = []
@fail_on_error = true
@rcov = false
@rcov_opts = ['--exclude', 'lib\/spec,bin\/spec,config\/boot.rb']
@rcov_dir = "coverage"
yield self if block_given?
@pattern = 'spec/**/*_spec.rb' if pattern.nil? && spec_files.nil?
define
end
def define # :nodoc:
spec_script = File.expand_path(File.dirname(__FILE__) + '/../../../bin/spec')
lib_path = libs.join(File::PATH_SEPARATOR)
actual_name = Hash === name ? name.keys.first : name
unless ::Rake.application.last_comment
desc "Run specs" + (rcov ? " using RCov" : "")
end
task name do
RakeFileUtils.verbose(verbose) do
unless spec_file_list.empty?
# ruby [ruby_opts] -Ilib -S rcov [rcov_opts] bin/spec -- examples [spec_opts]
# or
# ruby [ruby_opts] -Ilib bin/spec examples [spec_opts]
cmd_parts = [RUBY]
cmd_parts += ruby_opts
cmd_parts << %[-I"#{lib_path}"]
cmd_parts << "-S rcov" if rcov
cmd_parts << "-w" if warning
cmd_parts << rcov_option_list
cmd_parts << %[-o "#{rcov_dir}"] if rcov
cmd_parts << %["#{spec_script}"]
cmd_parts << "--" if rcov
cmd_parts += spec_file_list.collect { |fn| %["#{fn}"] }
cmd_parts << spec_option_list
if out
cmd_parts << %[> "#{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
cmd = cmd_parts.join(" ")
puts cmd if verbose
unless system(cmd)
STDERR.puts failure_message if failure_message
raise("Command #{cmd} failed") if fail_on_error
end
end
end
end
if rcov
desc "Remove rcov products for #{actual_name}"
task paste("clobber_", actual_name) do
rm_r rcov_dir rescue nil
end
clobber_task = paste("clobber_", actual_name)
task :clobber => [clobber_task]
task actual_name => clobber_task
end
self
end
def rcov_option_list # :nodoc:
return "" unless rcov
ENV['RCOV_OPTS'] || rcov_opts.join(" ") || ""
end
def spec_option_list # :nodoc:
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:
if ENV['SPEC']
FileList[ ENV['SPEC'] ]
else
result = []
result += spec_files.to_a if spec_files
result += FileList[ pattern ].to_a if pattern
FileList[result]
end
end
end
end
end

View file

@ -0,0 +1,52 @@
module RCov
# A task that can verify that the RCov coverage doesn't
# drop below a certain threshold. It should be run after
# running Spec::Rake::SpecTask.
class VerifyTask < Rake::TaskLib
# Name of the task. Defaults to :verify_rcov
attr_accessor :name
# Path to the index.html file generated by RCov, which
# is the file containing the total coverage.
# Defaults to 'coverage/index.html'
attr_accessor :index_html
# Whether or not to output details. Defaults to true.
attr_accessor :verbose
# The threshold value (in percent) for coverage. If the
# actual coverage is not equal to this value, the task will raise an
# exception.
attr_accessor :threshold
# 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
end
def define
desc "Verify that rcov coverage is at least #{threshold}%"
task @name do
total_coverage = nil
File.open(index_html).each_line do |line|
if line =~ /<tt class='coverage_total'>(\d+\.\d+)%<\/tt>/
total_coverage = eval($1)
break
end
end
puts "Coverage: #{total_coverage}% (threshold: #{threshold}%)" if verbose
raise "Coverage must be at least #{threshold}% but was #{total_coverage}%" if total_coverage < threshold
raise "Coverage has increased above the threshold of #{threshold}% to #{total_coverage}%. You should update your threshold value." if (total_coverage > threshold) and require_exact_threshold
end
end
end
end

201
vendor/plugins/rspec/lib/spec/runner.rb vendored Normal file
View file

@ -0,0 +1,201 @@
require 'spec/runner/options'
require 'spec/runner/option_parser'
require 'spec/runner/example_group_runner'
require 'spec/runner/command_line'
require 'spec/runner/drb_command_line'
require 'spec/runner/backtrace_tweaker'
require 'spec/runner/reporter'
require 'spec/runner/spec_parser'
require 'spec/runner/class_and_arguments_parser'
module Spec
# == ExampleGroups and Examples
#
# Rather than expressing examples in classes, RSpec uses a custom DSLL (DSL light) to
# describe groups of examples.
#
# A ExampleGroup 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
#
# describe Account do
#
# before do
# @account = Account.new
# end
#
# it "should have a balance of $0" do
# @account.balance.should == Money.new(0, :dollars)
# end
#
# end
#
# We use the before block to set up the Example (given), and then the #it method to
# hold the example code that expresses the event (when) and the expected outcome (then).
#
# == Helper Methods
#
# A primary goal of RSpec is to keep the examples clear. We therefore prefer
# less indirection than you might see in xUnit examples and in well factored, DRY production code. We feel
# that duplication is OK if removing it makes it harder to understand an example without
# having to look elsewhere to understand its context.
#
# That said, RSpec does support some level of encapsulating common code in helper
# methods that can exist within a context or within an included module.
#
# == Setup and Teardown
#
# You can use before and after within a Example. Both methods take an optional
# scope argument so you can run the block before :each example or before :all examples
#
# describe "..." do
# before :all do
# ...
# end
#
# before :each do
# ...
# end
#
# it "should do something" do
# ...
# end
#
# it "should do something else" do
# ...
# end
#
# after :each do
# ...
# end
#
# after :all do
# ...
# end
#
# end
#
# 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>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:
#
# describe "..." do
#
# it "..." do
# helper_method
# end
#
# def helper_method
# ...
# end
#
# end
#
# == Included helper methods
#
# You can include helper methods in multiple contexts by expressing them within
# a module, and then including that module in your context:
#
# module AccountExampleHelperMethods
# def helper_method
# ...
# end
# end
#
# describe "A new account" do
# include AccountExampleHelperMethods
# before do
# @account = Account.new
# end
#
# it "should have a balance of $0" do
# helper_method
# @account.balance.should eql(Money.new(0, :dollars))
# end
# end
#
# == Shared Example Groups
#
# You can define a shared Example Group, that may be used on other groups
#
# share_examples_for "All Editions" do
# it "all editions behaviour" ...
# end
#
# describe SmallEdition do
# it_should_behave_like "All Editions"
#
# it "should do small edition stuff" do
# ...
# end
# end
#
# You can also assign the shared group to a module and include that
#
# share_as :AllEditions do
# it "should do all editions stuff" ...
# end
#
# describe SmallEdition do
# it_should_behave_like AllEditions
#
# it "should do small edition stuff" do
# ...
# end
# end
#
# And, for those of you who prefer to use something more like Ruby, you
# can just include the module directly
#
# describe SmallEdition do
# include AllEditions
#
# it "should do small edition stuff" do
# ...
# end
# end
module Runner
class << self
def configuration # :nodoc:
@configuration ||= Spec::Example::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::Example::Configuration instance. See its RDoc
# for details about what you can do with it.
#
def configure
yield configuration
end
def register_at_exit_hook # :nodoc:
$spec_runner_at_exit_hook_registered ||= nil
unless $spec_runner_at_exit_hook_registered
at_exit do
unless $! || Spec.run?
success = Spec.run
exit success if Spec.exit?
end
end
$spec_runner_at_exit_hook_registered = true
end
end
end
end
end

View file

@ -0,0 +1,57 @@
module Spec
module Runner
class BacktraceTweaker
def clean_up_double_slashes(line)
line.gsub!('//','/')
end
end
class NoisyBacktraceTweaker < BacktraceTweaker
def tweak_backtrace(error)
return if error.backtrace.nil?
error.backtrace.each do |line|
clean_up_double_slashes(line)
end
end
end
# Tweaks raised Exceptions to mask noisy (unneeded) parts of the backtrace
class QuietBacktraceTweaker < BacktraceTweaker
unless defined?(IGNORE_PATTERNS)
root_dir = File.expand_path(File.join(__FILE__, '..', '..', '..', '..'))
spec_files = Dir["#{root_dir}/lib/*"].map do |path|
subpath = path[root_dir.length..-1]
/#{subpath}/
end
IGNORE_PATTERNS = spec_files + [
/\/lib\/ruby\//,
/bin\/spec:/,
/bin\/rcov:/,
/lib\/rspec-rails/,
/vendor\/rails/,
# TextMate's Ruby and RSpec plugins
/Ruby\.tmbundle\/Support\/tmruby.rb:/,
/RSpec\.tmbundle\/Support\/lib/,
/temp_textmate\./,
/mock_frameworks\/rspec/,
/spec_server/
]
end
def tweak_backtrace(error)
return if error.backtrace.nil?
error.backtrace.collect! do |line|
clean_up_double_slashes(line)
IGNORE_PATTERNS.each do |ignore|
if line =~ ignore
line = nil
break
end
end
line
end
error.backtrace.compact!
end
end
end
end

View file

@ -0,0 +1,16 @@
module Spec
module Runner
class ClassAndArgumentsParser
class << self
def parse(s)
if s =~ /([a-zA-Z_]+(?:::[a-zA-Z_]+)*):?(.*)/
arg = $2 == "" ? nil : $2
[$1, arg]
else
raise "Couldn't parse #{s.inspect}"
end
end
end
end
end
end

View file

@ -0,0 +1,28 @@
require 'spec/runner/option_parser'
module Spec
module Runner
# Facade to run specs without having to fork a new ruby process (using `spec ...`)
class CommandLine
class << self
# Runs specs. +argv+ is the commandline args as per the spec commandline API, +err+
# and +out+ are the streams output will be written to.
def run(instance_rspec_options)
# NOTE - this call to init_rspec_options is not spec'd, but neither is any of this
# swapping of $rspec_options. That is all here to enable rspec to run against itself
# and maintain coverage in a single process. Therefore, DO NOT mess with this stuff
# unless you know what you are doing!
init_rspec_options(instance_rspec_options)
orig_rspec_options = rspec_options
begin
$rspec_options = instance_rspec_options
return $rspec_options.run_examples
ensure
::Spec.run = true
$rspec_options = orig_rspec_options
end
end
end
end
end
end

View file

@ -0,0 +1,20 @@
require "drb/drb"
module Spec
module Runner
# Facade to run specs by connecting to a DRB server
class DrbCommandLine
# Runs specs on a DRB server. Note that this API is similar to that of
# CommandLine - making it possible for clients to use both interchangeably.
def self.run(options)
begin
DRb.start_service
spec_server = DRbObject.new_with_uri("druby://127.0.0.1:8989")
spec_server.run(options.argv, options.error_stream, options.output_stream)
rescue DRb::DRbConnError => e
options.error_stream.puts "No server is running"
end
end
end
end
end

View file

@ -0,0 +1,59 @@
module Spec
module Runner
class ExampleGroupRunner
def initialize(options)
@options = options
end
def load_files(files)
# It's important that loading files (or choosing not to) stays the
# responsibility of the ExampleGroupRunner. Some implementations (like)
# the one using DRb may choose *not* to load files, but instead tell
# someone else to do it over the wire.
files.each do |file|
load file
end
end
def run
prepare
success = true
example_groups.each do |example_group|
success = success & example_group.run
end
return success
ensure
finish
end
protected
def prepare
reporter.start(number_of_examples)
example_groups.reverse! if reverse
end
def finish
reporter.end
reporter.dump
end
def reporter
@options.reporter
end
def reverse
@options.reverse
end
def example_groups
@options.example_groups
end
def number_of_examples
@options.number_of_examples
end
end
# TODO: BT - Deprecate BehaviourRunner?
BehaviourRunner = ExampleGroupRunner
end
end

View file

@ -0,0 +1,77 @@
module Spec
module Runner
module Formatter
# Baseclass for formatters that implements all required methods as no-ops.
class BaseFormatter
attr_accessor :example_group, :options, :where
def initialize(options, where)
@options = options
@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_example_group
def start(example_count)
end
# This method is invoked at the beginning of the execution of each example_group.
# +example_group+ is the example_group.
#
# The next method to be invoked after this is #example_failed or #example_finished
def add_example_group(example_group)
@example_group = example_group
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.
# +message+ is the message from the ExamplePendingError, if it exists, or the
# default value of "Not Yet Implemented"
def example_pending(example, 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

View file

@ -0,0 +1,130 @@
require 'spec/runner/formatter/base_formatter'
module Spec
module Runner
module Formatter
# Baseclass for text-based formatters. Can in fact be used for
# non-text based ones too - just ignore the +output+ constructor
# argument.
class BaseTextFormatter < BaseFormatter
attr_reader :output, :pending_examples
# 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(options, where)
super
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
@pending_examples = []
end
def example_pending(example, message)
@pending_examples << [example.__full_description, message]
end
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?)
red(s)
elsif(failure.pending_fixed?)
blue(s)
else
magenta(s)
end
end
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 = "#{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
if pending_count > 0
@output.puts yellow(summary)
else
@output.puts green(summary)
end
else
@output.puts red(summary)
end
@output.flush
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")
end
protected
def colour?
@options.colour ? true : false
end
def dry_run?
@options.dry_run ? true : false
end
def backtrace_line(line)
line.sub(/\A([^:]+:\d+)$/, '\\1:')
end
def colour(text, colour_code)
return text unless colour? && output_to_tty?
"#{colour_code}#{text}\e[0m"
end
def output_to_tty?
begin
@output == Kernel || @output.tty?
rescue NoMethodError
false
end
end
def 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
end
end

View file

@ -0,0 +1,27 @@
require 'spec/runner/formatter/base_text_formatter'
module Spec
module Runner
module Formatter
class FailingExampleGroupsFormatter < BaseTextFormatter
def example_failed(example, counter, failure)
if @example_group
description_parts = @example_group.description_parts.collect do |description|
description =~ /(.*) \(druby.*\)$/ ? $1 : description
end
@output.puts ::Spec::Example::ExampleGroupMethods.description_text(*description_parts)
@output.flush
@example_group = nil
end
end
def dump_failure(counter, failure)
end
def dump_summary(duration, example_count, failure_count, pending_count)
end
end
end
end
end

View file

@ -0,0 +1,20 @@
require 'spec/runner/formatter/base_text_formatter'
module Spec
module Runner
module Formatter
class FailingExamplesFormatter < BaseTextFormatter
def example_failed(example, counter, failure)
@output.puts "#{example_group.description} #{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

View file

@ -0,0 +1,337 @@
require 'erb'
require 'spec/runner/formatter/base_text_formatter'
module Spec
module Runner
module Formatter
class HtmlFormatter < BaseTextFormatter
include ERB::Util # for the #h method
def initialize(options, output)
super
@example_group_number = 0
@example_number = 0
end
def method_missing(sym, *args)
# no-op
end
# The number of the currently running example_group
def example_group_number
@example_group_number
end
# The number of the currently running example (a global counter)
def example_number
@example_number
end
def start(example_count)
@example_count = example_count
@output.puts html_header
@output.puts report_header
@output.flush
end
def add_example_group(example_group)
super
@example_group_red = false
@example_group_red = false
@example_group_number += 1
unless example_group_number == 1
@output.puts " </dl>"
@output.puts "</div>"
end
@output.puts "<div class=\"example_group\">"
@output.puts " <dl>"
@output.puts " <dt id=\"example_group_#{example_group_number}\">#{h(example_group.description)}</dt>"
@output.flush
end
def start_dump
@output.puts " </dl>"
@output.puts "</div>"
@output.flush
end
def example_started(example)
@example_number += 1
end
def example_passed(example)
move_progress
@output.puts " <dd class=\"spec passed\"><span class=\"passed_spec_name\">#{h(example.description)}</span></dd>"
@output.flush
end
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('example_group_#{example_group_number}');</script>" unless @example_group_red
@example_group_red = true
move_progress
@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>#{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 unless extra == ""
@output.puts " </div>"
@output.puts " </dd>"
@output.flush
end
def example_pending(example, message)
@output.puts " <script type=\"text/javascript\">makeYellow('rspec-header');</script>" unless @header_red
@output.puts " <script type=\"text/javascript\">makeYellow('example_group_#{example_group_number}');</script>" unless @example_group_red
move_progress
@output.puts " <dd class=\"spec not_implemented\"><span class=\"not_implemented_spec_name\">#{h(example.description)} (PENDING: #{h(message)})</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(failure)
require 'spec/runner/formatter/snippet_extractor'
@snippet_extractor ||= SnippetExtractor.new
" <pre class=\"ruby\"><code>#{@snippet_extractor.snippet(failure.exception)}</code></pre>"
end
def move_progress
@output.puts " <script type=\"text/javascript\">moveProgressBar('#{percent_done}');</script>"
@output.flush
end
def percent_done
result = 100.0
if @example_count != 0
result = ((example_number).to_f / @example_count.to_f * 1000).to_i / 10.0
end
result
end
def dump_failure(counter, failure)
end
def dump_summary(duration, example_count, failure_count, pending_count)
if dry_run?
totals = "This was a dry-run"
else
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>"
@output.flush
end
def html_header
<<-EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>RSpec results</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<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
def report_header
<<-EOF
<div class="rspec-report">
<script type="text/javascript">
// <![CDATA[
#{global_scripts}
// ]]>
</script>
<style type="text/css">
#{global_styles}
</style>
<div id="rspec-header">
<h1>RSpec Results</h1>
<div id="summary">
<p id="totals">&nbsp;</p>
<p id="duration">&nbsp;</p>
</div>
</div>
<div class="results">
EOF
end
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';
}
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';
}
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;
}
.rspec-report h1 {
margin: 0px 10px 0px 10px;
padding: 10px;
font-family: "Lucida Grande", Helvetica, sans-serif;
font-size: 1.8em;
}
#summary {
margin: 0; padding: 5px 10px;
font-family: "Lucida Grande", Helvetica, sans-serif;
text-align: right;
position: absolute;
top: 0px;
right: 0px;
}
#summary p {
margin: 0 0 0 2px;
}
#summary #totals {
font-size: 1.2em;
}
.example_group {
margin: 0 10px 5px;
background: #fff;
}
dl {
margin: 0; padding: 0 0 5px;
font: normal 11px "Lucida Grande", Helvetica, sans-serif;
}
dt {
padding: 3px;
background: #65C400;
color: #fff;
font-weight: bold;
}
dd {
margin: 5px 0 5px 5px;
padding: 3px 3px 3px 18px;
}
dd.spec.passed {
border-left: 5px solid #65C400;
border-bottom: 1px solid #65C400;
background: #DBFFB4; color: #3D7700;
}
dd.spec.failed {
border-left: 5px solid #C20000;
border-bottom: 1px solid #C20000;
color: #C20000; background: #FFFBD3;
}
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
end

View file

@ -0,0 +1,65 @@
require 'spec/runner/formatter/base_text_formatter'
module Spec
module Runner
module Formatter
class NestedTextFormatter < BaseTextFormatter
attr_reader :previous_nested_example_groups
def initialize(options, where)
super
@previous_nested_example_groups = []
end
def add_example_group(example_group)
super
current_nested_example_groups = described_example_group_chain
current_nested_example_groups.each_with_index do |nested_example_group, i|
unless nested_example_group == previous_nested_example_groups[i]
output.puts "#{' ' * i}#{nested_example_group.description_args}"
end
end
@previous_nested_example_groups = described_example_group_chain
end
def example_failed(example, counter, failure)
message = if failure.expectation_not_met?
"#{current_indentation}#{example.description} (FAILED - #{counter})"
else
"#{current_indentation}#{example.description} (ERROR - #{counter})"
end
output.puts(failure.expectation_not_met? ? red(message) : magenta(message))
output.flush
end
def example_passed(example)
message = "#{current_indentation}#{example.description}"
output.puts green(message)
output.flush
end
def example_pending(example, message)
super
output.puts yellow("#{current_indentation}#{example.description} (PENDING: #{message})")
output.flush
end
def current_indentation
' ' * previous_nested_example_groups.length
end
def described_example_group_chain
example_group_chain = []
example_group.send(:execute_in_class_hierarchy) do |parent_example_group|
if parent_example_group.description_args && !parent_example_group.description_args.empty?
example_group_chain << parent_example_group
end
end
example_group_chain
end
end
end
end
end

View file

@ -0,0 +1,51 @@
require 'spec/runner/formatter/progress_bar_formatter'
module Spec
module Runner
module Formatter
class ProfileFormatter < ProgressBarFormatter
def initialize(options, where)
super
@example_times = []
end
def start(count)
@output.puts "Profiling enabled."
end
def example_started(example)
@time = Time.now
end
def example_passed(example)
super
@example_times << [
example_group.description,
example.description,
Time.now - @time
]
end
def start_dump
super
@output.puts "\n\nTop 10 slowest examples:\n"
@example_times = @example_times.sort_by do |description, example, time|
time
end.reverse
@example_times[0..9].each do |description, example, time|
@output.print red(sprintf("%.7f", time))
@output.puts " #{description} #{example}"
end
@output.flush
end
def method_missing(sym, *args)
# ignore
end
end
end
end
end

View file

@ -0,0 +1,34 @@
require 'spec/runner/formatter/base_text_formatter'
module Spec
module Runner
module Formatter
class ProgressBarFormatter < BaseTextFormatter
def example_failed(example, counter, failure)
@output.print colourise('F', failure)
@output.flush
end
def example_passed(example)
@output.print green('.')
@output.flush
end
def example_pending(example, message)
super
@output.print yellow('P')
@output.flush
end
def start_dump
@output.puts
@output.flush
end
def method_missing(sym, *args)
# ignore
end
end
end
end
end

View file

@ -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

View file

@ -0,0 +1,39 @@
require 'spec/runner/formatter/base_text_formatter'
module Spec
module Runner
module Formatter
class SpecdocFormatter < BaseTextFormatter
def add_example_group(example_group)
super
output.puts
output.puts example_group.description
output.flush
end
def example_failed(example, counter, failure)
message = if failure.expectation_not_met?
"- #{example.description} (FAILED - #{counter})"
else
"- #{example.description} (ERROR - #{counter})"
end
output.puts(failure.expectation_not_met? ? red(message) : magenta(message))
output.flush
end
def example_passed(example)
message = "- #{example.description}"
output.puts green(message)
output.flush
end
def example_pending(example, message)
super
output.puts yellow("- #{example.description} (PENDING: #{message})")
output.flush
end
end
end
end
end

View file

@ -0,0 +1,161 @@
require 'erb'
require 'spec/runner/formatter/base_text_formatter'
module Spec
module Runner
module Formatter
module Story
class HtmlFormatter < BaseTextFormatter
include ERB::Util
def initialize(options, where)
super
@previous_type = nil
@scenario_text = ""
@story_text = ""
@scenario_failed = false
@story_failed = false
end
def run_started(count)
@output.puts <<-EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>Stories</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="Expires" content="-1" />
<meta http-equiv="Pragma" content="no-cache" />
<script src="javascripts/prototype.js" type="text/javascript"></script>
<script src="javascripts/scriptaculous.js" type="text/javascript"></script>
<script src="javascripts/rspec.js" type="text/javascript"></script>
<link href="stylesheets/rspec.css" rel="stylesheet" type="text/css" />
</head>
<body>
<div id="container">
EOF
end
def collected_steps(steps)
unless steps.empty?
@output.puts " <ul id=\"stock_steps\" style=\"display: none;\">"
steps.each do |step|
@output.puts " <li>#{step}</li>"
end
@output.puts " </ul>"
end
end
def run_ended
@output.puts <<-EOF
</div>
</body>
</head>
EOF
end
def story_started(title, narrative)
@story_failed = false
@story_text = <<-EOF
<dt>Story: #{h title}</dt>
<dd>
<p>
#{h(narrative).split("\n").join("<br />")}
</p>
EOF
end
def story_ended(title, narrative)
if @story_failed
@output.puts <<-EOF
<dl class="story failed">
EOF
else
@output.puts <<-EOF
<dl class="story passed">
EOF
end
@output.puts <<-EOF
#{@story_text}
</dd>
</dl>
EOF
end
def scenario_started(story_title, scenario_name)
@scenario_failed = false
@scenario_text = <<-EOF
<dt>Scenario: #{h scenario_name}</dt>
<dd>
<ul class="steps">
EOF
end
def scenario_ended
if @scenario_failed
@story_text += <<-EOF
<dl class="failed">
EOF
else
@story_text += <<-EOF
<dl class="passed">
EOF
end
@story_text += <<-EOF
#{@scenario_text}
</ul>
</dd>
</dl>
EOF
end
def found_scenario(type, description)
end
def scenario_succeeded(story_title, scenario_name)
scenario_ended
end
def scenario_pending(story_title, scenario_name, reason)
scenario_ended
end
def scenario_failed(story_title, scenario_name, err)
@scenario_failed = true
@story_failed = true
scenario_ended
end
def step_upcoming(type, description, *args)
end
def step_succeeded(type, description, *args)
print_step('passed', type, description, *args) # TODO: uses succeeded CSS class
end
def step_pending(type, description, *args)
print_step('pending', type, description, *args)
end
def step_failed(type, description, *args)
print_step('failed', type, description, *args)
end
def print_step(klass, type, description, *args)
spans = args.map { |arg| "<span class=\"param\">#{arg}</span>" }
desc_string = description.step_name
arg_regexp = description.arg_regexp
i = -1
inner = type.to_s.capitalize + ' ' + desc_string.gsub(arg_regexp) { |param| spans[i+=1] }
@scenario_text += " <li class=\"#{klass}\">#{inner}</li>\n"
end
end
end
end
end
end

View file

@ -0,0 +1,188 @@
require 'spec/runner/formatter/base_text_formatter'
module Spec
module Runner
module Formatter
module Story
class PlainTextFormatter < BaseTextFormatter
def initialize(options, where)
super
@successful_scenario_count = 0
@pending_scenario_count = 0
@pre_story_pending_count = 0
@pre_story_successful_count = 0
@failed_scenarios = []
@pending_steps = []
@previous_type = nil
@scenario_body_text = ""
@story_body_text = ""
@scenario_head_text = ""
@story_head_text = ""
@scenario_failed = false
@story_failed = false
end
def run_started(count)
@count = count
@output.puts "Running #@count scenarios\n\n"
end
def story_started(title, narrative)
@pre_story_pending_count = @pending_scenario_count
@pre_story_successful_count = @successful_scenario_count
@current_story_title = title
@story_failed = false
@story_body_text = ""
@story_head_text = "Story: #{title}\n\n"
narrative.each_line do |line|
@story_head_text += " "
@story_head_text += line
end
end
def story_ended(title, narrative)
if @story_failed
@output.print red(@story_head_text)
elsif @pre_story_successful_count == @successful_scenario_count &&
@pending_scenario_count >= @pre_story_pending_count
@output.print yellow(@story_head_text)
else
@output.print green(@story_head_text)
end
@output.print @story_body_text
@output.puts
@output.puts
end
def scenario_started(story_title, scenario_name)
@current_scenario_name = scenario_name
@scenario_already_failed = false
@scenario_head_text = "\n\n Scenario: #{scenario_name}"
@scenario_body_text = ""
@scenario_ok = true
@scenario_pending = false
@scenario_failed = false
end
def scenario_succeeded(story_title, scenario_name)
@successful_scenario_count += 1
scenario_ended
end
def scenario_failed(story_title, scenario_name, err)
@options.backtrace_tweaker.tweak_backtrace(err)
@failed_scenarios << [story_title, scenario_name, err] unless @scenario_already_failed
@scenario_already_failed = true
@story_failed = true
@scenario_failed = true
scenario_ended
end
def scenario_pending(story_title, scenario_name, msg)
@pending_scenario_count += 1 unless @scenario_already_failed
@scenario_pending = true
@scenario_already_failed = true
scenario_ended
end
def scenario_ended
if @scenario_failed
@story_body_text += red(@scenario_head_text)
elsif @scenario_pending
@story_body_text += yellow(@scenario_head_text)
else
@story_body_text += green(@scenario_head_text)
end
@story_body_text += @scenario_body_text
end
def run_ended
@output.puts "#@count scenarios: #@successful_scenario_count succeeded, #{@failed_scenarios.size} failed, #@pending_scenario_count pending"
unless @pending_steps.empty?
@output.puts "\nPending Steps:"
@pending_steps.each_with_index do |pending, i|
story_name, scenario_name, msg = pending
@output.puts "#{i+1}) #{story_name} (#{scenario_name}): #{msg}"
end
end
unless @failed_scenarios.empty?
@output.print "\nFAILURES:"
@failed_scenarios.each_with_index do |failure, i|
title, scenario_name, err = failure
@output.print %[
#{i+1}) #{title} (#{scenario_name}) FAILED
#{err.class}: #{err.message}
#{err.backtrace.join("\n")}
]
end
end
end
def step_upcoming(type, description, *args)
end
def step_succeeded(type, description, *args)
found_step(type, description, false, false, *args)
end
def step_pending(type, description, *args)
found_step(type, description, false, true, *args)
@pending_steps << [@current_story_title, @current_scenario_name, description]
@scenario_body_text += yellow(" (PENDING)")
@scenario_pending = true
@scenario_ok = false
end
def step_failed(type, description, *args)
found_step(type, description, true, @scenario_pending, *args)
if @scenario_pending
@scenario_body_text += yellow(" (SKIPPED)")
else
@scenario_body_text += red(@scenario_ok ? " (FAILED)" : " (SKIPPED)")
end
@scenario_ok = false
end
def collected_steps(steps)
end
def method_missing(sym, *args, &block) #:nodoc:
# noop - ignore unknown messages
end
private
def found_step(type, description, failed, pending, *args)
desc_string = description.step_name
arg_regexp = description.arg_regexp
text = if(type == @previous_type)
"\n And "
else
"\n\n #{type.to_s.capitalize} "
end
i = -1
text << desc_string.gsub(arg_regexp) { |param| args[i+=1] }
if pending
@scenario_body_text += yellow(text)
else
@scenario_body_text += (failed ? red(text) : green(text))
end
if type == :'given scenario'
@previous_type = :given
else
@previous_type = type
end
end
end
end
end
end
end

View file

@ -0,0 +1,16 @@
require 'spec/runner/formatter/html_formatter'
module Spec
module Runner
module Formatter
# Formats backtraces so they're clickable by TextMate
class TextMateFormatter < HtmlFormatter
def backtrace_line(line)
line.gsub(/([^:]*\.rb):(\d*)/) do
"<a href=\"txmt://open?url=file://#{File.expand_path($1)}&line=#{$2}\">#{$1}:#{$2}</a> "
end
end
end
end
end
end

View file

@ -0,0 +1,72 @@
begin
require 'rubygems'
require 'heckle'
rescue LoadError ; raise "You must gem install heckle to use --heckle" ; end
module Spec
module Runner
# Creates a new Heckler configured to heckle all methods in the classes
# whose name matches +filter+
class HeckleRunner
def initialize(filter, heckle_class=Heckler)
@filter = filter
@heckle_class = heckle_class
end
# Runs all the example groups held by +rspec_options+ once for each of the
# methods in the matched classes.
def heckle_with
if @filter =~ /(.*)[#\.](.*)/
heckle_method($1, $2)
else
heckle_class_or_module(@filter)
end
end
def heckle_method(class_name, method_name)
verify_constant(class_name)
heckle = @heckle_class.new(class_name, method_name, rspec_options)
heckle.validate
end
def heckle_class_or_module(class_or_module_name)
verify_constant(class_or_module_name)
pattern = /^#{class_or_module_name}/
classes = []
ObjectSpace.each_object(Class) do |klass|
classes << klass if klass.name =~ pattern
end
classes.each do |klass|
klass.instance_methods(false).each do |method_name|
heckle = @heckle_class.new(klass.name, method_name, rspec_options)
heckle.validate
end
end
end
def verify_constant(name)
begin
# This is defined in Heckle
name.to_class
rescue
raise "Heckling failed - \"#{name}\" is not a known class or module"
end
end
end
#Supports Heckle 1.2 and prior (earlier versions used Heckle::Base)
class Heckler < (Heckle.const_defined?(:Base) ? Heckle::Base : Heckle)
def initialize(klass_name, method_name, rspec_options)
super(klass_name, method_name)
@rspec_options = rspec_options
end
def tests_pass?
success = @rspec_options.run_examples
success
end
end
end
end

View file

@ -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

View file

@ -0,0 +1,203 @@
require 'optparse'
require 'stringio'
module Spec
module Runner
class OptionParser < ::OptionParser
class << self
def parse(args, err, out)
parser = new(err, out)
parser.parse(args)
parser.options
end
end
attr_reader :options
OPTIONS = {
:pattern => ["-p", "--pattern [PATTERN]","Limit files loaded to those matching this pattern. Defaults to '**/*_spec.rb'",
"Separate multiple patterns with commas.",
"Applies only to directories named on the command line (files",
"named explicitly on the command line will be loaded regardless)."],
: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 for examples: ",
"progress|p : Text progress",
"profile|o : Text progress with profiling of 10 slowest examples",
"specdoc|s : Example doc as text",
"indented|i : Example doc as indented text",
"html|h : A nice HTML report",
"failing_examples|e : Write all failing examples - input for --example",
"failing_example_groups|g : Write all failing example groups - input for --example",
" ",
"Builtin formats for stories: ",
"plain|p : Plain Text",
"html|h : A nice HTML report",
" ",
"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 Runner."],
: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(err, out)
super()
@error_stream = err
@out_stream = out
@options = Options.new(@error_stream, @out_stream)
@file_factory = File
self.banner = "Usage: spec (FILE|DIRECTORY|GLOB)+ [options]"
self.separator ""
on(*OPTIONS[:pattern]) {|pattern| @options.filename_pattern = pattern}
on(*OPTIONS[:diff]) {|diff| @options.parse_diff(diff)}
on(*OPTIONS[:colour]) {@options.colour = true}
on(*OPTIONS[:example]) {|example| @options.parse_example(example)}
on(*OPTIONS[:specification]) {|example| @options.parse_example(example)}
on(*OPTIONS[:line]) {|line_number| @options.line_number = line_number.to_i}
on(*OPTIONS[:format]) {|format| @options.parse_format(format)}
on(*OPTIONS[:require]) {|requires| invoke_requires(requires)}
on(*OPTIONS[:backtrace]) {@options.backtrace_tweaker = NoisyBacktraceTweaker.new}
on(*OPTIONS[:loadby]) {|loadby| @options.loadby = loadby}
on(*OPTIONS[:reverse]) {@options.reverse = true}
on(*OPTIONS[:timeout]) {|timeout| @options.timeout = timeout.to_f}
on(*OPTIONS[:heckle]) {|heckle| @options.load_heckle_runner(heckle)}
on(*OPTIONS[:dry_run]) {@options.dry_run = true}
on(*OPTIONS[:options_file]) {|options_file| parse_options_file(options_file)}
on(*OPTIONS[:generate_options]) {|options_file|}
on(*OPTIONS[:runner]) {|runner| @options.user_input_for_runner = runner}
on(*OPTIONS[:drb]) {}
on(*OPTIONS[:version]) {parse_version}
on_tail(*OPTIONS[:help]) {parse_help}
end
def order!(argv, &blk)
@argv = argv
@options.argv = @argv.dup
return if parse_generate_options
return if parse_drb
super(@argv) do |file|
@options.files << file
blk.call(file) if blk
end
@options
end
protected
def invoke_requires(requires)
requires.split(",").each do |file|
require file
end
end
def parse_options_file(options_file)
option_file_args = IO.readlines(options_file).map {|l| l.chomp.split " "}.flatten
@argv.push(*option_file_args)
# TODO - this is a brute force solution to http://rspec.lighthouseapp.com/projects/5645/tickets/293.
# Let's look for a cleaner way. Might not be one. But let's look. If not, perhaps
# this can be moved to a different method to indicate the special handling for drb?
parse_drb(@argv)
end
def parse_generate_options
# Remove the --generate-options option and the argument before writing to file
options_file = nil
['-G', '--generate-options'].each do |option|
if index = @argv.index(option)
@argv.delete_at(index)
options_file = @argv.delete_at(index)
end
end
if options_file
write_generated_options(options_file)
return true
else
return false
end
end
def write_generated_options(options_file)
File.open(options_file, 'w') do |io|
io.puts @argv.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}"
@options.examples_should_not_be_run
end
def parse_drb(argv = nil)
argv ||= @options.argv # TODO - see note about about http://rspec.lighthouseapp.com/projects/5645/tickets/293
is_drb = false
is_drb ||= argv.delete(OPTIONS[:drb][0])
is_drb ||= argv.delete(OPTIONS[:drb][1])
return false unless is_drb
@options.examples_should_not_be_run
DrbCommandLine.run(
self.class.parse(argv, @error_stream, @out_stream)
)
true
end
def parse_version
@out_stream.puts ::Spec::VERSION::DESCRIPTION
exit if stdout?
end
def parse_help
@out_stream.puts self
exit if stdout?
end
def stdout?
@out_stream == $stdout
end
end
end
end

View file

@ -0,0 +1,309 @@
module Spec
module Runner
class Options
FILE_SORTERS = {
'mtime' => lambda {|file_a, file_b| File.mtime(file_b) <=> File.mtime(file_a)}
}
EXAMPLE_FORMATTERS = { # Load these lazily for better speed
'specdoc' => ['spec/runner/formatter/specdoc_formatter', 'Formatter::SpecdocFormatter'],
's' => ['spec/runner/formatter/specdoc_formatter', 'Formatter::SpecdocFormatter'],
'nested' => ['spec/runner/formatter/nested_text_formatter', 'Formatter::NestedTextFormatter'],
'n' => ['spec/runner/formatter/nested_text_formatter', 'Formatter::NestedTextFormatter'],
'html' => ['spec/runner/formatter/html_formatter', 'Formatter::HtmlFormatter'],
'h' => ['spec/runner/formatter/html_formatter', 'Formatter::HtmlFormatter'],
'progress' => ['spec/runner/formatter/progress_bar_formatter', 'Formatter::ProgressBarFormatter'],
'p' => ['spec/runner/formatter/progress_bar_formatter', 'Formatter::ProgressBarFormatter'],
'failing_examples' => ['spec/runner/formatter/failing_examples_formatter', 'Formatter::FailingExamplesFormatter'],
'e' => ['spec/runner/formatter/failing_examples_formatter', 'Formatter::FailingExamplesFormatter'],
'failing_example_groups' => ['spec/runner/formatter/failing_example_groups_formatter', 'Formatter::FailingExampleGroupsFormatter'],
'g' => ['spec/runner/formatter/failing_example_groups_formatter', 'Formatter::FailingExampleGroupsFormatter'],
'profile' => ['spec/runner/formatter/profile_formatter', 'Formatter::ProfileFormatter'],
'o' => ['spec/runner/formatter/profile_formatter', 'Formatter::ProfileFormatter'],
'textmate' => ['spec/runner/formatter/text_mate_formatter', 'Formatter::TextMateFormatter']
}
STORY_FORMATTERS = {
'plain' => ['spec/runner/formatter/story/plain_text_formatter', 'Formatter::Story::PlainTextFormatter'],
'p' => ['spec/runner/formatter/story/plain_text_formatter', 'Formatter::Story::PlainTextFormatter'],
'html' => ['spec/runner/formatter/story/html_formatter', 'Formatter::Story::HtmlFormatter'],
'h' => ['spec/runner/formatter/story/html_formatter', 'Formatter::Story::HtmlFormatter']
}
attr_accessor(
:filename_pattern,
:backtrace_tweaker,
:context_lines,
:diff_format,
:dry_run,
:profile,
:examples,
:heckle_runner,
:line_number,
:loadby,
:reporter,
:reverse,
:timeout,
:verbose,
:user_input_for_runner,
:error_stream,
:output_stream,
:before_suite_parts,
:after_suite_parts,
# TODO: BT - Figure out a better name
:argv
)
attr_reader :colour, :differ_class, :files, :example_groups
def initialize(error_stream, output_stream)
@error_stream = error_stream
@output_stream = output_stream
@filename_pattern = "**/*_spec.rb"
@backtrace_tweaker = QuietBacktraceTweaker.new
@examples = []
@colour = false
@profile = false
@dry_run = false
@reporter = Reporter.new(self)
@context_lines = 3
@diff_format = :unified
@files = []
@example_groups = []
@result = nil
@examples_run = false
@examples_should_be_run = nil
@user_input_for_runner = nil
@before_suite_parts = []
@after_suite_parts = []
end
def add_example_group(example_group)
@example_groups << example_group
end
def remove_example_group(example_group)
@example_groups.delete(example_group)
end
def run_examples
return true unless examples_should_be_run?
success = true
begin
before_suite_parts.each do |part|
part.call
end
runner = custom_runner || ExampleGroupRunner.new(self)
unless @files_loaded
runner.load_files(files_to_load)
@files_loaded = true
end
if example_groups.empty?
true
else
set_spec_from_line_number if line_number
success = runner.run
@examples_run = true
heckle if heckle_runner
success
end
ensure
after_suite_parts.each do |part|
part.call(success)
end
end
end
def examples_run?
@examples_run
end
def examples_should_not_be_run
@examples_should_be_run = false
end
def colour=(colour)
@colour = colour
if @colour && RUBY_PLATFORM =~ /win32/ ;\
begin ;\
require 'rubygems' ;\
require 'Win32/Console/ANSI' ;\
rescue LoadError ;\
warn "You must 'gem install win32console' to use colour on Windows" ;\
@colour = false ;\
end
end
end
def parse_diff(format)
case format
when :context, 'context', 'c'
@diff_format = :context
default_differ
when :unified, 'unified', 'u', '', nil
@diff_format = :unified
default_differ
else
@diff_format = :custom
self.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 = ClassAndArgumentsParser.parse(format_arg)
unless where
raise "When using several --format options only one of them can be without a file" if @out_used
where = @output_stream
@out_used = true
end
@format_options ||= []
@format_options << [format, where]
end
def formatters
@format_options ||= [['progress', @output_stream]]
@formatters ||= load_formatters(@format_options, EXAMPLE_FORMATTERS)
end
def story_formatters
@format_options ||= [['plain', @output_stream]]
@formatters ||= load_formatters(@format_options, STORY_FORMATTERS)
end
def load_formatters(format_options, formatters)
format_options.map do |format, where|
formatter_type = if formatters[format]
require formatters[format][0]
eval(formatters[format][1], binding, __FILE__, __LINE__)
else
load_class(format, 'formatter', '--format')
end
formatter_type.new(self, where)
end
end
def load_heckle_runner(heckle)
suffix = [/mswin/, /java/].detect{|p| p =~ RUBY_PLATFORM} ? '_unsupported' : ''
require "spec/runner/heckle_runner#{suffix}"
@heckle_runner = HeckleRunner.new(heckle)
end
def number_of_examples
total = 0
@example_groups.each do |example_group|
total += example_group.number_of_examples
end
total
end
def files_to_load
result = []
sorted_files.each do |file|
if File.directory?(file)
filename_pattern.split(",").each do |pattern|
result += Dir[File.expand_path("#{file}/#{pattern.strip}")]
end
elsif File.file?(file)
result << file
else
raise "File or directory not found: #{file}"
end
end
result
end
protected
def examples_should_be_run?
return @examples_should_be_run unless @examples_should_be_run.nil?
@examples_should_be_run = true
end
def differ_class=(klass)
return unless klass
@differ_class = klass
Spec::Expectations.differ = self.differ_class.new(self)
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"
@error_stream.puts m
raise m
end
begin
eval(name, binding, __FILE__, __LINE__)
rescue NameError => e
@error_stream.puts "Couldn't find #{kind} class #{name}"
@error_stream.puts "Make sure the --require option is specified *before* #{option}"
if $_spec_spec ; raise e ; else exit(1) ; end
end
end
def custom_runner
return nil unless custom_runner?
klass_name, arg = ClassAndArgumentsParser.parse(user_input_for_runner)
runner_type = load_class(klass_name, 'behaviour runner', '--runner')
return runner_type.new(self, arg)
end
def custom_runner?
return user_input_for_runner ? true : false
end
def heckle
heckle_runner = self.heckle_runner
self.heckle_runner = nil
heckle_runner.heckle_with
end
def sorted_files
return sorter ? files.sort(&sorter) : files
end
def sorter
FILE_SORTERS[loadby]
end
def default_differ
require 'spec/expectations/differs/default'
self.differ_class = Spec::Expectations::Differs::Default
end
def set_spec_from_line_number
if examples.empty?
if files.length == 1
if File.directory?(files[0])
error_stream.puts "You must specify one file, not a directory when using the --line option"
exit(1) if stderr?
else
example = SpecParser.new.spec_name_for(files[0], line_number)
@examples = [example]
end
else
error_stream.puts "Only one file can be specified when using the --line option: #{files.inspect}"
exit(3) if stderr?
end
else
error_stream.puts "You cannot use both --line and --example"
exit(4) if stderr?
end
end
def stderr?
@error_stream == $stderr
end
end
end
end

View file

@ -0,0 +1,147 @@
module Spec
module Runner
class Reporter
attr_reader :options, :example_groups
def initialize(options)
@options = options
@options.reporter = self
clear
end
def add_example_group(example_group)
formatters.each do |f|
f.add_example_group(example_group)
end
example_groups << example_group
end
def example_started(example)
formatters.each{|f| f.example_started(example)}
end
def example_finished(example, error=nil)
@examples << example
if error.nil?
example_passed(example)
elsif Spec::Example::ExamplePendingError === error
example_pending(example, error.message)
else
example_failed(example, error)
end
end
def failure(example, error)
backtrace_tweaker.tweak_backtrace(error)
failure = Failure.new(example, error)
@failures << failure
formatters.each do |f|
f.example_failed(example, @failures.length, failure)
end
end
alias_method :example_failed, :failure
def start(number_of_examples)
clear
@start_time = Time.new
formatters.each{|f| f.start(number_of_examples)}
end
def end
@end_time = Time.new
end
# Dumps the summary and returns the total number of failures
def dump
formatters.each{|f| f.start_dump}
dump_pending
dump_failures
formatters.each do |f|
f.dump_summary(duration, @examples.length, @failures.length, @pending_count)
f.close
end
@failures.length
end
private
def formatters
@options.formatters
end
def backtrace_tweaker
@options.backtrace_tweaker
end
def clear
@example_groups = []
@failures = []
@pending_count = 0
@examples = []
@start_time = nil
@end_time = nil
end
def dump_failures
return if @failures.empty?
@failures.inject(1) do |index, failure|
formatters.each{|f| f.dump_failure(index, failure)}
index + 1
end
end
def dump_pending
formatters.each{|f| f.dump_pending}
end
def duration
return @end_time - @start_time unless (@end_time.nil? or @start_time.nil?)
return "0.0"
end
def example_passed(example)
formatters.each{|f| f.example_passed(example)}
end
def example_pending(example, message="Not Yet Implemented")
@pending_count += 1
formatters.each do |f|
f.example_pending(example, message)
end
end
class Failure
attr_reader :example, :exception
def initialize(example, exception)
@example = example
@exception = exception
end
def header
if expectation_not_met?
"'#{example_name}' FAILED"
elsif pending_fixed?
"'#{example_name}' FIXED"
else
"#{@exception.class.name} in '#{example_name}'"
end
end
def pending_fixed?
@exception.is_a?(Spec::Example::PendingExampleFixedError)
end
def expectation_not_met?
@exception.is_a?(Spec::Expectations::ExpectationNotMetError)
end
protected
def example_name
@example.__full_description
end
end
end
end
end

View file

@ -0,0 +1,71 @@
module Spec
module Runner
# Parses a spec file and finds the nearest example for a given line number.
class SpecParser
attr_reader :best_match
def initialize
@best_match = {}
end
def spec_name_for(file, line_number)
best_match.clear
file = File.expand_path(file)
rspec_options.example_groups.each do |example_group|
consider_example_groups_for_best_match example_group, file, line_number
example_group.examples.each do |example|
consider_example_for_best_match example, example_group, file, line_number
end
end
if best_match[:example_group]
if best_match[:example]
"#{best_match[:example_group].description} #{best_match[:example].description}"
else
best_match[:example_group].description
end
else
nil
end
end
protected
def consider_example_groups_for_best_match(example_group, file, line_number)
parsed_backtrace = parse_backtrace(example_group.registration_backtrace)
parsed_backtrace.each do |example_file, example_line|
if is_best_match?(file, line_number, example_file, example_line)
best_match.clear
best_match[:example_group] = example_group
best_match[:line] = example_line
end
end
end
def consider_example_for_best_match(example, example_group, file, line_number)
parsed_backtrace = parse_backtrace(example.implementation_backtrace)
parsed_backtrace.each do |example_file, example_line|
if is_best_match?(file, line_number, example_file, example_line)
best_match.clear
best_match[:example_group] = example_group
best_match[:example] = example
best_match[:line] = example_line
end
end
end
def is_best_match?(file, line_number, example_file, example_line)
file == File.expand_path(example_file) &&
example_line <= line_number &&
example_line > best_match[:line].to_i
end
def parse_backtrace(backtrace)
backtrace.collect do |trace_line|
split_line = trace_line.split(':')
[split_line[0], Integer(split_line[1])]
end
end
end
end
end

10
vendor/plugins/rspec/lib/spec/story.rb vendored Normal file
View file

@ -0,0 +1,10 @@
require 'spec'
require 'spec/story/extensions'
require 'spec/story/given_scenario'
require 'spec/story/runner'
require 'spec/story/scenario'
require 'spec/story/step'
require 'spec/story/step_group'
require 'spec/story/step_mother'
require 'spec/story/story'
require 'spec/story/world'

View file

@ -0,0 +1,3 @@
require 'spec/story/extensions/main'
require 'spec/story/extensions/string'
require 'spec/story/extensions/regexp'

View file

@ -0,0 +1,86 @@
module Spec
module Story
module Extensions
module Main
def Story(title, narrative, params = {}, &body)
::Spec::Story::Runner.story_runner.Story(title, narrative, params, &body)
end
# Calling this deprecated is silly, since it hasn't been released yet. But, for
# those who are reading this - this will be deleted before the 1.1 release.
def run_story(*args, &block)
runner = Spec::Story::Runner::PlainTextStoryRunner.new(*args)
runner.instance_eval(&block) if block
runner.run
end
# Creates (or appends to an existing) a namespaced group of steps for use in Stories
#
# == Examples
#
# # Creating a new group
# steps_for :forms do
# When("user enters $value in the $field field") do ... end
# When("user submits the $form form") do ... end
# end
def steps_for(tag, &block)
steps = rspec_story_steps[tag]
steps.instance_eval(&block) if block
steps
end
# Creates a context for running a Plain Text Story with specific groups of Steps.
# Also supports adding arbitrary steps that will only be accessible to
# the Story being run.
#
# == Examples
#
# # Run a Story with one group of steps
# with_steps_for :checking_accounts do
# run File.dirname(__FILE__) + "/withdraw_cash"
# end
#
# # Run a Story, adding steps that are only available for this Story
# with_steps_for :accounts do
# Given "user is logged in as account administrator"
# run File.dirname(__FILE__) + "/reconcile_accounts"
# end
#
# # Run a Story with steps from two groups
# with_steps_for :checking_accounts, :savings_accounts do
# run File.dirname(__FILE__) + "/transfer_money"
# end
#
# # Run a Story with a specific Story extension
# with_steps_for :login, :navigation do
# run File.dirname(__FILE__) + "/user_changes_password", :type => RailsStory
# end
def with_steps_for(*tags, &block)
steps = Spec::Story::StepGroup.new do
extend StoryRunnerStepGroupAdapter
end
tags.each {|tag| steps << rspec_story_steps[tag]}
steps.instance_eval(&block) if block
steps
end
private
module StoryRunnerStepGroupAdapter
def run(path, options={})
runner = Spec::Story::Runner::PlainTextStoryRunner.new(path, options)
runner.steps << self
runner.run
end
end
def rspec_story_steps # :nodoc:
$rspec_story_steps ||= Spec::Story::StepGroupHash.new
end
end
end
end
end
include Spec::Story::Extensions::Main

View file

@ -0,0 +1,9 @@
class Regexp
def step_name
self.source.gsub '\\$', '$$'
end
def arg_regexp
::Spec::Story::Step::PARAM_OR_GROUP_PATTERN
end
end

Some files were not shown because too many files have changed in this diff Show more