From 946d56e811ad892692bbce20b16de4b52d78f959 Mon Sep 17 00:00:00 2001 From: Brian Ford Date: Wed, 16 Jul 2008 19:53:50 -0700 Subject: [PATCH] Updated MSpec source to 1c3ee1c8. --- .../0001-Updated-version-to-1.4.0.-See-below.patch | 217 ++++++++++ mspec/README | 93 ++++ mspec/lib/mspec/commands/mspec-ci.rb | 2 + mspec/lib/mspec/matchers.rb | 7 +- mspec/lib/mspec/matchers/equal_element.rb | 78 ++++ mspec/lib/mspec/runner/context.rb | 190 +++++--- mspec/lib/mspec/runner/example.rb | 71 ++- mspec/lib/mspec/runner/mspec.rb | 51 ++- mspec/lib/mspec/runner/object.rb | 6 +- mspec/lib/mspec/runner/shared.rb | 12 +- mspec/lib/mspec/version.rb | 2 +- mspec/mspec-1.4.0.gemspec | 33 ++ mspec/spec/commands/mspec_ci_spec.rb | 14 + mspec/spec/matchers/equal_element_spec.rb | 75 ++++ mspec/spec/runner/actions/debug_spec.rb | 3 +- mspec/spec/runner/actions/gdb_spec.rb | 2 +- mspec/spec/runner/actions/tag_spec.rb | 11 +- mspec/spec/runner/context_spec.rb | 479 ++++++++++++++++++--- mspec/spec/runner/example_spec.rb | 90 ++-- mspec/spec/runner/exception_spec.rb | 14 +- mspec/spec/runner/formatters/dotted_spec.rb | 7 +- mspec/spec/runner/formatters/html_spec.rb | 7 +- mspec/spec/runner/formatters/specdoc_spec.rb | 5 +- mspec/spec/runner/formatters/summary_spec.rb | 3 +- mspec/spec/runner/formatters/unit_spec.rb | 3 +- mspec/spec/runner/formatters/yaml_spec.rb | 3 +- mspec/spec/runner/mspec_spec.rb | 110 ++++- mspec/spec/runner/shared_spec.rb | 68 ++- 28 files changed, 1363 insertions(+), 293 deletions(-) create mode 100644 mspec/0001-Updated-version-to-1.4.0.-See-below.patch create mode 100644 mspec/lib/mspec/matchers/equal_element.rb rewrite mspec/lib/mspec/runner/example.rb (66%) create mode 100644 mspec/mspec-1.4.0.gemspec create mode 100644 mspec/spec/matchers/equal_element_spec.rb rewrite mspec/spec/runner/shared_spec.rb (86%) diff --git a/mspec/0001-Updated-version-to-1.4.0.-See-below.patch b/mspec/0001-Updated-version-to-1.4.0.-See-below.patch new file mode 100644 index 000000000..ec9efcdc6 --- /dev/null +++ b/mspec/0001-Updated-version-to-1.4.0.-See-below.patch @@ -0,0 +1,217 @@ +From f3ec07ad7022d69e87addbc8a273ff720d8e0165 Mon Sep 17 00:00:00 2001 +From: Brian Ford +Date: Wed, 16 Jul 2008 17:16:39 -0700 +Subject: [PATCH] Updated version to 1.4.0. See below. + +This version significantly breaks compatibility with older versions +in two areas: RSpec-style shared specs and nested 'describe' blocks. +A concurrent version guard has been placed in the RubySpec repo +(spec_helper.rb) to prevent running the updated RubySpecs with an +older version of MSpec. + +See the README for information on shared and nested 'describe' blocks. +--- + README | 93 ++++++++++++++++++++++++++++++++++++++++++++++++++ + lib/mspec/version.rb | 2 +- + mspec-1.3.1.gemspec | 33 ------------------ + mspec-1.4.0.gemspec | 33 ++++++++++++++++++ + 4 files changed, 127 insertions(+), 34 deletions(-) + delete mode 100644 mspec-1.3.1.gemspec + create mode 100644 mspec-1.4.0.gemspec + +diff --git a/README b/README +index 1429ac0..5400b08 100644 +--- a/README ++++ b/README +@@ -91,6 +91,99 @@ All of these should be applied to a block created with `lambda` or `proc`: + is associated with. The exception class can be given for finer-grained + control (inheritance works normally so Exception would catch everything.) + ++== Nested 'describe' blocks ++ ++MSpec supports nesting one 'describe' block inside another. The examples in ++the nested block are evaluated with all the before/after blocks of all the ++containing 'describe' blocks. The following example illustrates this: ++ ++describe "Some#method" do ++ before :each do ++ @obj = 1 ++ end ++ ++ describe "when passed String" do ++ before :each do ++ @meth = :to_s ++ end ++ ++ it "returns false" do ++ # when this example is evaluated, @obj = 1 and @meth = :to_s ++ end ++ end ++end ++ ++The output when using the SpecdocFormatter (selected with -fs to the runners) ++will be as follows: ++ ++Some#method when passed String ++- returns false ++ ++ ++== Shared 'describe' blocks ++ ++MSpec supports RSpec-style shared 'describe' blocks. MSpec also provides a ++convenience method to assist in writing specs for the numerous aliased methods ++that Ruby provides. The following example illustrates shared blocks: ++ ++describe :someclass_some_method, :shared => true do ++ it "does something" do ++ end ++end ++ ++describe "SomeClass#some_method" do ++ it_should_behave_like "someclass_some_method" ++end ++ ++The first argument to 'describe' for a shared block is an object that ++duck-types as a String. The representation of the object must be unique. This ++example uses a symbol. This was the convention for the previous facility that ++MSpec provided for aliased method (#it_behaves_like). However, this convention ++is not set in stone (but the uniqueness requirement is). Note that the ++argument to the #it_should_behave_like is a String because at this time RSpec ++will not find the shared block by the symbol. ++ ++MSpec continues to support the #it_behaves_like convenience method for ++specifying aliased methods. The syntax is as follows: ++ ++it_behaves_like :symbol_matching_shared_describe, :method [, :object] ++ ++describe :someclass_some_method, :shared => true do ++ it "returns true" do ++ obj.send(@method).should be_true ++ end ++ ++ it "returns something else" do ++ @object.send(@method).should be_something_else ++ end ++end ++ ++# example #1 ++describe "SomeClass#some_method" do ++ it_behaves_like :someclass_some_method, :other_method ++end ++ ++# example #2 ++describe "SomeOtherClass#some_method" do ++ it_behaves_like :someclass_some_method, :some_method, OtherClass ++end ++ ++The first form above (#1) is used for typical aliases. That is, methods with ++different names on the same class that behave identically. The ++#it_behaves_like helper creates a before(:all) block that sets @method to ++:other_method. The form of the first example block in the shared block ++illustrates the typical form of a spec for an aliased method. ++ ++The second form above (#2) is used for methods on different classes that are ++essentially aliases, even though Ruby does not provide a syntax for specifying ++such methods as aliases. Examples are the methods on File, FileTest, and ++File::Stat. In this case, the #it_behaves_like helper sets both @method and ++@object in the before(:all) block (@method = :some_method, @object = ++OtherClass in this example). ++ ++For shared specs that fall outside of either of these two narrow categories, ++use nested or shared 'describe' blocks as appropriate and use the ++#it_should_behave_like method directly. + + == Guards + +diff --git a/lib/mspec/version.rb b/lib/mspec/version.rb +index 0a75f11..6a7efc6 100644 +--- a/lib/mspec/version.rb ++++ b/lib/mspec/version.rb +@@ -1,3 +1,3 @@ + module MSpec +- VERSION = '1.3.1' ++ VERSION = '1.4.0' + end +diff --git a/mspec-1.3.1.gemspec b/mspec-1.3.1.gemspec +deleted file mode 100644 +index 5bf371c..0000000 +--- a/mspec-1.3.1.gemspec ++++ /dev/null +@@ -1,33 +0,0 @@ +-Gem::Specification.new do |s| +- s.name = %q{mspec} +- s.version = "1.3.1" +- +- s.specification_version = 2 if s.respond_to? :specification_version= +- +- s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= +- s.authors = ["Brian Ford"] +- s.date = %q{2008-05-21} +- s.email = %q{bford@engineyard.com} +- s.has_rdoc = true +- s.extra_rdoc_files = %w[ README LICENSE ] +- s.executables = ["mkspec", "mspec", "mspec-ci", "mspec-run", "mspec-tag"] +- s.files = FileList[ '{bin,lib,spec}/**/*.{yaml,txt,rb}', 'Rakefile', *s.extra_rdoc_files ] +- s.homepage = %q{http://rubyspec.org} +- s.rubyforge_project = 'http://rubyforge.org/projects/mspec' +- s.require_paths = ["lib"] +- s.rubygems_version = %q{1.1.1} +- s.summary = <= 0") if s.respond_to? :required_rubygems_version= ++ s.authors = ["Brian Ford"] ++ s.date = %q{2008-05-21} ++ s.email = %q{bford@engineyard.com} ++ s.has_rdoc = true ++ s.extra_rdoc_files = %w[ README LICENSE ] ++ s.executables = ["mkspec", "mspec", "mspec-ci", "mspec-run", "mspec-tag"] ++ s.files = FileList[ '{bin,lib,spec}/**/*.{yaml,txt,rb}', 'Rakefile', *s.extra_rdoc_files ] ++ s.homepage = %q{http://rubyspec.org} ++ s.rubyforge_project = 'http://rubyforge.org/projects/mspec' ++ s.require_paths = ["lib"] ++ s.rubygems_version = %q{1.1.1} ++ s.summary = < true do + it "does something" do + end +end + +describe "SomeClass#some_method" do + it_should_behave_like "someclass_some_method" +end + +The first argument to 'describe' for a shared block is an object that +duck-types as a String. The representation of the object must be unique. This +example uses a symbol. This was the convention for the previous facility that +MSpec provided for aliased method (#it_behaves_like). However, this convention +is not set in stone (but the uniqueness requirement is). Note that the +argument to the #it_should_behave_like is a String because at this time RSpec +will not find the shared block by the symbol. + +MSpec continues to support the #it_behaves_like convenience method for +specifying aliased methods. The syntax is as follows: + +it_behaves_like :symbol_matching_shared_describe, :method [, :object] + +describe :someclass_some_method, :shared => true do + it "returns true" do + obj.send(@method).should be_true + end + + it "returns something else" do + @object.send(@method).should be_something_else + end +end + +# example #1 +describe "SomeClass#some_method" do + it_behaves_like :someclass_some_method, :other_method +end + +# example #2 +describe "SomeOtherClass#some_method" do + it_behaves_like :someclass_some_method, :some_method, OtherClass +end + +The first form above (#1) is used for typical aliases. That is, methods with +different names on the same class that behave identically. The +#it_behaves_like helper creates a before(:all) block that sets @method to +:other_method. The form of the first example block in the shared block +illustrates the typical form of a spec for an aliased method. + +The second form above (#2) is used for methods on different classes that are +essentially aliases, even though Ruby does not provide a syntax for specifying +such methods as aliases. Examples are the methods on File, FileTest, and +File::Stat. In this case, the #it_behaves_like helper sets both @method and +@object in the before(:all) block (@method = :some_method, @object = +OtherClass in this example). + +For shared specs that fall outside of either of these two narrow categories, +use nested or shared 'describe' blocks as appropriate and use the +#it_should_behave_like method directly. == Guards diff --git a/mspec/lib/mspec/commands/mspec-ci.rb b/mspec/lib/mspec/commands/mspec-ci.rb index d9a8d402a..051d347d7 100644 --- a/mspec/lib/mspec/commands/mspec-ci.rb +++ b/mspec/lib/mspec/commands/mspec-ci.rb @@ -61,8 +61,10 @@ class MSpecCI < MSpecScript MSpec.register_tags_patterns config[:tags_patterns] MSpec.register_files files TagFilter.new(:exclude, "fails").register + TagFilter.new(:exclude, "critical").register TagFilter.new(:exclude, "unstable").register TagFilter.new(:exclude, "incomplete").register + TagFilter.new(:exclude, "unsupported").register MSpec.process exit MSpec.exit_code diff --git a/mspec/lib/mspec/matchers.rb b/mspec/lib/mspec/matchers.rb index 4a336ba01..0c227ca8a 100644 --- a/mspec/lib/mspec/matchers.rb +++ b/mspec/lib/mspec/matchers.rb @@ -6,12 +6,13 @@ require 'mspec/matchers/be_false' require 'mspec/matchers/be_kind_of' require 'mspec/matchers/be_nil' require 'mspec/matchers/be_true' -require 'mspec/matchers/equal' +require 'mspec/matchers/complain' require 'mspec/matchers/eql' +require 'mspec/matchers/equal' +require 'mspec/matchers/equal_element' +require 'mspec/matchers/equal_utf16' require 'mspec/matchers/include' require 'mspec/matchers/match_yaml' require 'mspec/matchers/raise_error' require 'mspec/matchers/output' require 'mspec/matchers/output_to_fd' -require 'mspec/matchers/complain' -require 'mspec/matchers/equal_utf16' diff --git a/mspec/lib/mspec/matchers/equal_element.rb b/mspec/lib/mspec/matchers/equal_element.rb new file mode 100644 index 000000000..45559a74d --- /dev/null +++ b/mspec/lib/mspec/matchers/equal_element.rb @@ -0,0 +1,78 @@ +class EqualElementMatcher + def initialize(element, attributes = nil, content = nil, options = {}) + @element = element + @attributes = attributes + @content = content + @options = options + end + + def matches?(actual) + @actual = actual + + matched = true + + if @options[:not_closed] + matched &&= actual =~ /^#{Regexp.quote("<" + @element)}.*#{Regexp.quote(">" + (@content || ''))}$/ + else + matched &&= actual =~ /^#{Regexp.quote("<" + @element)}/ + matched &&= actual =~ /#{Regexp.quote("")}$/ + matched &&= actual =~ /#{Regexp.quote(">" + @content + ")/).size == 1) + else + matched &&= (actual.scan(%Q{ #{key}="#{value}"}).size == 1) + end + end + end + end + + !!matched + end + + def failure_message + ["Expected #{@actual.pretty_inspect}", + "to be a '#{@element}' element with #{attributes_for_failure_message} and #{content_for_failure_message}"] + end + + def negative_failure_message + ["Expected #{@actual.pretty_inspect}", + "not to be a '#{@element}' element with #{attributes_for_failure_message} and #{content_for_failure_message}"] + end + + def attributes_for_failure_message + if @attributes + if @attributes.empty? + "no attributes" + else + @attributes.inject([]) { |memo, n| memo << %Q{#{n[0]}="#{n[1]}"} }.join(" ") + end + else + "any attributes" + end + end + + def content_for_failure_message + if @content + if @content.empty? + "no content" + else + "#{@content.inspect} as content" + end + else + "any content" + end + end +end + +class Object + def equal_element(*args) + EqualElementMatcher.new(*args) + end +end \ No newline at end of file diff --git a/mspec/lib/mspec/runner/context.rb b/mspec/lib/mspec/runner/context.rb index 4f021f976..a0a469bcf 100644 --- a/mspec/lib/mspec/runner/context.rb +++ b/mspec/lib/mspec/runner/context.rb @@ -13,84 +13,170 @@ require 'mspec/runner/example' # is evaluated, just as +it+ refers to the example itself. #++ class ContextState - attr_reader :state - - def initialize - @start = [] - @before = [] - @after = [] - @finish = [] - @spec = [] + attr_reader :state, :parent, :parents, :children, :examples, :to_s + + def initialize(mod, options=nil) + @to_s = mod.to_s + if options.is_a? Hash + @options = options + else + @to_s += "#{".:#".include?(options[0,1]) ? "" : " "}#{options}" if options + @options = { } + end + @options[:shared] ||= false + + @parsed = false + @before = { :all => [], :each => [] } + @after = { :all => [], :each => [] } + @pre = {} + @post = {} + @examples = [] + @parent = nil + @parents = [self] + @children = [] + @mock_verify = lambda { Mock.verify_count } @mock_cleanup = lambda { Mock.cleanup } @expectation_missing = lambda { raise ExpectationNotFoundError } end - def before(at=:each, &block) - case at - when :each - @before << block - when :all - @start << block - end + # Returns true if this is a shared +ContextState+. Essentially, when + # created with: describe "Something", :shared => true { ... } + def shared? + return @options[:shared] end - def after(at=:each, &block) - case at - when :each - @after << block - when :all - @finish << block + # Set the parent (enclosing) +ContextState+ for this state. Creates + # the +parents+ list. + def parent=(parent) + @description = nil + @parent = parent + parent.child self if parent and not shared? + + state = parent + while state + parents.unshift state + state = state.parent end end + # Add the ContextState instance +child+ to the list of nested + # describe blocks. + def child(child) + @children << child + end + + # Returns a list of all before(+what+) blocks from self and any parents. + def pre(what) + @pre[what] ||= parents.inject([]) { |l, s| l.push(*s.before(what)) } + end + + # Returns a list of all after(+what+) blocks from self and any parents. + # The list is in reverse order. In other words, the blocks defined in + # inner describes are in the list before those defined in outer describes, + # and in a particular describe block those defined later are in the list + # before those defined earlier. + def post(what) + @post[what] ||= parents.inject([]) { |l, s| l.unshift(*s.after(what)) } + end + + # Records before(:each) and before(:all) blocks. + def before(what, &block) + block ? @before[what].push(block) : @before[what] + end + + # Records after(:each) and after(:all) blocks. + def after(what, &block) + block ? @after[what].unshift(block) : @after[what] + end + + # Creates an ExampleState instance for the block and stores it + # in a list of examples to evaluate unless the example is filtered. def it(desc, &block) - state = ExampleState.new @describe, desc - @spec << [desc, block, state] unless state.filtered? + @examples << ExampleState.new(self, desc, block) + end + + # Evaluates the block and resets the toplevel +ContextState+ to #parent. + def describe(&block) + @parsed = protect @to_s, block, false + MSpec.register_current parent + MSpec.register_shared self if shared? + end + + # Returns a description string generated from self and all parents + def description + @description ||= parents.inject([]) { |l,s| l << s.to_s }.join(" ") end - def describe(mod, desc=nil, &block) - @describe = desc ? "#{mod} #{desc}" : mod.to_s - @block = block + # Injects the before/after blocks and examples from the shared + # describe block into this +ContextState+ instance. + def it_should_behave_like(desc) + unless state = MSpec.retrieve_shared(desc) + raise Exception, "Unable to find shared 'describe' for #{desc}" + end + + state.examples.each { |ex| ex.context = self; @examples << ex } + state.before(:all).each { |b| before :all, &b } + state.before(:each).each { |b| before :each, &b } + state.after(:each).each { |b| after :each, &b } + state.after(:all).each { |b| after :all, &b } end + # Evaluates each block in +blocks+ using the +MSpec.protect+ method + # so that exceptions are handled and tallied. Returns true and does + # NOT evaluate any blocks if +check+ is true and +MSpec.pretend_mode?+ + # is true. def protect(what, blocks, check=true) return true if check and MSpec.pretend_mode? Array(blocks).all? { |block| MSpec.protect what, &block } end + # Removes filtered examples. Returns true if there are examples + # left to evaluate. + def filter_examples + @examples.reject! { |ex| ex.filtered? } + not @examples.empty? + end + + # Evaluates the examples in a +ContextState+. Invokes the MSpec events + # for :enter, :before, :after, :leave. def process - protect @describe, @block, false - return unless @spec.any? { |desc, spec, state| state.unfiltered? } - - MSpec.shuffle @spec if MSpec.randomize? - MSpec.actions :enter, @describe - - if protect "before :all", @start - @spec.each do |desc, spec, state| - @state = state - MSpec.actions :before, state - - if protect("before :each", @before) - MSpec.clear_expectations - passed = protect nil, spec - if spec - MSpec.actions :example, state, spec - protect nil, @expectation_missing unless MSpec.expectation? or not passed + MSpec.register_current self + + if @parsed and filter_examples + MSpec.shuffle @examples if MSpec.randomize? + MSpec.actions :enter, description + + if protect "before :all", pre(:all) + @examples.each do |state| + @state = state + example = state.example + MSpec.actions :before, state + + if protect "before :each", pre(:each) + MSpec.clear_expectations + if example + passed = protect nil, example + MSpec.actions :example, state, example + protect nil, @expectation_missing unless MSpec.expectation? or not passed + end + protect "after :each", post(:each) + protect "Mock.verify_count", @mock_verify end - protect "after :each", @after - protect "Mock.verify_count", @mock_verify - end + protect "Mock.cleanup", @mock_cleanup + MSpec.actions :after, state + @state = nil + end + protect "after :all", post(:all) + else protect "Mock.cleanup", @mock_cleanup - MSpec.actions :after, state - @state = nil end - protect "after :all", @finish - else - protect "Mock.cleanup", @mock_cleanup + + MSpec.actions :leave end - MSpec.actions :leave + MSpec.register_current nil + children.each { |child| child.process } end end diff --git a/mspec/lib/mspec/runner/example.rb b/mspec/lib/mspec/runner/example.rb dissimilarity index 66% index a88b1e576..1c1e4023c 100644 --- a/mspec/lib/mspec/runner/example.rb +++ b/mspec/lib/mspec/runner/example.rb @@ -1,37 +1,34 @@ -require 'mspec/runner/mspec' - -# Holds some of the state of the example (i.e. +it+ block) that is -# being evaluated. See also +ContextState+. -class ExampleState - def initialize(describe, it) - @describe = describe - @it = it - @unfiltered = nil - end - - def describe - @describe - end - - def it - @it - end - - def description - @description ||= "#{@describe} #{@it}" - end - - def unfiltered? - unless @unfiltered - incl = MSpec.retrieve(:include) || [] - excl = MSpec.retrieve(:exclude) || [] - @unfiltered = incl.empty? || incl.any? { |f| f === description } - @unfiltered &&= excl.empty? || !excl.any? { |f| f === description } - end - @unfiltered - end - - def filtered? - not unfiltered? - end -end +require 'mspec/runner/mspec' + +# Holds some of the state of the example (i.e. +it+ block) that is +# being evaluated. See also +ContextState+. +class ExampleState + attr_reader :context, :it, :example + + def initialize(context, it, example=nil) + @context = context + @it = it + @example = example + end + + def context=(context) + @description = nil + @context = context + end + + def describe + @context.description + end + + def description + @description ||= "#{describe} #{@it}" + end + + def filtered? + incl = MSpec.retrieve(:include) || [] + excl = MSpec.retrieve(:exclude) || [] + included = incl.empty? || incl.any? { |f| f === description } + included &&= excl.empty? || !excl.any? { |f| f === description } + not included + end +end diff --git a/mspec/lib/mspec/runner/mspec.rb b/mspec/lib/mspec/runner/mspec.rb index e2358cfb2..7c84427cd 100644 --- a/mspec/lib/mspec/runner/mspec.rb +++ b/mspec/lib/mspec/runner/mspec.rb @@ -18,18 +18,21 @@ module MSpec @mode = nil @load = nil @unload = nil + @current = nil + @shared = {} @exception = nil @randomize = nil @expectation = nil @expectations = false - def self.describe(mod, msg=nil, &block) - stack.push ContextState.new + def self.describe(mod, options=nil, &block) + state = ContextState.new mod, options + state.parent = current - current.describe(mod, msg, &block) - current.process + MSpec.register_current state + state.describe(&block) - stack.pop + state.process unless state.shared? or current end def self.process @@ -62,6 +65,8 @@ module MSpec begin @env.instance_eval(&block) return true + rescue SystemExit + raise rescue Exception => exc register_exit 1 actions :exception, ExceptionState.new(current && current.state, location, exc) @@ -69,14 +74,42 @@ module MSpec end end + # Sets the toplevel ContextState to +state+. + def self.register_current(state) + store :current, state + end + + # Sets the toplevel ContextState to +nil+. + def self.clear_current + store :current, nil + end + + # Returns the toplevel ContextState. + def self.current + retrieve :current + end + + # Stores the shared ContextState keyed by description. + def self.register_shared(state) + @shared[state.to_s] = state + end + + # Returns the shared ContextState matching description. + def self.retrieve_shared(desc) + @shared[desc.to_s] + end + + # Stores the exit code used by the runner scripts. def self.register_exit(code) store :exit, code end + # Retrieves the stored exit code. def self.exit_code retrieve(:exit).to_i end + # Stores the list of files to be evaluated. def self.register_files(files) store :files, files end @@ -141,14 +174,6 @@ module MSpec end end - def self.stack - @stack ||= [] - end - - def self.current - stack.last - end - def self.verify_mode? @mode == :verify end diff --git a/mspec/lib/mspec/runner/object.rb b/mspec/lib/mspec/runner/object.rb index 67bee2673..f96d030d6 100644 --- a/mspec/lib/mspec/runner/object.rb +++ b/mspec/lib/mspec/runner/object.rb @@ -7,7 +7,7 @@ class Object MSpec.current.after at, &block end - def describe(mod, msg=nil, &block) + def describe(mod, msg=nil, options=nil, &block) MSpec.describe mod, msg, &block end @@ -15,6 +15,10 @@ class Object MSpec.current.it msg, &block end + def it_should_behave_like(desc) + MSpec.current.it_should_behave_like desc + end + alias_method :context, :describe alias_method :specify, :it end diff --git a/mspec/lib/mspec/runner/shared.rb b/mspec/lib/mspec/runner/shared.rb index e3169e218..edd4a50ea 100644 --- a/mspec/lib/mspec/runner/shared.rb +++ b/mspec/lib/mspec/runner/shared.rb @@ -1,12 +1,12 @@ require 'mspec/runner/mspec' class Object - def shared(msg, &block) - MSpec.store msg.to_sym, block - end + def it_behaves_like(desc, meth, obj=nil) + send :before, :all do + @method = meth + @object = obj if obj + end - def it_behaves_like(behavior, *args) - p = MSpec.retrieve behavior.to_sym - p[*args] + send :it_should_behave_like, desc.to_s end end diff --git a/mspec/lib/mspec/version.rb b/mspec/lib/mspec/version.rb index 0a75f115b..6a7efc6a8 100644 --- a/mspec/lib/mspec/version.rb +++ b/mspec/lib/mspec/version.rb @@ -1,3 +1,3 @@ module MSpec - VERSION = '1.3.1' + VERSION = '1.4.0' end diff --git a/mspec/mspec-1.4.0.gemspec b/mspec/mspec-1.4.0.gemspec new file mode 100644 index 000000000..8d47f7c1c --- /dev/null +++ b/mspec/mspec-1.4.0.gemspec @@ -0,0 +1,33 @@ +Gem::Specification.new do |s| + s.name = %q{mspec} + s.version = "1.4.0" + + s.specification_version = 2 if s.respond_to? :specification_version= + + s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= + s.authors = ["Brian Ford"] + s.date = %q{2008-05-21} + s.email = %q{bford@engineyard.com} + s.has_rdoc = true + s.extra_rdoc_files = %w[ README LICENSE ] + s.executables = ["mkspec", "mspec", "mspec-ci", "mspec-run", "mspec-tag"] + s.files = FileList[ '{bin,lib,spec}/**/*.{yaml,txt,rb}', 'Rakefile', *s.extra_rdoc_files ] + s.homepage = %q{http://rubyspec.org} + s.rubyforge_project = 'http://rubyforge.org/projects/mspec' + s.require_paths = ["lib"] + s.rubygems_version = %q{1.1.1} + s.summary = <').should be_true + EqualElementMatcher.new("A").matches?('').should be_true + EqualElementMatcher.new("A").matches?('').should be_true + + EqualElementMatcher.new("BASE").matches?('').should be_false + EqualElementMatcher.new("BASE").matches?('').should be_false + EqualElementMatcher.new("BASE").matches?('').should be_false + EqualElementMatcher.new("BASE").matches?('').should be_false + EqualElementMatcher.new("BASE").matches?('').should be_false + end + + it "matches if it finds an element with the passed name and the passed attributes" do + EqualElementMatcher.new("A", {}).matches?('').should be_true + EqualElementMatcher.new("A", nil).matches?('').should be_true + EqualElementMatcher.new("A", "HREF" => "http://example.com").matches?('').should be_true + + EqualElementMatcher.new("A", {}).matches?('').should be_false + EqualElementMatcher.new("A", "HREF" => "http://example.com").matches?('').should be_false + EqualElementMatcher.new("A", "HREF" => "http://example.com").matches?('').should be_false + EqualElementMatcher.new("A", "HREF" => "http://example.com").matches?('').should be_false + end + + it "matches if it finds an element with the passed name, the passed attributes and the passed content" do + EqualElementMatcher.new("A", {}, "").matches?('').should be_true + EqualElementMatcher.new("A", {"HREF" => "http://example.com"}, "Example").matches?('Example').should be_true + + EqualElementMatcher.new("A", {}, "Test").matches?('').should be_false + EqualElementMatcher.new("A", {"HREF" => "http://example.com"}, "Example").matches?('').should be_false + EqualElementMatcher.new("A", {"HREF" => "http://example.com"}, "Example").matches?('Test').should be_false + end + + it "can match unclosed elements" do + EqualElementMatcher.new("BASE", nil, nil, :not_closed => true).matches?('').should be_true + EqualElementMatcher.new("BASE", {"HREF" => "http://example.com"}, nil, :not_closed => true).matches?('').should be_true + EqualElementMatcher.new("BASE", {"HREF" => "http://example.com"}, "Example", :not_closed => true).matches?('Example').should be_true + + EqualElementMatcher.new("BASE", {}, nil, :not_closed => true).matches?('').should be_false + EqualElementMatcher.new("BASE", {"HREF" => "http://example.com"}, "", :not_closed => true).matches?('Example').should be_false + EqualElementMatcher.new("BASE", {"HREF" => "http://example.com"}, "Test", :not_closed => true).matches?('Example').should be_false + end + + it "provides a useful failure message" do + equal_element = EqualElementMatcher.new("A", {}, "Test") + equal_element.matches?('').should be_false + equal_element.failure_message.should == [%{Expected ""\n}, %{to be a 'A' element with no attributes and "Test" as content}] + + equal_element = EqualElementMatcher.new("A", {}, "") + equal_element.matches?('Test').should be_false + equal_element.failure_message.should == [%{Expected "Test"\n}, %{to be a 'A' element with no attributes and no content}] + + equal_element = EqualElementMatcher.new("A", "HREF" => "http://www.example.com") + equal_element.matches?('Test').should be_false + equal_element.failure_message.should == [%{Expected "Test"\n}, %{to be a 'A' element with HREF="http://www.example.com" and any content}] + end + + it "provides a useful negative failure message" do + equal_element = EqualElementMatcher.new("A", {}, "Test") + equal_element.matches?('').should be_false + equal_element.negative_failure_message.should == [%{Expected ""\n}, %{not to be a 'A' element with no attributes and "Test" as content}] + + equal_element = EqualElementMatcher.new("A", {}, "") + equal_element.matches?('Test').should be_false + equal_element.negative_failure_message.should == [%{Expected "Test"\n}, %{not to be a 'A' element with no attributes and no content}] + + equal_element = EqualElementMatcher.new("A", "HREF" => "http://www.example.com") + equal_element.matches?('Test').should be_false + equal_element.negative_failure_message.should == [%{Expected "Test"\n}, %{not to be a 'A' element with HREF="http://www.example.com" and any content}] + end +end diff --git a/mspec/spec/runner/actions/debug_spec.rb b/mspec/spec/runner/actions/debug_spec.rb index 4e6f98a0e..d2c5e25d0 100644 --- a/mspec/spec/runner/actions/debug_spec.rb +++ b/mspec/spec/runner/actions/debug_spec.rb @@ -1,6 +1,7 @@ require File.dirname(__FILE__) + '/../../spec_helper' require 'mspec/runner/actions/debug' require 'mspec/runner/mspec' +require 'mspec/runner/context' require 'mspec/runner/example' describe DebugAction do @@ -18,7 +19,7 @@ end describe DebugAction, "#before" do before :each do MSpec.stub!(:read_tags).and_return([]) - @state = ExampleState.new "Catch#me", "if you can" + @state = ExampleState.new ContextState.new("Catch#me"), "if you can" end it "does not invoke the debugger if the description does not match" do diff --git a/mspec/spec/runner/actions/gdb_spec.rb b/mspec/spec/runner/actions/gdb_spec.rb index 4c9eccfdc..ccfdef745 100644 --- a/mspec/spec/runner/actions/gdb_spec.rb +++ b/mspec/spec/runner/actions/gdb_spec.rb @@ -18,7 +18,7 @@ end describe GdbAction, "#before" do before :each do MSpec.stub!(:read_tags).and_return([]) - @state = ExampleState.new "Catch#me", "if you can" + @state = ExampleState.new ContextState.new("Catch#me"), "if you can" end it "does not invoke the debugger if the description does not match" do diff --git a/mspec/spec/runner/actions/tag_spec.rb b/mspec/spec/runner/actions/tag_spec.rb index 1d74ae942..946ac2551 100644 --- a/mspec/spec/runner/actions/tag_spec.rb +++ b/mspec/spec/runner/actions/tag_spec.rb @@ -94,7 +94,7 @@ describe TagAction, "#before" do action.exception?.should be_false action.exception ExceptionState.new(nil, nil, Exception.new("Fail!")) action.exception?.should be_true - action.before(ExampleState.new("describe", "it")) + action.before(ExampleState.new(ContextState.new("describe"), "it")) action.exception?.should be_false end end @@ -111,7 +111,8 @@ end describe TagAction, "#after when action is :add" do before :each do MSpec.stub!(:read_tags).and_return([]) - @state = ExampleState.new "Catch#me", "if you can" + context = ContextState.new "Catch#me" + @state = ExampleState.new context, "if you can" @tag = SpecTag.new "tag(comment):Catch#me if you can" SpecTag.stub!(:new).and_return(@tag) @exception = ExceptionState.new nil, nil, Exception.new("failed") @@ -159,7 +160,8 @@ end describe TagAction, "#after when action is :del" do before :each do MSpec.stub!(:read_tags).and_return([]) - @state = ExampleState.new "Catch#me", "if you can" + context = ContextState.new "Catch#me" + @state = ExampleState.new context, "if you can" @tag = SpecTag.new "tag(comment):Catch#me if you can" SpecTag.stub!(:new).and_return(@tag) @exception = ExceptionState.new nil, nil, Exception.new("failed") @@ -207,7 +209,8 @@ end describe TagAction, "#finish" do before :each do $stdout = @out = IOStub.new - @state = ExampleState.new "Catch#me", "if you can" + context = ContextState.new "Catch#me" + @state = ExampleState.new context, "if you can" MSpec.stub!(:write_tag).and_return(true) MSpec.stub!(:delete_tag).and_return(true) end diff --git a/mspec/spec/runner/context_spec.rb b/mspec/spec/runner/context_spec.rb index cc25e9176..bfb2eb6f6 100644 --- a/mspec/spec/runner/context_spec.rb +++ b/mspec/spec/runner/context_spec.rb @@ -5,43 +5,198 @@ require 'mspec/runner/mspec' require 'mspec/mocks/mock' require 'mspec/runner/context' -describe ContextState do +describe ContextState, "#describe" do before :each do - @state = ContextState.new + @state = ContextState.new "C#m" + @proc = lambda { ScratchPad.record :a } + ScratchPad.clear + end + + it "evaluates the passed block" do + @state.describe(&@proc) + ScratchPad.recorded.should == :a + end + + it "evaluates the passed block via #protect" do + @state.should_receive(:protect).with("C#m", @proc, false) + @state.describe(&@proc) + end + + it "registers #parent as the current MSpec ContextState" do + parent = ContextState.new "" + @state.parent = parent + MSpec.should_receive(:register_current).with(parent) + @state.describe { } + end + + it "registers self with MSpec when #shared? is true" do + state = ContextState.new "something shared", :shared => true + MSpec.should_receive(:register_shared).with(state) + state.describe { } + end +end + +describe ContextState, "#shared?" do + it "returns false when the ContextState is not shared" do + ContextState.new("").shared?.should be_false + end + + it "returns true when the ContextState is shared" do + ContextState.new("", {:shared => true}).shared?.should be_true + end +end + +describe ContextState, "#to_s" do + it "returns a description string for self when passed a Module" do + ContextState.new(Object).to_s.should == "Object" + end + + it "returns a description string for self when passed a String" do + ContextState.new("SomeClass").to_s.should == "SomeClass" + end + + it "returns a description string for self when passed a Module, String" do + ContextState.new(Object, "when empty").to_s.should == "Object when empty" + end + + it "returns a description string for self when passed a Module and String beginning with '#'" do + ContextState.new(Object, "#to_s").to_s.should == "Object#to_s" + end + + it "returns a description string for self when passed a Module and String beginning with '.'" do + ContextState.new(Object, ".to_s").to_s.should == "Object.to_s" + end + + it "returns a description string for self when passed a Module and String beginning with '::'" do + ContextState.new(Object, "::to_s").to_s.should == "Object::to_s" + end +end + +describe ContextState, "#description" do + before :each do + @state = ContextState.new "when empty" + @parent = ContextState.new "Toplevel" + end + + it "returns a composite description string from self and all parents" do + @parent.description.should == "Toplevel" + @state.description.should == "when empty" + @state.parent = @parent + @state.description.should == "Toplevel when empty" + end +end + +describe ContextState, "#it" do + before :each do + @state = ContextState.new "" @proc = lambda { } end - it "records before(:all) blocks" do - @state.before(:all, &@proc) - @state.instance_variable_get(:@start).should == [@proc] + it "creates an ExampleState instance for the block" do + ex = ExampleState.new("", "", &@proc) + ExampleState.should_receive(:new).with(@state, "it", @proc).and_return(ex) + @state.describe(&@proc) + @state.it("it", &@proc) + end +end + +describe ContextState, "#examples" do + before :each do + @state = ContextState.new "" + end + + it "returns a list of all examples in this ContextState" do + @state.it("first") { } + @state.it("second") { } + @state.examples.size.should == 2 + end +end + +describe ContextState, "#before" do + before :each do + @state = ContextState.new "" + @proc = lambda { } end - it "records before(:each) blocks" do + it "records the block for :each" do @state.before(:each, &@proc) - @state.instance_variable_get(:@before).should == [@proc] + @state.before(:each).should == [@proc] end - it "records after(:all) blocks" do - @state.after(:all, &@proc) - @state.instance_variable_get(:@finish).should == [@proc] + it "records the block for :all" do + @state.before(:all, &@proc) + @state.before(:all).should == [@proc] + end +end + +describe ContextState, "#after" do + before :each do + @state = ContextState.new "" + @proc = lambda { } end - it "records after(:each) blocks" do + it "records the block for :each" do @state.after(:each, &@proc) - @state.instance_variable_get(:@after).should == [@proc] + @state.after(:each).should == [@proc] + end + + it "records the block for :all" do + @state.after(:all, &@proc) + @state.after(:all).should == [@proc] + end +end + +describe ContextState, "#pre" do + before :each do + @a = lambda { } + @b = lambda { } + @c = lambda { } + + parent = ContextState.new "" + parent.before(:each, &@c) + parent.before(:all, &@c) + + @state = ContextState.new "" + @state.parent = parent + end + + it "returns before(:each) actions in the order they were defined" do + @state.before(:each, &@a) + @state.before(:each, &@b) + @state.pre(:each).should == [@c, @a, @b] + end + + it "returns before(:all) actions in the order they were defined" do + @state.before(:all, &@a) + @state.before(:all, &@b) + @state.pre(:all).should == [@c, @a, @b] + end +end + +describe ContextState, "#post" do + before :each do + @a = lambda { } + @b = lambda { } + @c = lambda { } + + parent = ContextState.new "" + parent.after(:each, &@c) + parent.after(:all, &@c) + + @state = ContextState.new "" + @state.parent = parent end - it "records it blocks" do - @state.it("message", &@proc) - msg, proc = @state.instance_variable_get(:@spec)[0] - msg.should == "message" - proc.should == @proc + it "returns after(:each) actions in the reverse order they were defined" do + @state.after(:each, &@a) + @state.after(:each, &@b) + @state.post(:each).should == [@b, @a, @c] end - it "records describe blocks" do - @state.describe(Object, "message", &@proc) - @state.instance_variable_get(:@describe).should == "Object message" - @state.instance_variable_get(:@block).should == @proc + it "returns after(:all) actions in the reverse order they were defined" do + @state.after(:all, &@a) + @state.after(:all, &@b) + @state.post(:all).should == [@b, @a, @c] end end @@ -55,27 +210,116 @@ describe ContextState, "#protect" do it "returns true and does execute any blocks if check is true and MSpec.pretend_mode? is true" do MSpec.stub!(:pretend_mode?).and_return(true) - ContextState.new.protect("message", [@a, @b]).should be_true + ContextState.new("").protect("message", [@a, @b]).should be_true ScratchPad.recorded.should == [] end it "executes the blocks if MSpec.pretend_mode? is false" do MSpec.stub!(:pretend_mode?).and_return(false) - ContextState.new.protect("message", [@a, @b]) + ContextState.new("").protect("message", [@a, @b]) ScratchPad.recorded.should == [:a, :b] end it "executes the blocks if check is false" do - ContextState.new.protect("message", [@a, @b], false) + ContextState.new("").protect("message", [@a, @b], false) ScratchPad.recorded.should == [:a, :b] end it "returns true if none of the blocks raise an exception" do - ContextState.new.protect("message", [@a, @b]).should be_true + ContextState.new("").protect("message", [@a, @b]).should be_true end it "returns false if any of the blocks raise an exception" do - ContextState.new.protect("message", [@a, @c, @b]).should be_false + ContextState.new("").protect("message", [@a, @c, @b]).should be_false + end +end + +describe ContextState, "#parent=" do + before :each do + @state = ContextState.new "" + @parent = mock("describe") + @parent.stub!(:parent).and_return(nil) + @parent.stub!(:child) + end + + it "does not set self as a child of parent if shared" do + @parent.should_not_receive(:child) + state = ContextState.new "", :shared => true + state.parent = @parent + end + + it "sets self as a child of parent" do + @parent.should_receive(:child).with(@state) + @state.parent = @parent + end + + it "creates the list of parents" do + @state.parent = @parent + @state.parents.should == [@parent, @state] + end +end + +describe ContextState, "#parent" do + before :each do + @state = ContextState.new "" + @parent = mock("describe") + @parent.stub!(:parent).and_return(nil) + @parent.stub!(:child) + end + + it "returns nil if parent has not been set" do + @state.parent.should be_nil + end + + it "returns the parent" do + @state.parent = @parent + @state.parent.should == @parent + end +end + +describe ContextState, "#parents" do + before :each do + @first = ContextState.new "" + @second = ContextState.new "" + @parent = mock("describe") + @parent.stub!(:parent).and_return(nil) + @parent.stub!(:child) + end + + it "returns a list of all enclosing ContextState instances" do + @first.parent = @parent + @second.parent = @first + @second.parents.should == [@parent, @first, @second] + end +end + +describe ContextState, "#child" do + before :each do + @first = ContextState.new "" + @second = ContextState.new "" + @parent = mock("describe") + @parent.stub!(:parent).and_return(nil) + @parent.stub!(:child) + end + + it "adds the ContextState to the list of contained ContextStates" do + @first.child @second + @first.children.should == [@second] + end +end + +describe ContextState, "#children" do + before :each do + @parent = ContextState.new "" + @first = ContextState.new "" + @second = ContextState.new "" + end + + it "returns the list of directly contained ContextStates" do + @first.parent = @parent + @second.parent = @first + @parent.children.should == [@first] + @first.children.should == [@second] end end @@ -84,7 +328,7 @@ describe ContextState, "#state" do MSpec.store :before, [] MSpec.store :after, [] - @state = ContextState.new + @state = ContextState.new "" end it "returns nil if no spec is being executed" do @@ -93,7 +337,7 @@ describe ContextState, "#state" do it "returns a ExampleState instance if an example is being executed" do ScratchPad.record @state - @state.describe("") { } + @state.describe { } @state.it("") { ScratchPad.record ScratchPad.recorded.state } @state.process @state.state.should == nil @@ -105,9 +349,10 @@ describe ContextState, "#process" do before :each do MSpec.store :before, [] MSpec.store :after, [] + MSpec.stub!(:register_current) - @state = ContextState.new - @state.describe("") { } + @state = ContextState.new "" + @state.describe { } @a = lambda { ScratchPad << :a } @b = lambda { ScratchPad << :b } @@ -127,7 +372,7 @@ describe ContextState, "#process" do @state.after(:all, &@b) @state.it("") { } @state.process - ScratchPad.recorded.should == [:a, :b] + ScratchPad.recorded.should == [:b, :a] end it "calls each it block" do @@ -137,6 +382,14 @@ describe ContextState, "#process" do ScratchPad.recorded.should == [:a, :b] end + it "does not call the #it block if #filtered? returns true" do + @state.it("one", &@a) + @state.it("two", &@b) + @state.examples.first.stub!(:filtered?).and_return(true) + @state.process + ScratchPad.recorded.should == [:b] + end + it "calls each before(:each) block" do @state.before(:each, &@a) @state.before(:each, &@b) @@ -150,7 +403,7 @@ describe ContextState, "#process" do @state.after(:each, &@b) @state.it("") { } @state.process - ScratchPad.recorded.should == [:a, :b] + ScratchPad.recorded.should == [:b, :a] end it "calls Mock.cleanup for each it block" do @@ -169,14 +422,14 @@ describe ContextState, "#process" do it "calls the describe block" do ScratchPad.record [] - @state.describe(Object, "msg") { ScratchPad << :a } + @state.describe { ScratchPad << :a } @state.process ScratchPad.recorded.should == [:a] end it "creates a new ExampleState instance for each example" do ScratchPad.record @state - @state.describe("desc") { } + @state.describe { } @state.it("it") { ScratchPad.record ScratchPad.recorded.state } @state.process ScratchPad.recorded.should be_kind_of(ExampleState) @@ -197,14 +450,45 @@ describe ContextState, "#process" do @state.process MSpec.randomize false end + + it "sets the current MSpec ContextState" do + MSpec.should_receive(:register_current).with(@state) + @state.process + end + + it "resets the current MSpec ContextState to nil when there are examples" do + MSpec.should_receive(:register_current).with(nil) + @state.it("") { } + @state.process + end + + it "resets the current MSpec ContextState to nil when there are no examples" do + MSpec.should_receive(:register_current).with(nil) + @state.process + end + + it "call #process on children when there are examples" do + child = ContextState.new "" + child.should_receive(:process) + @state.child child + @state.it("") { } + @state.process + end + + it "call #process on children when there are no examples" do + child = ContextState.new "" + child.should_receive(:process) + @state.child child + @state.process + end end describe ContextState, "#process" do before :each do MSpec.store :exception, [] - @state = ContextState.new - @state.describe("") { } + @state = ContextState.new "" + @state.describe { } action = mock("action") def action.exception(exc) @@ -243,8 +527,8 @@ describe ContextState, "#process" do before :each do MSpec.store :example, [] - @state = ContextState.new - @state.describe("") { } + @state = ContextState.new "" + @state.describe { } example = mock("example") def example.example(state, spec) @@ -279,8 +563,8 @@ describe ContextState, "#process" do MSpec.store :before, [] MSpec.store :after, [] - @state = ContextState.new - @state.describe("") { } + @state = ContextState.new "" + @state.describe { } @state.it("") { MSpec.expectation } end @@ -315,15 +599,12 @@ describe ContextState, "#process" do end describe ContextState, "#process" do -end - -describe ContextState, "#process" do before :each do MSpec.store :enter, [] MSpec.store :leave, [] - @state = ContextState.new - @state.describe("") { } + @state = ContextState.new "C#m" + @state.describe { } @state.it("") { MSpec.expectation } end @@ -334,7 +615,7 @@ describe ContextState, "#process" do it "calls registered enter actions with the current #describe string" do enter = mock("enter") - enter.should_receive(:enter).and_return { ScratchPad.record :enter } + enter.should_receive(:enter).with("C#m").and_return { ScratchPad.record :enter } MSpec.register :enter, enter @state.process ScratchPad.recorded.should == :enter @@ -354,8 +635,8 @@ describe ContextState, "#process when an exception is raised in before(:all)" do MSpec.store :before, [] MSpec.store :after, [] - @state = ContextState.new - @state.describe("") { } + @state = ContextState.new "" + @state.describe { } @a = lambda { ScratchPad << :a } @b = lambda { ScratchPad << :b } @@ -414,8 +695,8 @@ describe ContextState, "#process when an exception is raised in before(:each)" d MSpec.store :before, [] MSpec.store :after, [] - @state = ContextState.new - @state.describe("") { } + @state = ContextState.new "" + @state.describe { } @a = lambda { ScratchPad << :a } @b = lambda { ScratchPad << :b } @@ -463,8 +744,8 @@ describe ContextState, "#process in pretend mode" do MSpec.store :before, [] MSpec.store :after, [] - @state = ContextState.new - @state.describe("") { } + @state = ContextState.new "" + @state.describe { } @state.it("") { } end @@ -511,8 +792,8 @@ describe ContextState, "#process in pretend mode" do MSpec.store :before, [] MSpec.store :after, [] - @state = ContextState.new - @state.describe("") { } + @state = ContextState.new "" + @state.describe { } @a = lambda { ScratchPad << :a } @b = lambda { ScratchPad << :b } @@ -521,7 +802,7 @@ describe ContextState, "#process in pretend mode" do it "calls the describe block" do ScratchPad.record [] - @state.describe(Object, "msg") { ScratchPad << :a } + @state.describe { ScratchPad << :a } @state.process ScratchPad.recorded.should == [:a] end @@ -586,8 +867,8 @@ describe ContextState, "#process in pretend mode" do MSpec.store :enter, [] MSpec.store :leave, [] - @state = ContextState.new - @state.describe("") { } + @state = ContextState.new "" + @state.describe { } @state.it("") { } end @@ -612,3 +893,87 @@ describe ContextState, "#process in pretend mode" do ScratchPad.recorded.should == :leave end end + +describe ContextState, "#it_should_behave_like" do + before :each do + @shared = ContextState.new("", :shared => true) + MSpec.stub!(:retrieve_shared).and_return(@shared) + + @state = ContextState.new "" + @a = lambda { } + @b = lambda { } + end + + it "raises an Exception if unable to find the shared ContextState" do + MSpec.should_receive(:retrieve_shared).and_return(nil) + lambda { @state.it_should_behave_like "this" }.should raise_error(Exception) + end + + it "adds examples from the shared ContextState" do + @shared.it "some", &@a + @shared.it "thing", &@b + @state.it_should_behave_like "" + @state.examples.should include(*@shared.examples) + end + + it "sets the containing ContextState for the examples" do + @shared.it "some", &@a + @shared.it "thing", &@b + @shared.examples.each { |ex| ex.should_receive(:context=).with(@state) } + @state.it_should_behave_like "" + end + + it "adds before(:all) blocks from the shared ContextState" do + @shared.before :all, &@a + @shared.before :all, &@b + @state.it_should_behave_like "" + @state.before(:all).should include(*@shared.before(:all)) + end + + it "adds before(:each) blocks from the shared ContextState" do + @shared.before :each, &@a + @shared.before :each, &@b + @state.it_should_behave_like "" + @state.before(:each).should include(*@shared.before(:each)) + end + + it "adds after(:each) blocks from the shared ContextState" do + @shared.after :each, &@a + @shared.after :each, &@b + @state.it_should_behave_like "" + @state.after(:each).should include(*@shared.after(:each)) + end + + it "adds after(:all) blocks from the shared ContextState" do + @shared.after :all, &@a + @shared.after :all, &@b + @state.it_should_behave_like "" + @state.after(:all).should include(*@shared.after(:all)) + end +end + +describe ContextState, "#filter_examples" do + before :each do + @state = ContextState.new "" + @state.it("one") { } + @state.it("two") { } + end + + it "removes examples that are filtered" do + @state.examples.first.stub!(:filtered?).and_return(true) + @state.examples.size.should == 2 + @state.filter_examples + @state.examples.size.should == 1 + end + + it "returns true if there are remaining examples to evaluate" do + @state.examples.first.stub!(:filtered?).and_return(true) + @state.filter_examples.should be_true + end + + it "returns false if there are no remaining examples to evaluate" do + @state.examples.first.stub!(:filtered?).and_return(true) + @state.examples.last.stub!(:filtered?).and_return(true) + @state.filter_examples.should be_false + end +end diff --git a/mspec/spec/runner/example_spec.rb b/mspec/spec/runner/example_spec.rb index 9c4163039..33cd367ac 100644 --- a/mspec/spec/runner/example_spec.rb +++ b/mspec/spec/runner/example_spec.rb @@ -5,24 +5,27 @@ require 'mspec/mocks/mock' require 'mspec/runner/example' describe ExampleState do - it "is initialized with the describe and it strings" do - ExampleState.new("This", "does").should be_kind_of(ExampleState) + it "is initialized with the ContextState, #it string, and #it block" do + prc = lambda { } + context = ContextState.new "" + ExampleState.new(context, "does", prc).should be_kind_of(ExampleState) end end describe ExampleState, "#describe" do before :each do - @state = ExampleState.new("describe", "it") + @context = ContextState.new Object, "#to_s" + @state = ExampleState.new @context, "it" end - it "returns the arguments to the #describe block stringified and concatenated" do - @state.describe.should == "describe" + it "returns the ContextState#description" do + @state.describe.should == @context.description end end describe ExampleState, "#it" do before :each do - @state = ExampleState.new("describe", "it") + @state = ExampleState.new ContextState.new("describe"), "it" end it "returns the argument to the #it block" do @@ -30,67 +33,80 @@ describe ExampleState, "#it" do end end -describe ExampleState, "#unfiltered?" do +describe ExampleState, "#context=" do + before :each do + @state = ExampleState.new ContextState.new("describe"), "it" + @context = ContextState.new "New#context" + end + + it "sets the containing ContextState" do + @state.context = @context + @state.context.should == @context + end + + it "resets the description" do + @state.description.should == "describe it" + @state.context = @context + @state.description.should == "New#context it" + end +end + +describe ExampleState, "#example" do + before :each do + @proc = lambda { } + @state = ExampleState.new ContextState.new("describe"), "it", @proc + end + + it "returns the #it block" do + @state.example.should == @proc + end +end + +describe ExampleState, "#filtered?" do before :each do MSpec.store :include, nil MSpec.store :exclude, nil - @state = ExampleState.new("describe", "it") + @state = ExampleState.new ContextState.new("describe"), "it" @filter = mock("filter") end - it "returns true if MSpec include filters list is empty" do - @state.unfiltered?.should == true + it "returns false if MSpec include filters list is empty" do + @state.filtered?.should == false end - it "returns true if MSpec include filters match this spec" do + it "returns false if MSpec include filters match this spec" do @filter.should_receive(:===).and_return(true) MSpec.register :include, @filter - @state.unfiltered?.should == true + @state.filtered?.should == false end - it "returns false if MSpec include filters do not match this spec" do + it "returns true if MSpec include filters do not match this spec" do @filter.should_receive(:===).and_return(false) MSpec.register :include, @filter - @state.unfiltered?.should == false + @state.filtered?.should == true end - it "returns true if MSpec exclude filters list is empty" do - @state.unfiltered?.should == true + it "returns false if MSpec exclude filters list is empty" do + @state.filtered?.should == false end - it "returns true if MSpec exclude filters do not match this spec" do + it "returns false if MSpec exclude filters do not match this spec" do @filter.should_receive(:===).and_return(false) MSpec.register :exclude, @filter - @state.unfiltered?.should == true + @state.filtered?.should == false end - it "returns false if MSpec exclude filters match this spec" do + it "returns true if MSpec exclude filters match this spec" do @filter.should_receive(:===).and_return(true) MSpec.register :exclude, @filter - @state.unfiltered?.should == false + @state.filtered?.should == true end - it "returns false if MSpec include and exclude filters match this spec" do + it "returns true if MSpec include and exclude filters match this spec" do @filter.should_receive(:===).twice.and_return(true) MSpec.register :include, @filter MSpec.register :exclude, @filter - @state.unfiltered?.should == false - end -end - -describe ExampleState, "#filtered?" do - before :each do - @state = ExampleState.new("describe", "it") - end - - it "returns true if #unfiltered returns false" do - @state.should_receive(:unfiltered?).and_return(false) @state.filtered?.should == true end - - it "returns false if #unfiltered returns true" do - @state.should_receive(:unfiltered?).and_return(true) - @state.filtered?.should == false - end end diff --git a/mspec/spec/runner/exception_spec.rb b/mspec/spec/runner/exception_spec.rb index 7d4c49a98..bdb554c4e 100644 --- a/mspec/spec/runner/exception_spec.rb +++ b/mspec/spec/runner/exception_spec.rb @@ -5,7 +5,8 @@ require 'mspec/runner/exception' describe ExceptionState, "#initialize" do it "takes a state, location (e.g. before :each), and exception" do - state = ExampleState.new "Class#method", "does something" + context = ContextState.new "Class#method" + state = ExampleState.new context, "does something" exc = Exception.new "Fail!" ExceptionState.new(state, "location", exc).should be_kind_of(ExceptionState) end @@ -13,7 +14,8 @@ end describe ExceptionState, "#description" do before :each do - @state = ExampleState.new "Class#method", "does something" + context = ContextState.new "Class#method" + @state = ExampleState.new context, "does something" end it "returns the state description if state was not nil" do @@ -34,7 +36,8 @@ end describe ExceptionState, "#describe" do before :each do - @state = ExampleState.new "Class#method", "does something" + context = ContextState.new "Class#method" + @state = ExampleState.new context, "does something" end it "returns the ExampleState#describe string if created with a non-nil state" do @@ -48,7 +51,8 @@ end describe ExceptionState, "#it" do before :each do - @state = ExampleState.new "Class#method", "does something" + context = ContextState.new "Class#method" + @state = ExampleState.new context, "does something" end it "returns the ExampleState#it string if created with a non-nil state" do @@ -62,7 +66,7 @@ end describe ExceptionState, "#failure?" do before :each do - @state = ExampleState.new "C#m", "works" + @state = ExampleState.new ContextState.new("C#m"), "works" end it "returns true if the exception is an ExpectationNotMetError" do diff --git a/mspec/spec/runner/formatters/dotted_spec.rb b/mspec/spec/runner/formatters/dotted_spec.rb index f52d89df5..90463d935 100644 --- a/mspec/spec/runner/formatters/dotted_spec.rb +++ b/mspec/spec/runner/formatters/dotted_spec.rb @@ -145,7 +145,7 @@ end describe DottedFormatter, "#before" do before :each do - @state = ExampleState.new("describe", "it") + @state = ExampleState.new ContextState.new("describe"), "it" @formatter = DottedFormatter.new @formatter.exception ExceptionState.new(nil, nil, ExpectationNotMetError.new("Failed!")) end @@ -167,7 +167,7 @@ describe DottedFormatter, "#after" do before :each do $stdout = @out = IOStub.new @formatter = DottedFormatter.new - @state = ExampleState.new("describe", "it") + @state = ExampleState.new ContextState.new("describe"), "it" end after :each do @@ -211,7 +211,8 @@ describe DottedFormatter, "#finish" do TimerAction.stub!(:new).and_return(@timer) $stdout = @out = IOStub.new - @state = ExampleState.new("Class#method", "runs") + context = ContextState.new "Class#method" + @state = ExampleState.new(context, "runs") MSpec.stub!(:register) @formatter = DottedFormatter.new @formatter.register diff --git a/mspec/spec/runner/formatters/html_spec.rb b/mspec/spec/runner/formatters/html_spec.rb index 009efd1b5..8ca251f16 100644 --- a/mspec/spec/runner/formatters/html_spec.rb +++ b/mspec/spec/runner/formatters/html_spec.rb @@ -95,7 +95,7 @@ describe HtmlFormatter, "#exception" do $stdout = @out = IOStub.new @formatter = HtmlFormatter.new @formatter.register - @state = ExampleState.new("describe", "it") + @state = ExampleState.new ContextState.new("describe"), "it" end after :each do @@ -119,7 +119,7 @@ describe HtmlFormatter, "#after" do $stdout = @out = IOStub.new @formatter = HtmlFormatter.new @formatter.register - @state = ExampleState.new("describe", "it") + @state = ExampleState.new ContextState.new("describe"), "it" end after :each do @@ -148,7 +148,8 @@ describe HtmlFormatter, "#finish" do TimerAction.stub!(:new).and_return(@timer) $stdout = @out = IOStub.new - @state = ExampleState.new("describe", "it") + context = ContextState.new "describe" + @state = ExampleState.new(context, "it") MSpec.stub!(:register) @formatter = HtmlFormatter.new @formatter.register diff --git a/mspec/spec/runner/formatters/specdoc_spec.rb b/mspec/spec/runner/formatters/specdoc_spec.rb index a4f91aee9..c56697b7b 100644 --- a/mspec/spec/runner/formatters/specdoc_spec.rb +++ b/mspec/spec/runner/formatters/specdoc_spec.rb @@ -34,7 +34,7 @@ describe SpecdocFormatter, "#before" do before :each do $stdout = @out = IOStub.new @formatter = SpecdocFormatter.new - @state = ExampleState.new "describe", "it" + @state = ExampleState.new ContextState.new("describe"), "it" end after :each do @@ -59,7 +59,8 @@ describe SpecdocFormatter, "#exception" do before :each do $stdout = @out = IOStub.new @formatter = SpecdocFormatter.new - @state = ExampleState.new "describe", "it" + context = ContextState.new "describe" + @state = ExampleState.new context, "it" end after :each do diff --git a/mspec/spec/runner/formatters/summary_spec.rb b/mspec/spec/runner/formatters/summary_spec.rb index 1b4df9d4c..0d50ef5f5 100644 --- a/mspec/spec/runner/formatters/summary_spec.rb +++ b/mspec/spec/runner/formatters/summary_spec.rb @@ -7,7 +7,8 @@ describe SummaryFormatter, "#after" do $stdout = @out = IOStub.new @formatter = SummaryFormatter.new @formatter.register - @state = ExampleState.new("describe", "it") + context = ContextState.new "describe" + @state = ExampleState.new(context, "it") end after :each do diff --git a/mspec/spec/runner/formatters/unit_spec.rb b/mspec/spec/runner/formatters/unit_spec.rb index 7d40ea9fa..6fa78f448 100644 --- a/mspec/spec/runner/formatters/unit_spec.rb +++ b/mspec/spec/runner/formatters/unit_spec.rb @@ -10,7 +10,8 @@ describe UnitdiffFormatter, "#finish" do TimerAction.stub!(:new).and_return(@timer) $stdout = @out = IOStub.new - @state = ExampleState.new("describe", "it") + context = ContextState.new "describe" + @state = ExampleState.new(context, "it") MSpec.stub!(:register) @formatter = UnitdiffFormatter.new @formatter.register diff --git a/mspec/spec/runner/formatters/yaml_spec.rb b/mspec/spec/runner/formatters/yaml_spec.rb index 627bf43d8..1f7c56a50 100644 --- a/mspec/spec/runner/formatters/yaml_spec.rb +++ b/mspec/spec/runner/formatters/yaml_spec.rb @@ -57,7 +57,8 @@ describe YamlFormatter, "#finish" do TimerAction.stub!(:new).and_return(@timer) $stdout = IOStub.new - @state = ExampleState.new("describe", "it") + context = ContextState.new "describe" + @state = ExampleState.new(context, "it") @formatter = YamlFormatter.new @formatter.stub!(:backtrace).and_return("") diff --git a/mspec/spec/runner/mspec_spec.rb b/mspec/spec/runner/mspec_spec.rb index 189d4c6f9..ad6495d95 100644 --- a/mspec/spec/runner/mspec_spec.rb +++ b/mspec/spec/runner/mspec_spec.rb @@ -87,11 +87,12 @@ end describe MSpec, ".protect" do before :each do - MSpec.stack.clear - @es = ExampleState.new "C#m", "runs" - @cs = ContextState.new + MSpec.clear_current + @cs = ContextState.new "C#m" @cs.stub!(:state).and_return(@es) - MSpec.stack.push @cs + @cs.parent = MSpec.current + + @es = ExampleState.new @cs, "runs" ScratchPad.record Exception.new("Sharp!") end @@ -107,6 +108,15 @@ describe MSpec, ".protect" do MSpec.protect("") { raise Exception, "Now you see me..." } end + it "does not rescue SystemExit" do + begin + MSpec.protect("") { exit 1 } + rescue SystemExit + ScratchPad.record :system_exit + end + ScratchPad.recorded.should == :system_exit + end + it "calls all the exception actions" do exc = ExceptionState.new @es, "testing", ScratchPad.recorded ExceptionState.stub!(:new).and_return(exc) @@ -123,18 +133,43 @@ describe MSpec, ".protect" do end end -describe MSpec, ".stack" do - it "returns an array" do - MSpec.stack.should be_kind_of(Array) +describe MSpec, ".register_current" do + before :each do + MSpec.clear_current + end + + it "sets the value returned by MSpec.current" do + MSpec.current.should be_nil + MSpec.register_current :a + MSpec.current.should == :a + end +end + +describe MSpec, ".clear_current" do + it "sets the value returned by MSpec.current to nil" do + MSpec.register_current :a + MSpec.current.should_not be_nil + MSpec.clear_current + MSpec.current.should be_nil end end describe MSpec, ".current" do - it "returns the top of the execution stack" do - MSpec.stack.clear - MSpec.stack.push :a - MSpec.stack.push :b - MSpec.current.should == :b + before :each do + MSpec.clear_current + end + + it "returns nil if no ContextState has been registered" do + MSpec.current.should be_nil + end + + it "returns the most recently registered ContextState" do + first = ContextState.new "" + second = ContextState.new "" + MSpec.register_current first + MSpec.current.should == first + MSpec.register_current second + MSpec.current.should == second end end @@ -187,23 +222,32 @@ end describe MSpec, ".describe" do before :each do - MSpec.stack.clear + MSpec.clear_current + @cs = ContextState.new "" + ContextState.stub!(:new).and_return(@cs) + MSpec.stub!(:current).and_return(nil) + MSpec.stub!(:register_current) end - it "accepts one argument" do - MSpec.describe(Object) { ScratchPad.record MSpec.current } - ScratchPad.recorded.should be_kind_of(ContextState) + it "creates a new ContextState for the block" do + ContextState.should_receive(:new).and_return(@cs) + MSpec.describe(Object) { } end - it "pushes a new ContextState instance on the stack" do - MSpec.describe(Object, "msg") { ScratchPad.record MSpec.current } - ScratchPad.recorded.should be_kind_of(ContextState) + it "accepts an optional second argument" do + ContextState.should_receive(:new).and_return(@cs) + MSpec.describe(Object, "msg") { } end - it "pops the ContextState instance off the stack when finished" do - MSpec.describe(Object, "msg") { ScratchPad.record MSpec.current } - ScratchPad.recorded.should be_kind_of(ContextState) - MSpec.stack.should == [] + it "registers the newly created ContextState" do + MSpec.should_receive(:register_current).with(@cs).twice + MSpec.describe(Object) { } + end + + it "invokes the ContextState#describe method" do + prc = lambda { } + @cs.should_receive(:describe).with(&prc) + MSpec.describe(Object, "msg", &prc) end end @@ -419,3 +463,23 @@ describe MSpec, ".clear_expectations" do MSpec.expectation?.should be_false end end + +describe MSpec, ".register_shared" do + it "stores a shared ContextState by description" do + parent = ContextState.new "container" + state = ContextState.new "shared" + state.parent = parent + prc = lambda { } + state.describe(&prc) + MSpec.register_shared(state) + MSpec.retrieve(:shared)["shared"].should == state + end +end + +describe MSpec, ".retrieve_shared" do + it "retrieves the shared ContextState matching description" do + state = ContextState.new "" + MSpec.retrieve(:shared)["shared"] = state + MSpec.retrieve_shared(:shared).should == state + end +end diff --git a/mspec/spec/runner/shared_spec.rb b/mspec/spec/runner/shared_spec.rb dissimilarity index 86% index 5bf5fce55..2e64909e0 100644 --- a/mspec/spec/runner/shared_spec.rb +++ b/mspec/spec/runner/shared_spec.rb @@ -1,41 +1,27 @@ -require File.dirname(__FILE__) + '/../spec_helper' -require 'mspec/runner/shared' - -describe Object, "#shared" do - it "stores the passed block in the MSpec module" do - proc = lambda { :shared } - shared :shared, &proc - MSpec.retrieve(:shared).should == proc - end -end - -describe Object, "#it_behaves_like" do - before :each do - end - - it "retrieves the instance variable set on Object and calls the proc" do - proc = lambda { |a| raise Exception, "visited with #{a.inspect}" } - shared :shared, &proc - lambda { - it_behaves_like(:shared, nil) - }.should raise_error(Exception, "visited with nil") - end - - it "accepts an optional argument to specify the class/module" do - proc = lambda { |a, b| raise Exception, "visited with #{a.inspect}, #{b.inspect}" } - shared :shared, &proc - lambda { - it_behaves_like(:shared, :method, :klass) - }.should raise_error(Exception, "visited with :method, :klass") - end - - it "accepts an optional argument to specify the class/module name" do - proc = lambda { |a, b, c| - raise Exception, "visited with #{a.inspect}, #{b.inspect}, #{c.inspect}" - } - shared :shared, &proc - lambda { - it_behaves_like(:shared, :method, :klass, :name) - }.should raise_error(Exception, "visited with :method, :klass, :name") - end -end +require File.dirname(__FILE__) + '/../spec_helper' +require 'mspec/runner/shared' + +describe Object, "#it_behaves_like" do + before :each do + @recv = Object.new + def @recv.before(what) + yield + end + @recv.stub!(:it_should_behave_like) + end + + it "creates @method set to the name of the aliased method" do + @recv.it_behaves_like "something", :some_method + @recv.instance_variable_get(:@method).should == :some_method + end + + it "creates @object if the passed object is not nil" do + @recv.it_behaves_like "something", :some_method, :some_object + @recv.instance_variable_get(:@object).should == :some_object + end + + it "sends :it_should_behave_like" do + @recv.should_receive(:it_should_behave_like) + @recv.it_behaves_like "something", :some_method + end +end -- 2.11.4.GIT