diff options
author | francis <francis> | 2008-01-23 01:57:59 +0000 |
---|---|---|
committer | francis <francis> | 2008-01-23 01:57:59 +0000 |
commit | 6d1733e8a0b5f404326cfbd78b7139c87ecbffa3 (patch) | |
tree | c86561445b9ec5179020f8f56afb7b9174b390c5 /vendor/plugins/rspec/lib | |
parent | 8bd6667c7885870af45522315f501a2b87340b1a (diff) |
Add files new to new rspec
Diffstat (limited to 'vendor/plugins/rspec/lib')
47 files changed, 2754 insertions, 0 deletions
diff --git a/vendor/plugins/rspec/lib/spec/example.rb b/vendor/plugins/rspec/lib/spec/example.rb new file mode 100644 index 000000000..39ff76b99 --- /dev/null +++ b/vendor/plugins/rspec/lib/spec/example.rb @@ -0,0 +1,12 @@ +require 'timeout' +require 'forwardable' +require 'spec/example/pending' +require 'spec/example/module_reopening_fix' +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' diff --git a/vendor/plugins/rspec/lib/spec/example/configuration.rb b/vendor/plugins/rspec/lib/spec/example/configuration.rb new file mode 100644 index 000000000..674184727 --- /dev/null +++ b/vendor/plugins/rspec/lib/spec/example/configuration.rb @@ -0,0 +1,144 @@ +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 + # setup_mocks_for_rspec, verify_mocks_for_rspec and teardown_mocks_for_rspec. + # These are your hooks into the lifecycle of a given example. RSpec will + # call setup_mocks_for_rspec before running anything else in each Example. + # After executing the #after methods, RSpec will then call verify_mocks_for_rspec + # and teardown_mocks_for_rspec (this is guaranteed to run even if there are + # failures in verify_mocks_for_rspec). + # + # Once you've defined this module, you can pass that to mock_with: + # + # Spec::Runner.configure do |config| + # config.mock_with MyMockFrameworkAdapter + # end + # + def mock_with(mock_framework) + @mock_framework = case mock_framework + when Symbol + mock_framework_path(mock_framework.to_s) + else + mock_framework + end + end + + def mock_framework # :nodoc: + @mock_framework ||= mock_framework_path("rspec") + end + + # Declares modules to be included in all example groups (<tt>describe</tt> blocks). + # + # config.include(My::Bottle, My::Cup) + # + # If you want to restrict the inclusion to a subset of all the example groups then + # specify this in a Hash as the last argument: + # + # 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.should_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 diff --git a/vendor/plugins/rspec/lib/spec/example/errors.rb b/vendor/plugins/rspec/lib/spec/example/errors.rb new file mode 100644 index 000000000..c6cb22453 --- /dev/null +++ b/vendor/plugins/rspec/lib/spec/example/errors.rb @@ -0,0 +1,9 @@ +module Spec + module Example + class ExamplePendingError < StandardError + end + + class PendingExampleFixedError < StandardError + end + end +end diff --git a/vendor/plugins/rspec/lib/spec/example/example_group.rb b/vendor/plugins/rspec/lib/spec/example/example_group.rb new file mode 100644 index 000000000..d6e156f93 --- /dev/null +++ b/vendor/plugins/rspec/lib/spec/example/example_group.rb @@ -0,0 +1,16 @@ +module Spec + module Example + # The superclass for all regular RSpec examples. + 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 diff --git a/vendor/plugins/rspec/lib/spec/example/example_group_factory.rb b/vendor/plugins/rspec/lib/spec/example/example_group_factory.rb new file mode 100644 index 000000000..0414a3b96 --- /dev/null +++ b/vendor/plugins/rspec/lib/spec/example/example_group_factory.rb @@ -0,0 +1,62 @@ +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, Spec::Farm::Example::FarmExampleGroup) + # + # This will cause Main#describe from a file living in + # <tt>spec/farm</tt> to create example group instances of type + # Spec::Farm::Example::FarmExampleGroup. + def register(id, example_group_class) + @example_group_types[id] = 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(id=nil) + if @example_group_types.values.include?(id) + id + else + @example_group_types[id] + end + end + + def create_example_group(*args, &block) + opts = Hash === args.last ? args.last : {} + if opts[:shared] + SharedExampleGroup.new(*args, &block) + else + superclass = determine_superclass(opts) + superclass.describe(*args, &block) + end + end + + protected + + def determine_superclass(opts) + id = if opts[:type] + opts[:type] + elsif opts[:spec_path] =~ /spec(\\|\/)(#{@example_group_types.keys.join('|')})/ + $2 == '' ? nil : $2.to_sym + end + get(id) + end + + end + self.reset + end + end +end diff --git a/vendor/plugins/rspec/lib/spec/example/example_group_methods.rb b/vendor/plugins/rspec/lib/spec/example/example_group_methods.rb new file mode 100644 index 000000000..8f73a9853 --- /dev/null +++ b/vendor/plugins/rspec/lib/spec/example/example_group_methods.rb @@ -0,0 +1,418 @@ +module Spec + module Example + + module ExampleGroupMethods + class << self + 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 + + def inherited(klass) + super + klass.register + 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) + if example_group_block + self.subclass("Subclass") do + describe(*args) + module_eval(&example_group_block) + end + else + set_description(*args) + before_eval + self + end + end + + # 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 + + def run + examples = examples_to_run + return true if examples.empty? + reporter.add_example_group(self) + 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 + include described_type + end + self + end + + 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 + rspec_options.add_example_group self + end + + def unregister #:nodoc: + rspec_options.remove_example_group self + 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 = ExampleMatcher.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 + end + end + + def after_parts_from_scope(scope) + case scope + when :each; after_each_parts + when :all; after_all_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 diff --git a/vendor/plugins/rspec/lib/spec/example/example_matcher.rb b/vendor/plugins/rspec/lib/spec/example/example_matcher.rb new file mode 100644 index 000000000..435eabf52 --- /dev/null +++ b/vendor/plugins/rspec/lib/spec/example/example_matcher.rb @@ -0,0 +1,42 @@ +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 + end +end diff --git a/vendor/plugins/rspec/lib/spec/example/example_methods.rb b/vendor/plugins/rspec/lib/spec/example/example_methods.rb new file mode 100644 index 000000000..6dd4c9c72 --- /dev/null +++ b/vendor/plugins/rspec/lib/spec/example/example_methods.rb @@ -0,0 +1,102 @@ +module Spec + module Example + module ExampleMethods + extend ExampleGroupMethods + extend ModuleReopeningFix + + 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 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 + + 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
\ No newline at end of file diff --git a/vendor/plugins/rspec/lib/spec/example/module_reopening_fix.rb b/vendor/plugins/rspec/lib/spec/example/module_reopening_fix.rb new file mode 100644 index 000000000..dc01dd666 --- /dev/null +++ b/vendor/plugins/rspec/lib/spec/example/module_reopening_fix.rb @@ -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
\ No newline at end of file diff --git a/vendor/plugins/rspec/lib/spec/example/pending.rb b/vendor/plugins/rspec/lib/spec/example/pending.rb new file mode 100644 index 000000000..b1f27c866 --- /dev/null +++ b/vendor/plugins/rspec/lib/spec/example/pending.rb @@ -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 diff --git a/vendor/plugins/rspec/lib/spec/example/shared_example_group.rb b/vendor/plugins/rspec/lib/spec/example/shared_example_group.rb new file mode 100644 index 000000000..a6fd6211c --- /dev/null +++ b/vendor/plugins/rspec/lib/spec/example/shared_example_group.rb @@ -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 diff --git a/vendor/plugins/rspec/lib/spec/extensions/class.rb b/vendor/plugins/rspec/lib/spec/extensions/class.rb new file mode 100644 index 000000000..30730f87e --- /dev/null +++ b/vendor/plugins/rspec/lib/spec/extensions/class.rb @@ -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
\ No newline at end of file diff --git a/vendor/plugins/rspec/lib/spec/extensions/main.rb b/vendor/plugins/rspec/lib/spec/extensions/main.rb new file mode 100644 index 000000000..281cbf879 --- /dev/null +++ b/vendor/plugins/rspec/lib/spec/extensions/main.rb @@ -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
\ No newline at end of file diff --git a/vendor/plugins/rspec/lib/spec/interop/test.rb b/vendor/plugins/rspec/lib/spec/interop/test.rb new file mode 100644 index 000000000..5c9543398 --- /dev/null +++ b/vendor/plugins/rspec/lib/spec/interop/test.rb @@ -0,0 +1,10 @@ +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)
\ No newline at end of file diff --git a/vendor/plugins/rspec/lib/spec/interop/test/unit/autorunner.rb b/vendor/plugins/rspec/lib/spec/interop/test/unit/autorunner.rb new file mode 100644 index 000000000..3944e6995 --- /dev/null +++ b/vendor/plugins/rspec/lib/spec/interop/test/unit/autorunner.rb @@ -0,0 +1,6 @@ +class Test::Unit::AutoRunner + remove_method :process_args + def process_args(argv) + true + end +end diff --git a/vendor/plugins/rspec/lib/spec/interop/test/unit/testcase.rb b/vendor/plugins/rspec/lib/spec/interop/test/unit/testcase.rb new file mode 100644 index 000000000..b32a820c1 --- /dev/null +++ b/vendor/plugins/rspec/lib/spec/interop/test/unit/testcase.rb @@ -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 diff --git a/vendor/plugins/rspec/lib/spec/interop/test/unit/testresult.rb b/vendor/plugins/rspec/lib/spec/interop/test/unit/testresult.rb new file mode 100644 index 000000000..1386dc728 --- /dev/null +++ b/vendor/plugins/rspec/lib/spec/interop/test/unit/testresult.rb @@ -0,0 +1,6 @@ +class Test::Unit::TestResult + alias_method :tu_passed?, :passed? + def passed? + return tu_passed? & ::Spec.run + end +end
\ No newline at end of file diff --git a/vendor/plugins/rspec/lib/spec/interop/test/unit/testsuite_adapter.rb b/vendor/plugins/rspec/lib/spec/interop/test/unit/testsuite_adapter.rb new file mode 100644 index 000000000..7c0ed092d --- /dev/null +++ b/vendor/plugins/rspec/lib/spec/interop/test/unit/testsuite_adapter.rb @@ -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 + diff --git a/vendor/plugins/rspec/lib/spec/interop/test/unit/ui/console/testrunner.rb b/vendor/plugins/rspec/lib/spec/interop/test/unit/ui/console/testrunner.rb new file mode 100644 index 000000000..663dd4722 --- /dev/null +++ b/vendor/plugins/rspec/lib/spec/interop/test/unit/ui/console/testrunner.rb @@ -0,0 +1,60 @@ +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) + 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 diff --git a/vendor/plugins/rspec/lib/spec/matchers/exist.rb b/vendor/plugins/rspec/lib/spec/matchers/exist.rb new file mode 100644 index 000000000..a5a911132 --- /dev/null +++ b/vendor/plugins/rspec/lib/spec/matchers/exist.rb @@ -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 diff --git a/vendor/plugins/rspec/lib/spec/matchers/simple_matcher.rb b/vendor/plugins/rspec/lib/spec/matchers/simple_matcher.rb new file mode 100644 index 000000000..ac547d06a --- /dev/null +++ b/vendor/plugins/rspec/lib/spec/matchers/simple_matcher.rb @@ -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
\ No newline at end of file diff --git a/vendor/plugins/rspec/lib/spec/runner/class_and_arguments_parser.rb b/vendor/plugins/rspec/lib/spec/runner/class_and_arguments_parser.rb new file mode 100644 index 000000000..65dc4519c --- /dev/null +++ b/vendor/plugins/rspec/lib/spec/runner/class_and_arguments_parser.rb @@ -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
\ No newline at end of file diff --git a/vendor/plugins/rspec/lib/spec/runner/example_group_runner.rb b/vendor/plugins/rspec/lib/spec/runner/example_group_runner.rb new file mode 100644 index 000000000..7275c6a88 --- /dev/null +++ b/vendor/plugins/rspec/lib/spec/runner/example_group_runner.rb @@ -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
\ No newline at end of file diff --git a/vendor/plugins/rspec/lib/spec/runner/formatter/failing_example_groups_formatter.rb b/vendor/plugins/rspec/lib/spec/runner/formatter/failing_example_groups_formatter.rb new file mode 100644 index 000000000..5a4607983 --- /dev/null +++ b/vendor/plugins/rspec/lib/spec/runner/formatter/failing_example_groups_formatter.rb @@ -0,0 +1,31 @@ +require 'spec/runner/formatter/base_text_formatter' + +module Spec + module Runner + module Formatter + class FailingExampleGroupsFormatter < BaseTextFormatter + def add_example_group(example_group) + super + @example_group_description_parts = example_group.description_parts + end + + def example_failed(example, counter, failure) + if @example_group_description_parts + 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_description_parts = nil + end + end + + def dump_failure(counter, failure) + end + + def dump_summary(duration, example_count, failure_count, pending_count) + end + end + end + end +end diff --git a/vendor/plugins/rspec/lib/spec/runner/formatter/profile_formatter.rb b/vendor/plugins/rspec/lib/spec/runner/formatter/profile_formatter.rb new file mode 100644 index 000000000..3784f3ac7 --- /dev/null +++ b/vendor/plugins/rspec/lib/spec/runner/formatter/profile_formatter.rb @@ -0,0 +1,47 @@ +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 + end + end + end +end diff --git a/vendor/plugins/rspec/lib/spec/runner/formatter/story/html_formatter.rb b/vendor/plugins/rspec/lib/spec/runner/formatter/story/html_formatter.rb new file mode 100644 index 000000000..b70ac153a --- /dev/null +++ b/vendor/plugins/rspec/lib/spec/runner/formatter/story/html_formatter.rb @@ -0,0 +1,125 @@ +require 'erb' +require 'spec/runner/formatter/base_text_formatter' + +module Spec + module Runner + module Formatter + module Story + class HtmlFormatter < BaseTextFormatter + include ERB::Util + + 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) + @output.puts <<-EOF + <dl class="story passed"> + <dt>Story: #{h title}</dt> + <dd> + <p> + #{h(narrative).split("\n").join("<br />")} + </p> +EOF + end + + def story_ended(title, narrative) + @output.puts <<-EOF + </dd> + </dl> +EOF + end + + def scenario_started(story_title, scenario_name) + @output.puts <<-EOF + <dl class="passed"> + <dt>Scenario: #{h scenario_name}</dt> + <dd> + <ul class="steps"> +EOF + end + + def scenario_ended + @output.puts <<-EOF + </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_ended + 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] } + @output.puts " <li class=\"#{klass}\">#{inner}</li>" + end + end + end + end + end +end
\ No newline at end of file diff --git a/vendor/plugins/rspec/lib/spec/runner/formatter/story/plain_text_formatter.rb b/vendor/plugins/rspec/lib/spec/runner/formatter/story/plain_text_formatter.rb new file mode 100644 index 000000000..424e27cc6 --- /dev/null +++ b/vendor/plugins/rspec/lib/spec/runner/formatter/story/plain_text_formatter.rb @@ -0,0 +1,128 @@ +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 + @failed_scenarios = [] + @pending_steps = [] + @previous_type = nil + end + + def run_started(count) + @count = count + @output.puts "Running #@count scenarios\n\n" + end + + def story_started(title, narrative) + @current_story_title = title + @output.puts "Story: #{title}\n\n" + narrative.each_line do |line| + @output.print " " + @output.print line + end + end + + def story_ended(title, narrative) + @output.puts + @output.puts + end + + def scenario_started(story_title, scenario_name) + @current_scenario_name = scenario_name + @scenario_already_failed = false + @output.print "\n\n Scenario: #{scenario_name}" + @scenario_ok = true + end + + def scenario_succeeded(story_title, scenario_name) + @successful_scenario_count += 1 + 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 + end + + def scenario_pending(story_title, scenario_name, msg) + @pending_scenario_count += 1 unless @scenario_already_failed + @scenario_already_failed = true + 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_succeeded(type, description, *args) + found_step(type, description, false, *args) + end + + def step_pending(type, description, *args) + found_step(type, description, false, *args) + @pending_steps << [@current_story_title, @current_scenario_name, description] + @output.print " (PENDING)" + @scenario_ok = false + end + + def step_failed(type, description, *args) + found_step(type, description, true, *args) + @output.print red(@scenario_ok ? " (FAILED)" : " (SKIPPED)") + @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, *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] } + @output.print(failed ? red(text) : green(text)) + + if type == :'given scenario' + @previous_type = :given + else + @previous_type = type + end + end + end + end + end + end +end diff --git a/vendor/plugins/rspec/lib/spec/runner/formatter/text_mate_formatter.rb b/vendor/plugins/rspec/lib/spec/runner/formatter/text_mate_formatter.rb new file mode 100644 index 000000000..4c0a9c7de --- /dev/null +++ b/vendor/plugins/rspec/lib/spec/runner/formatter/text_mate_formatter.rb @@ -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 diff --git a/vendor/plugins/rspec/lib/spec/story.rb b/vendor/plugins/rspec/lib/spec/story.rb new file mode 100644 index 000000000..bc6960a28 --- /dev/null +++ b/vendor/plugins/rspec/lib/spec/story.rb @@ -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' diff --git a/vendor/plugins/rspec/lib/spec/story/extensions.rb b/vendor/plugins/rspec/lib/spec/story/extensions.rb new file mode 100644 index 000000000..dc7dd1140 --- /dev/null +++ b/vendor/plugins/rspec/lib/spec/story/extensions.rb @@ -0,0 +1,3 @@ +require 'spec/story/extensions/main' +require 'spec/story/extensions/string' +require 'spec/story/extensions/regexp' diff --git a/vendor/plugins/rspec/lib/spec/story/extensions/main.rb b/vendor/plugins/rspec/lib/spec/story/extensions/main.rb new file mode 100644 index 000000000..6336b630c --- /dev/null +++ b/vendor/plugins/rspec/lib/spec/story/extensions/main.rb @@ -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
\ No newline at end of file diff --git a/vendor/plugins/rspec/lib/spec/story/extensions/regexp.rb b/vendor/plugins/rspec/lib/spec/story/extensions/regexp.rb new file mode 100644 index 000000000..7955b4c33 --- /dev/null +++ b/vendor/plugins/rspec/lib/spec/story/extensions/regexp.rb @@ -0,0 +1,9 @@ +class Regexp + def step_name + self.source + end + + def arg_regexp + ::Spec::Story::Step::PARAM_OR_GROUP_PATTERN + end +end
\ No newline at end of file diff --git a/vendor/plugins/rspec/lib/spec/story/extensions/string.rb b/vendor/plugins/rspec/lib/spec/story/extensions/string.rb new file mode 100644 index 000000000..896578def --- /dev/null +++ b/vendor/plugins/rspec/lib/spec/story/extensions/string.rb @@ -0,0 +1,9 @@ +class String + def step_name + self + end + + def arg_regexp + ::Spec::Story::Step::PARAM_PATTERN + end +end
\ No newline at end of file diff --git a/vendor/plugins/rspec/lib/spec/story/given_scenario.rb b/vendor/plugins/rspec/lib/spec/story/given_scenario.rb new file mode 100644 index 000000000..88c51f981 --- /dev/null +++ b/vendor/plugins/rspec/lib/spec/story/given_scenario.rb @@ -0,0 +1,14 @@ +module Spec + module Story + class GivenScenario + def initialize(name) + @name = name + end + + def perform(instance, ignore_name) + scenario = Runner::StoryRunner.scenario_from_current_story(@name) + Runner::ScenarioRunner.new.run(scenario, instance) + end + end + end +end diff --git a/vendor/plugins/rspec/lib/spec/story/runner.rb b/vendor/plugins/rspec/lib/spec/story/runner.rb new file mode 100644 index 000000000..2dd36fbc6 --- /dev/null +++ b/vendor/plugins/rspec/lib/spec/story/runner.rb @@ -0,0 +1,58 @@ +require 'spec/story/runner/scenario_collector.rb' +require 'spec/story/runner/scenario_runner.rb' +require 'spec/story/runner/story_runner.rb' +require 'spec/story/runner/story_parser.rb' +require 'spec/story/runner/story_mediator.rb' +require 'spec/story/runner/plain_text_story_runner.rb' + +module Spec + module Story + module Runner + class << self + def run_options # :nodoc: + @run_options ||= ::Spec::Runner::OptionParser.parse(ARGV, $stderr, $stdout) + end + + def story_runner # :nodoc: + unless @story_runner + @story_runner = StoryRunner.new(scenario_runner, world_creator) + run_options.story_formatters.each do |formatter| + register_listener(formatter) + end + Runner.register_exit_hook + end + @story_runner + end + + def scenario_runner # :nodoc: + @scenario_runner ||= ScenarioRunner.new + end + + def world_creator # :nodoc: + @world_creator ||= World + end + + # Use this to register a customer output formatter. + def register_listener(listener) + story_runner.add_listener(listener) # run_started, story_started, story_ended, #run_ended + world_creator.add_listener(listener) # found_scenario, step_succeeded, step_failed, step_failed + scenario_runner.add_listener(listener) # scenario_started, scenario_succeeded, scenario_pending, scenario_failed + end + + def register_exit_hook # :nodoc: + # TODO - when story runner uses test/unit runners like example runner does we can kill + # this and also the assorted Kernel.stub!(:at_exit) in examples + at_exit do + Runner.story_runner.run_stories unless $! + end + # TODO exit with non-zero status if run fails + end + + def dry_run + run_options.dry_run + end + + end + end + end +end diff --git a/vendor/plugins/rspec/lib/spec/story/runner/plain_text_story_runner.rb b/vendor/plugins/rspec/lib/spec/story/runner/plain_text_story_runner.rb new file mode 100644 index 000000000..8d34ea2d2 --- /dev/null +++ b/vendor/plugins/rspec/lib/spec/story/runner/plain_text_story_runner.rb @@ -0,0 +1,48 @@ +module Spec + module Story + module Runner + class PlainTextStoryRunner + # You can initialize a PlainTextStoryRunner with the path to the + # story file or a block, in which you can define the path using load. + # + # == Examples + # + # PlainTextStoryRunner.new('path/to/file') + # + # PlainTextStoryRunner.new do |runner| + # runner.load 'path/to/file' + # end + def initialize(*args) + @options = Hash === args.last ? args.pop : {} + @story_file = args.empty? ? nil : args.shift + yield self if block_given? + end + + def []=(key, value) + @options[key] = value + end + + def load(path) + @story_file = path + end + + def run + raise "You must set a path to the file with the story. See the RDoc." if @story_file.nil? + mediator = Spec::Story::Runner::StoryMediator.new(steps, Spec::Story::Runner.story_runner, @options) + parser = Spec::Story::Runner::StoryParser.new(mediator) + + story_text = File.read(@story_file) + parser.parse(story_text.split("\n")) + + mediator.run_stories + end + + def steps + @step_group ||= Spec::Story::StepGroup.new + yield @step_group if block_given? + @step_group + end + end + end + end +end diff --git a/vendor/plugins/rspec/lib/spec/story/runner/scenario_collector.rb b/vendor/plugins/rspec/lib/spec/story/runner/scenario_collector.rb new file mode 100644 index 000000000..78339fd22 --- /dev/null +++ b/vendor/plugins/rspec/lib/spec/story/runner/scenario_collector.rb @@ -0,0 +1,18 @@ +module Spec + module Story + module Runner + class ScenarioCollector + attr_accessor :scenarios + + def initialize(story) + @story = story + @scenarios = [] + end + + def Scenario(name, &body) + @scenarios << Scenario.new(@story, name, &body) + end + end + end + end +end diff --git a/vendor/plugins/rspec/lib/spec/story/runner/scenario_runner.rb b/vendor/plugins/rspec/lib/spec/story/runner/scenario_runner.rb new file mode 100644 index 000000000..aee52e412 --- /dev/null +++ b/vendor/plugins/rspec/lib/spec/story/runner/scenario_runner.rb @@ -0,0 +1,46 @@ +module Spec + module Story + module Runner + class ScenarioRunner + def initialize + @listeners = [] + end + + def run(scenario, world) + @listeners.each { |l| l.scenario_started(scenario.story.title, scenario.name) } + run_story_ignoring_scenarios(scenario.story, world) + + world.start_collecting_errors + world.instance_eval(&scenario.body) + if world.errors.empty? + @listeners.each { |l| l.scenario_succeeded(scenario.story.title, scenario.name) } + else + if Spec::Example::ExamplePendingError === (e = world.errors.first) + @listeners.each { |l| l.scenario_pending(scenario.story.title, scenario.name, e.message) } + else + @listeners.each { |l| l.scenario_failed(scenario.story.title, scenario.name, e) } + end + end + end + + def add_listener(listener) + @listeners << listener + end + + private + + def run_story_ignoring_scenarios(story, world) + class << world + def Scenario(name, &block) + # do nothing + end + end + story.run_in(world) + class << world + remove_method(:Scenario) + end + end + end + end + end +end diff --git a/vendor/plugins/rspec/lib/spec/story/runner/story_mediator.rb b/vendor/plugins/rspec/lib/spec/story/runner/story_mediator.rb new file mode 100644 index 000000000..1f4744b9f --- /dev/null +++ b/vendor/plugins/rspec/lib/spec/story/runner/story_mediator.rb @@ -0,0 +1,123 @@ + module Spec + module Story + module Runner + + class StoryMediator + def initialize(step_group, runner, options={}) + @step_group = step_group + @stories = [] + @runner = runner + @options = options + end + + def stories + @stories.collect { |p| p.to_proc } + end + + def create_story(title, narrative) + @stories << Story.new(title, narrative, @step_group, @options) + end + + def create_scenario(title) + current_story.add_scenario Scenario.new(title) + end + + def create_given(name) + current_scenario.add_step Step.new('Given', name) + end + + def create_given_scenario(name) + current_scenario.add_step Step.new('GivenScenario', name) + end + + def create_when(name) + current_scenario.add_step Step.new('When', name) + end + + def create_then(name) + current_scenario.add_step Step.new('Then', name) + end + + def run_stories + stories.each { |story| @runner.instance_eval(&story) } + end + + private + def current_story + @stories.last + end + + def current_scenario + current_story.current_scenario + end + + class Story + def initialize(title, narrative, step_group, options) + @title = title + @narrative = narrative + @scenarios = [] + @step_group = step_group + @options = options + end + + def to_proc + title = @title + narrative = @narrative + scenarios = @scenarios.collect { |scenario| scenario.to_proc } + options = @options.merge(:steps => @step_group) + lambda do + Story title, narrative, options do + scenarios.each { |scenario| instance_eval(&scenario) } + end + end + end + + def add_scenario(scenario) + @scenarios << scenario + end + + def current_scenario + @scenarios.last + end + end + + class Scenario + def initialize(name) + @name = name + @steps = [] + end + + def to_proc + name = @name + steps = @steps.collect { |step| step.to_proc } + lambda do + Scenario name do + steps.each { |step| instance_eval(&step) } + end + end + end + + def add_step(step) + @steps << step + end + end + + class Step + def initialize(type, name) + @type = type + @name = name + end + + def to_proc + type = @type + name = @name + lambda do + send(type, name) + end + end + end + end + + end + end +end diff --git a/vendor/plugins/rspec/lib/spec/story/runner/story_parser.rb b/vendor/plugins/rspec/lib/spec/story/runner/story_parser.rb new file mode 100644 index 000000000..d454df8cb --- /dev/null +++ b/vendor/plugins/rspec/lib/spec/story/runner/story_parser.rb @@ -0,0 +1,227 @@ +module Spec + module Story + module Runner + + class IllegalStepError < StandardError + def initialize(state, event) + super("Illegal attempt to create a #{event} after a #{state}") + end + end + + class StoryParser + def initialize(story_mediator) + @story_mediator = story_mediator + @current_story_lines = [] + transition_to(:starting_state) + end + + def parse(lines) + lines.reject! {|line| line == ""} + until lines.empty? + process_line(lines.shift) + end + @state.eof + end + + def process_line(line) + line.strip! + case line + when /^Story: / then @state.story(line) + when /^Scenario: / then @state.scenario(line) + when /^Given:? / then @state.given(line) + when /^GivenScenario:? / then @state.given_scenario(line) + when /^When:? / then @state.event(line) + when /^Then:? / then @state.outcome(line) + when /^And:? / then @state.one_more_of_the_same(line) + else @state.other(line) + end + end + + def init_story(title) + @current_story_lines.clear + add_story_line(title) + end + + def add_story_line(line) + @current_story_lines << line + end + + def create_story() + unless @current_story_lines.empty? + @story_mediator.create_story(@current_story_lines[0].gsub("Story: ",""), @current_story_lines[1..-1].join("\n")) + @current_story_lines.clear + end + end + + def create_scenario(title) + @story_mediator.create_scenario(title.gsub("Scenario: ","")) + end + + def create_given(name) + @story_mediator.create_given(name) + end + + def create_given_scenario(name) + @story_mediator.create_given_scenario(name) + end + + def create_when(name) + @story_mediator.create_when(name) + end + + def create_then(name) + @story_mediator.create_then(name) + end + + def transition_to(key) + @state = states[key] + end + + def states + @states ||= { + :starting_state => StartingState.new(self), + :story_state => StoryState.new(self), + :scenario_state => ScenarioState.new(self), + :given_state => GivenState.new(self), + :when_state => WhenState.new(self), + :then_state => ThenState.new(self) + } + end + + class State + def initialize(parser) + @parser = parser + end + + def story(line) + @parser.init_story(line) + @parser.transition_to(:story_state) + end + + def scenario(line) + @parser.create_scenario(line) + @parser.transition_to(:scenario_state) + end + + def given(line) + @parser.create_given(remove_tag_from(:given, line)) + @parser.transition_to(:given_state) + end + + def given_scenario(line) + @parser.create_given_scenario(remove_tag_from(:givenscenario, line)) + @parser.transition_to(:given_state) + end + + def event(line) + @parser.create_when(remove_tag_from(:when, line)) + @parser.transition_to(:when_state) + end + + def outcome(line) + @parser.create_then(remove_tag_from(:then, line)) + @parser.transition_to(:then_state) + end + + def remove_tag_from(tag, line) + tokens = line.split + # validation of tag can go here + tokens[0].downcase.match(/#{tag.to_s}:?/) ? + (tokens[1..-1].join(' ')) : line + end + + def eof + end + + def other(line) + # no-op - supports header text before the first story in a file + end + end + + class StartingState < State + def initialize(parser) + @parser = parser + end + end + + class StoryState < State + def one_more_of_the_same(line) + other(line) + end + + def story(line) + @parser.create_story + @parser.add_story_line(line) + end + + def scenario(line) + @parser.create_story + @parser.create_scenario(line) + @parser.transition_to(:scenario_state) + end + + def given(line) + other(line) + end + + def event(line) + other(line) + end + + def outcome(line) + other(line) + end + + def other(line) + @parser.add_story_line(line) + end + + def eof + @parser.create_story + end + end + + class ScenarioState < State + def one_more_of_the_same(line) + raise IllegalStepError.new("Scenario", "And") + end + + def scenario(line) + @parser.create_scenario(line) + end + end + + class GivenState < State + def one_more_of_the_same(line) + @parser.create_given(remove_tag_from(:and, line)) + end + + def given(line) + @parser.create_given(remove_tag_from(:given, line)) + end + end + + class WhenState < State + def one_more_of_the_same(line) + @parser.create_when(remove_tag_from(:and ,line)) + end + + def event(line) + @parser.create_when(remove_tag_from(:when ,line)) + end + end + + class ThenState < State + def one_more_of_the_same(line) + @parser.create_then(remove_tag_from(:and ,line)) + end + + def outcome(line) + @parser.create_then(remove_tag_from(:then ,line)) + end + end + + end + end + end +end diff --git a/vendor/plugins/rspec/lib/spec/story/runner/story_runner.rb b/vendor/plugins/rspec/lib/spec/story/runner/story_runner.rb new file mode 100644 index 000000000..f9eeb9ac1 --- /dev/null +++ b/vendor/plugins/rspec/lib/spec/story/runner/story_runner.rb @@ -0,0 +1,68 @@ +module Spec + module Story + module Runner + class StoryRunner + class << self + attr_accessor :current_story_runner + + def scenario_from_current_story(scenario_name) + current_story_runner.scenario_from_current_story(scenario_name) + end + end + + attr_accessor :stories, :scenarios, :current_story + + def initialize(scenario_runner, world_creator = World) + StoryRunner.current_story_runner = self + @scenario_runner = scenario_runner + @world_creator = world_creator + @stories = [] + @scenarios_by_story = {} + @scenarios = [] + @listeners = [] + end + + def Story(title, narrative, params = {}, &body) + story = Story.new(title, narrative, params, &body) + @stories << story + + # collect scenarios + collector = ScenarioCollector.new(story) + story.run_in(collector) + @scenarios += collector.scenarios + @scenarios_by_story[story.title] = collector.scenarios + end + + def run_stories + return if @stories.empty? + @listeners.each { |l| l.run_started(scenarios.size) } + @stories.each do |story| + story.assign_steps_to(World) + @current_story = story + @listeners.each { |l| l.story_started(story.title, story.narrative) } + scenarios = @scenarios_by_story[story.title] + scenarios.each do |scenario| + type = story[:type] || Object + args = story[:args] || [] + world = @world_creator.create(type, *args) + @scenario_runner.run(scenario, world) + end + @listeners.each { |l| l.story_ended(story.title, story.narrative) } + World.step_mother.clear + end + unique_steps = (World.step_names.collect {|n| Regexp === n ? n.source : n.to_s}).uniq.sort + @listeners.each { |l| l.collected_steps(unique_steps) } + @listeners.each { |l| l.run_ended } + end + + def add_listener(listener) + @listeners << listener + end + + def scenario_from_current_story(scenario_name) + @scenarios_by_story[@current_story.title].find {|s| s.name == scenario_name } + end + end + end + end +end diff --git a/vendor/plugins/rspec/lib/spec/story/scenario.rb b/vendor/plugins/rspec/lib/spec/story/scenario.rb new file mode 100644 index 000000000..d83b3eeb8 --- /dev/null +++ b/vendor/plugins/rspec/lib/spec/story/scenario.rb @@ -0,0 +1,14 @@ + +module Spec + module Story + class Scenario + attr_accessor :name, :body, :story + + def initialize(story, name, &body) + @story = story + @name = name + @body = body + end + end + end +end diff --git a/vendor/plugins/rspec/lib/spec/story/step.rb b/vendor/plugins/rspec/lib/spec/story/step.rb new file mode 100644 index 000000000..1d596e92c --- /dev/null +++ b/vendor/plugins/rspec/lib/spec/story/step.rb @@ -0,0 +1,56 @@ +module Spec + module Story + class Step + PARAM_PATTERN = /(\$\w*)/ + PARAM_OR_GROUP_PATTERN = /(\$\w*)|\(.*?\)/ + + attr_reader :name + def initialize(name, &block) + @name = name + assign_expression(name) + init_module(name, &block) + end + + def perform(instance, *args) + instance.extend(@mod) + instance.__send__(sanitize(@name), *args) + end + + def init_module(name, &block) + sanitized_name = sanitize(name) + @mod = Module.new do + define_method(sanitized_name, &block) + end + end + + def sanitize(a_string_or_regexp) + return a_string_or_regexp.source if Regexp == a_string_or_regexp + a_string_or_regexp.to_s + end + + + def matches?(name) + !(matches = name.match(@expression)).nil? + end + + def parse_args(name) + name.match(@expression)[1..-1] + end + + private + + def assign_expression(name) + expression = name.dup + if String === expression + expression.gsub! '(', '\(' + expression.gsub! ')', '\)' + while expression =~ PARAM_PATTERN + expression.gsub!($1, "(.*?)") + end + end + @expression = Regexp.new("^#{expression}$") + end + + end + end +end
\ No newline at end of file diff --git a/vendor/plugins/rspec/lib/spec/story/step_group.rb b/vendor/plugins/rspec/lib/spec/story/step_group.rb new file mode 100644 index 000000000..cae558c40 --- /dev/null +++ b/vendor/plugins/rspec/lib/spec/story/step_group.rb @@ -0,0 +1,89 @@ +module Spec + module Story + + class StepGroupHash < Hash + def initialize + super do |h,k| + h[k] = Spec::Story::StepGroup.new + end + end + end + + class StepGroup + def self.steps(&block) + @step_group ||= StepGroup.new(false) + @step_group.instance_eval(&block) if block + @step_group + end + + def initialize(init_defaults=true, &block) + @hash_of_lists_of_steps = Hash.new {|h, k| h[k] = []} + if init_defaults + self.class.steps.add_to(self) + end + instance_eval(&block) if block + end + + def find(type, name) + @hash_of_lists_of_steps[type].each do |step| + return step if step.matches?(name) + end + return nil + end + + def GivenScenario(name, &block) + create_matcher(:given_scenario, name, &block) + end + + def Given(name, &block) + create_matcher(:given, name, &block) + end + + def When(name, &block) + create_matcher(:when, name, &block) + end + + def Then(name, &block) + create_matcher(:then, name, &block) + end + + alias :given_scenario :GivenScenario + alias :given :Given + alias :when :When + alias :then :Then + + def add(type, steps) + (@hash_of_lists_of_steps[type] << steps).flatten! + end + + def clear + @hash_of_lists_of_steps.clear + end + + def empty? + [:given_scenario, :given, :when, :then].each do |type| + return false unless @hash_of_lists_of_steps[type].empty? + end + return true + end + + def add_to(other_step_matchers) + [:given_scenario, :given, :when, :then].each do |type| + other_step_matchers.add(type, @hash_of_lists_of_steps[type]) + end + end + + def <<(other_step_matchers) + other_step_matchers.add_to(self) if other_step_matchers.respond_to?(:add_to) + end + + # TODO - make me private + def create_matcher(type, name, &block) + matcher = Step.new(name, &block) + @hash_of_lists_of_steps[type] << matcher + matcher + end + + end + end +end diff --git a/vendor/plugins/rspec/lib/spec/story/step_mother.rb b/vendor/plugins/rspec/lib/spec/story/step_mother.rb new file mode 100644 index 000000000..a2e84e310 --- /dev/null +++ b/vendor/plugins/rspec/lib/spec/story/step_mother.rb @@ -0,0 +1,37 @@ +module Spec + module Story + class StepMother + def initialize + @steps = StepGroup.new + end + + def use(new_step_group) + @steps << new_step_group + end + + def store(type, step) + @steps.add(type, step) + end + + def find(type, name) + if @steps.find(type, name).nil? + @steps.add(type, + Step.new(name) do + raise Spec::Example::ExamplePendingError.new("Unimplemented step: #{name}") + end + ) + end + @steps.find(type, name) + end + + def clear + @steps.clear + end + + def empty? + @steps.empty? + end + + end + end +end diff --git a/vendor/plugins/rspec/lib/spec/story/story.rb b/vendor/plugins/rspec/lib/spec/story/story.rb new file mode 100644 index 000000000..112e9414b --- /dev/null +++ b/vendor/plugins/rspec/lib/spec/story/story.rb @@ -0,0 +1,42 @@ +module Spec + module Story + class Story + attr_reader :title, :narrative + + def initialize(title, narrative, params = {}, &body) + @body = body + @title = title + @narrative = narrative + @params = params + end + + def [](key) + @params[key] + end + + def run_in(obj) + obj.instance_eval(&@body) + end + + def assign_steps_to(assignee) + if @params[:steps] + assignee.use(@params[:steps]) + else + case keys = @params[:steps_for] + when Symbol + keys = [keys] + when nil + keys = [] + end + keys.each do |key| + assignee.use(steps_for(key)) + end + end + end + + def steps_for(key) + $rspec_story_steps[key] + end + end + end +end diff --git a/vendor/plugins/rspec/lib/spec/story/world.rb b/vendor/plugins/rspec/lib/spec/story/world.rb new file mode 100644 index 000000000..6296537b8 --- /dev/null +++ b/vendor/plugins/rspec/lib/spec/story/world.rb @@ -0,0 +1,124 @@ +require 'rubygems' +require 'spec/expectations' +require 'spec/matchers' +require 'spec/example/pending' + +module Spec + module Story +=begin + A World represents the actual instance a scenario will run in. + + The runner ensures any instance variables and methods defined anywhere + in a story block are available to all the scenarios. This includes + variables that are created or referenced inside Given, When and Then + blocks. +=end + module World + include ::Spec::Example::Pending + include ::Spec::Matchers + # store steps and listeners in the singleton metaclass. + # This serves both to keep them out of the way of runtime Worlds + # and to make them available to all instances. + class << self + def create(cls = Object, *args) + cls.new(*args).extend(World) + end + + def listeners + @listeners ||= [] + end + + def add_listener(listener) + listeners() << listener + end + + def step_mother + @step_mother ||= StepMother.new + end + + def use(steps) + step_mother.use(steps) + end + + def step_names + @step_names ||= [] + end + + def run_given_scenario_with_suspended_listeners(world, type, name, scenario) + current_listeners = Array.new(listeners) + begin + listeners.each { |l| l.found_scenario(type, name) } + @listeners.clear + scenario.perform(world, name) unless ::Spec::Story::Runner.dry_run + ensure + @listeners.replace(current_listeners) + end + end + + def store_and_call(world, type, name, *args, &block) + if block_given? + step_mother.store(type, Step.new(name, &block)) + end + step = step_mother.find(type, name) + + step_name = step.name + step_names << step_name + + # It's important to have access to the parsed args here, so + # we can give them to the listeners. The HTML reporter needs + # the args so it can style them. See the generated output in + # story_server/prototype/rspec_stories.html (generated by rake stories) + args = step.parse_args(name) if args.empty? + begin + step.perform(world, *args) unless ::Spec::Story::Runner.dry_run + listeners.each { |l| l.step_succeeded(type, step_name, *args) } + rescue Exception => e + case e + when Spec::Example::ExamplePendingError + @listeners.each { |l| l.step_pending(type, step_name, *args) } + else + @listeners.each { |l| l.step_failed(type, step_name, *args) } + end + errors << e + end + end + + def errors + @errors ||= [] + end + end # end of class << self + + def start_collecting_errors + errors.clear + end + + def errors + World.errors + end + + def GivenScenario(name) + World.run_given_scenario_with_suspended_listeners(self, :'given scenario', name, GivenScenario.new(name)) + @__previous_step = :given + end + + def Given(name, *args, &block) + World.store_and_call self, :given, name, *args, &block + @__previous_step = :given + end + + def When(name, *args, &block) + World.store_and_call self, :when, name, *args, &block + @__previous_step = :when + end + + def Then(name, *args, &block) + World.store_and_call self, :then, name, *args, &block + @__previous_step = :then + end + + def And(name, *args, &block) + World.store_and_call self, @__previous_step, name, *args, &block + end + end + end +end |