mspec 1.3.1 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +93 -0
- data/lib/mspec/commands/mspec-ci.rb +2 -0
- data/lib/mspec/matchers.rb +4 -3
- data/lib/mspec/matchers/equal_element.rb +78 -0
- data/lib/mspec/runner/context.rb +138 -52
- data/lib/mspec/runner/example.rb +17 -20
- data/lib/mspec/runner/mspec.rb +38 -13
- data/lib/mspec/runner/object.rb +5 -1
- data/lib/mspec/runner/shared.rb +6 -6
- data/lib/mspec/version.rb +1 -1
- data/spec/commands/mspec_ci_spec.rb +14 -0
- data/spec/matchers/equal_element_spec.rb +75 -0
- data/spec/runner/actions/debug_spec.rb +2 -1
- data/spec/runner/actions/gdb_spec.rb +1 -1
- data/spec/runner/actions/tag_spec.rb +7 -4
- data/spec/runner/context_spec.rb +422 -57
- data/spec/runner/example_spec.rb +53 -37
- data/spec/runner/exception_spec.rb +9 -5
- data/spec/runner/formatters/dotted_spec.rb +4 -3
- data/spec/runner/formatters/html_spec.rb +4 -3
- data/spec/runner/formatters/specdoc_spec.rb +3 -2
- data/spec/runner/formatters/summary_spec.rb +2 -1
- data/spec/runner/formatters/unit_spec.rb +2 -1
- data/spec/runner/formatters/yaml_spec.rb +2 -1
- data/spec/runner/mspec_spec.rb +87 -23
- data/spec/runner/shared_spec.rb +14 -28
- metadata +3 -1
data/README
CHANGED
@@ -91,6 +91,99 @@ All of these should be applied to a block created with `lambda` or `proc`:
|
|
91
91
|
is associated with. The exception class can be given for finer-grained
|
92
92
|
control (inheritance works normally so Exception would catch everything.)
|
93
93
|
|
94
|
+
== Nested 'describe' blocks
|
95
|
+
|
96
|
+
MSpec supports nesting one 'describe' block inside another. The examples in
|
97
|
+
the nested block are evaluated with all the before/after blocks of all the
|
98
|
+
containing 'describe' blocks. The following example illustrates this:
|
99
|
+
|
100
|
+
describe "Some#method" do
|
101
|
+
before :each do
|
102
|
+
@obj = 1
|
103
|
+
end
|
104
|
+
|
105
|
+
describe "when passed String" do
|
106
|
+
before :each do
|
107
|
+
@meth = :to_s
|
108
|
+
end
|
109
|
+
|
110
|
+
it "returns false" do
|
111
|
+
# when this example is evaluated, @obj = 1 and @meth = :to_s
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
The output when using the SpecdocFormatter (selected with -fs to the runners)
|
117
|
+
will be as follows:
|
118
|
+
|
119
|
+
Some#method when passed String
|
120
|
+
- returns false
|
121
|
+
|
122
|
+
|
123
|
+
== Shared 'describe' blocks
|
124
|
+
|
125
|
+
MSpec supports RSpec-style shared 'describe' blocks. MSpec also provides a
|
126
|
+
convenience method to assist in writing specs for the numerous aliased methods
|
127
|
+
that Ruby provides. The following example illustrates shared blocks:
|
128
|
+
|
129
|
+
describe :someclass_some_method, :shared => true do
|
130
|
+
it "does something" do
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
describe "SomeClass#some_method" do
|
135
|
+
it_should_behave_like "someclass_some_method"
|
136
|
+
end
|
137
|
+
|
138
|
+
The first argument to 'describe' for a shared block is an object that
|
139
|
+
duck-types as a String. The representation of the object must be unique. This
|
140
|
+
example uses a symbol. This was the convention for the previous facility that
|
141
|
+
MSpec provided for aliased method (#it_behaves_like). However, this convention
|
142
|
+
is not set in stone (but the uniqueness requirement is). Note that the
|
143
|
+
argument to the #it_should_behave_like is a String because at this time RSpec
|
144
|
+
will not find the shared block by the symbol.
|
145
|
+
|
146
|
+
MSpec continues to support the #it_behaves_like convenience method for
|
147
|
+
specifying aliased methods. The syntax is as follows:
|
148
|
+
|
149
|
+
it_behaves_like :symbol_matching_shared_describe, :method [, :object]
|
150
|
+
|
151
|
+
describe :someclass_some_method, :shared => true do
|
152
|
+
it "returns true" do
|
153
|
+
obj.send(@method).should be_true
|
154
|
+
end
|
155
|
+
|
156
|
+
it "returns something else" do
|
157
|
+
@object.send(@method).should be_something_else
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
# example #1
|
162
|
+
describe "SomeClass#some_method" do
|
163
|
+
it_behaves_like :someclass_some_method, :other_method
|
164
|
+
end
|
165
|
+
|
166
|
+
# example #2
|
167
|
+
describe "SomeOtherClass#some_method" do
|
168
|
+
it_behaves_like :someclass_some_method, :some_method, OtherClass
|
169
|
+
end
|
170
|
+
|
171
|
+
The first form above (#1) is used for typical aliases. That is, methods with
|
172
|
+
different names on the same class that behave identically. The
|
173
|
+
#it_behaves_like helper creates a before(:all) block that sets @method to
|
174
|
+
:other_method. The form of the first example block in the shared block
|
175
|
+
illustrates the typical form of a spec for an aliased method.
|
176
|
+
|
177
|
+
The second form above (#2) is used for methods on different classes that are
|
178
|
+
essentially aliases, even though Ruby does not provide a syntax for specifying
|
179
|
+
such methods as aliases. Examples are the methods on File, FileTest, and
|
180
|
+
File::Stat. In this case, the #it_behaves_like helper sets both @method and
|
181
|
+
@object in the before(:all) block (@method = :some_method, @object =
|
182
|
+
OtherClass in this example).
|
183
|
+
|
184
|
+
For shared specs that fall outside of either of these two narrow categories,
|
185
|
+
use nested or shared 'describe' blocks as appropriate and use the
|
186
|
+
#it_should_behave_like method directly.
|
94
187
|
|
95
188
|
== Guards
|
96
189
|
|
@@ -61,8 +61,10 @@ class MSpecCI < MSpecScript
|
|
61
61
|
MSpec.register_tags_patterns config[:tags_patterns]
|
62
62
|
MSpec.register_files files
|
63
63
|
TagFilter.new(:exclude, "fails").register
|
64
|
+
TagFilter.new(:exclude, "critical").register
|
64
65
|
TagFilter.new(:exclude, "unstable").register
|
65
66
|
TagFilter.new(:exclude, "incomplete").register
|
67
|
+
TagFilter.new(:exclude, "unsupported").register
|
66
68
|
|
67
69
|
MSpec.process
|
68
70
|
exit MSpec.exit_code
|
data/lib/mspec/matchers.rb
CHANGED
@@ -6,12 +6,13 @@ require 'mspec/matchers/be_false'
|
|
6
6
|
require 'mspec/matchers/be_kind_of'
|
7
7
|
require 'mspec/matchers/be_nil'
|
8
8
|
require 'mspec/matchers/be_true'
|
9
|
-
require 'mspec/matchers/
|
9
|
+
require 'mspec/matchers/complain'
|
10
10
|
require 'mspec/matchers/eql'
|
11
|
+
require 'mspec/matchers/equal'
|
12
|
+
require 'mspec/matchers/equal_element'
|
13
|
+
require 'mspec/matchers/equal_utf16'
|
11
14
|
require 'mspec/matchers/include'
|
12
15
|
require 'mspec/matchers/match_yaml'
|
13
16
|
require 'mspec/matchers/raise_error'
|
14
17
|
require 'mspec/matchers/output'
|
15
18
|
require 'mspec/matchers/output_to_fd'
|
16
|
-
require 'mspec/matchers/complain'
|
17
|
-
require 'mspec/matchers/equal_utf16'
|
@@ -0,0 +1,78 @@
|
|
1
|
+
class EqualElementMatcher
|
2
|
+
def initialize(element, attributes = nil, content = nil, options = {})
|
3
|
+
@element = element
|
4
|
+
@attributes = attributes
|
5
|
+
@content = content
|
6
|
+
@options = options
|
7
|
+
end
|
8
|
+
|
9
|
+
def matches?(actual)
|
10
|
+
@actual = actual
|
11
|
+
|
12
|
+
matched = true
|
13
|
+
|
14
|
+
if @options[:not_closed]
|
15
|
+
matched &&= actual =~ /^#{Regexp.quote("<" + @element)}.*#{Regexp.quote(">" + (@content || ''))}$/
|
16
|
+
else
|
17
|
+
matched &&= actual =~ /^#{Regexp.quote("<" + @element)}/
|
18
|
+
matched &&= actual =~ /#{Regexp.quote("</" + @element + ">")}$/
|
19
|
+
matched &&= actual =~ /#{Regexp.quote(">" + @content + "</")}/ if @content
|
20
|
+
end
|
21
|
+
|
22
|
+
if @attributes
|
23
|
+
if @attributes.empty?
|
24
|
+
matched &&= actual.scan(/\w+\=\"(.*)\"/).size == 0
|
25
|
+
else
|
26
|
+
@attributes.each do |key, value|
|
27
|
+
if value == true
|
28
|
+
matched &&= (actual.scan(/#{Regexp.quote(key)}(\s|>)/).size == 1)
|
29
|
+
else
|
30
|
+
matched &&= (actual.scan(%Q{ #{key}="#{value}"}).size == 1)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
!!matched
|
37
|
+
end
|
38
|
+
|
39
|
+
def failure_message
|
40
|
+
["Expected #{@actual.pretty_inspect}",
|
41
|
+
"to be a '#{@element}' element with #{attributes_for_failure_message} and #{content_for_failure_message}"]
|
42
|
+
end
|
43
|
+
|
44
|
+
def negative_failure_message
|
45
|
+
["Expected #{@actual.pretty_inspect}",
|
46
|
+
"not to be a '#{@element}' element with #{attributes_for_failure_message} and #{content_for_failure_message}"]
|
47
|
+
end
|
48
|
+
|
49
|
+
def attributes_for_failure_message
|
50
|
+
if @attributes
|
51
|
+
if @attributes.empty?
|
52
|
+
"no attributes"
|
53
|
+
else
|
54
|
+
@attributes.inject([]) { |memo, n| memo << %Q{#{n[0]}="#{n[1]}"} }.join(" ")
|
55
|
+
end
|
56
|
+
else
|
57
|
+
"any attributes"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def content_for_failure_message
|
62
|
+
if @content
|
63
|
+
if @content.empty?
|
64
|
+
"no content"
|
65
|
+
else
|
66
|
+
"#{@content.inspect} as content"
|
67
|
+
end
|
68
|
+
else
|
69
|
+
"any content"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
class Object
|
75
|
+
def equal_element(*args)
|
76
|
+
EqualElementMatcher.new(*args)
|
77
|
+
end
|
78
|
+
end
|
data/lib/mspec/runner/context.rb
CHANGED
@@ -13,84 +13,170 @@ require 'mspec/runner/example'
|
|
13
13
|
# is evaluated, just as +it+ refers to the example itself.
|
14
14
|
#++
|
15
15
|
class ContextState
|
16
|
-
attr_reader :state
|
17
|
-
|
18
|
-
def initialize
|
19
|
-
@
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
16
|
+
attr_reader :state, :parent, :parents, :children, :examples, :to_s
|
17
|
+
|
18
|
+
def initialize(mod, options=nil)
|
19
|
+
@to_s = mod.to_s
|
20
|
+
if options.is_a? Hash
|
21
|
+
@options = options
|
22
|
+
else
|
23
|
+
@to_s += "#{".:#".include?(options[0,1]) ? "" : " "}#{options}" if options
|
24
|
+
@options = { }
|
25
|
+
end
|
26
|
+
@options[:shared] ||= false
|
27
|
+
|
28
|
+
@parsed = false
|
29
|
+
@before = { :all => [], :each => [] }
|
30
|
+
@after = { :all => [], :each => [] }
|
31
|
+
@pre = {}
|
32
|
+
@post = {}
|
33
|
+
@examples = []
|
34
|
+
@parent = nil
|
35
|
+
@parents = [self]
|
36
|
+
@children = []
|
37
|
+
|
24
38
|
@mock_verify = lambda { Mock.verify_count }
|
25
39
|
@mock_cleanup = lambda { Mock.cleanup }
|
26
40
|
@expectation_missing = lambda { raise ExpectationNotFoundError }
|
27
41
|
end
|
28
42
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
when :all
|
34
|
-
@start << block
|
35
|
-
end
|
43
|
+
# Returns true if this is a shared +ContextState+. Essentially, when
|
44
|
+
# created with: describe "Something", :shared => true { ... }
|
45
|
+
def shared?
|
46
|
+
return @options[:shared]
|
36
47
|
end
|
37
48
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
49
|
+
# Set the parent (enclosing) +ContextState+ for this state. Creates
|
50
|
+
# the +parents+ list.
|
51
|
+
def parent=(parent)
|
52
|
+
@description = nil
|
53
|
+
@parent = parent
|
54
|
+
parent.child self if parent and not shared?
|
55
|
+
|
56
|
+
state = parent
|
57
|
+
while state
|
58
|
+
parents.unshift state
|
59
|
+
state = state.parent
|
44
60
|
end
|
45
61
|
end
|
46
62
|
|
63
|
+
# Add the ContextState instance +child+ to the list of nested
|
64
|
+
# describe blocks.
|
65
|
+
def child(child)
|
66
|
+
@children << child
|
67
|
+
end
|
68
|
+
|
69
|
+
# Returns a list of all before(+what+) blocks from self and any parents.
|
70
|
+
def pre(what)
|
71
|
+
@pre[what] ||= parents.inject([]) { |l, s| l.push(*s.before(what)) }
|
72
|
+
end
|
73
|
+
|
74
|
+
# Returns a list of all after(+what+) blocks from self and any parents.
|
75
|
+
# The list is in reverse order. In other words, the blocks defined in
|
76
|
+
# inner describes are in the list before those defined in outer describes,
|
77
|
+
# and in a particular describe block those defined later are in the list
|
78
|
+
# before those defined earlier.
|
79
|
+
def post(what)
|
80
|
+
@post[what] ||= parents.inject([]) { |l, s| l.unshift(*s.after(what)) }
|
81
|
+
end
|
82
|
+
|
83
|
+
# Records before(:each) and before(:all) blocks.
|
84
|
+
def before(what, &block)
|
85
|
+
block ? @before[what].push(block) : @before[what]
|
86
|
+
end
|
87
|
+
|
88
|
+
# Records after(:each) and after(:all) blocks.
|
89
|
+
def after(what, &block)
|
90
|
+
block ? @after[what].unshift(block) : @after[what]
|
91
|
+
end
|
92
|
+
|
93
|
+
# Creates an ExampleState instance for the block and stores it
|
94
|
+
# in a list of examples to evaluate unless the example is filtered.
|
47
95
|
def it(desc, &block)
|
48
|
-
|
49
|
-
|
96
|
+
@examples << ExampleState.new(self, desc, block)
|
97
|
+
end
|
98
|
+
|
99
|
+
# Evaluates the block and resets the toplevel +ContextState+ to #parent.
|
100
|
+
def describe(&block)
|
101
|
+
@parsed = protect @to_s, block, false
|
102
|
+
MSpec.register_current parent
|
103
|
+
MSpec.register_shared self if shared?
|
104
|
+
end
|
105
|
+
|
106
|
+
# Returns a description string generated from self and all parents
|
107
|
+
def description
|
108
|
+
@description ||= parents.inject([]) { |l,s| l << s.to_s }.join(" ")
|
50
109
|
end
|
51
110
|
|
52
|
-
|
53
|
-
|
54
|
-
|
111
|
+
# Injects the before/after blocks and examples from the shared
|
112
|
+
# describe block into this +ContextState+ instance.
|
113
|
+
def it_should_behave_like(desc)
|
114
|
+
unless state = MSpec.retrieve_shared(desc)
|
115
|
+
raise Exception, "Unable to find shared 'describe' for #{desc}"
|
116
|
+
end
|
117
|
+
|
118
|
+
state.examples.each { |ex| ex.context = self; @examples << ex }
|
119
|
+
state.before(:all).each { |b| before :all, &b }
|
120
|
+
state.before(:each).each { |b| before :each, &b }
|
121
|
+
state.after(:each).each { |b| after :each, &b }
|
122
|
+
state.after(:all).each { |b| after :all, &b }
|
55
123
|
end
|
56
124
|
|
125
|
+
# Evaluates each block in +blocks+ using the +MSpec.protect+ method
|
126
|
+
# so that exceptions are handled and tallied. Returns true and does
|
127
|
+
# NOT evaluate any blocks if +check+ is true and +MSpec.pretend_mode?+
|
128
|
+
# is true.
|
57
129
|
def protect(what, blocks, check=true)
|
58
130
|
return true if check and MSpec.pretend_mode?
|
59
131
|
Array(blocks).all? { |block| MSpec.protect what, &block }
|
60
132
|
end
|
61
133
|
|
134
|
+
# Removes filtered examples. Returns true if there are examples
|
135
|
+
# left to evaluate.
|
136
|
+
def filter_examples
|
137
|
+
@examples.reject! { |ex| ex.filtered? }
|
138
|
+
not @examples.empty?
|
139
|
+
end
|
140
|
+
|
141
|
+
# Evaluates the examples in a +ContextState+. Invokes the MSpec events
|
142
|
+
# for :enter, :before, :after, :leave.
|
62
143
|
def process
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
144
|
+
MSpec.register_current self
|
145
|
+
|
146
|
+
if @parsed and filter_examples
|
147
|
+
MSpec.shuffle @examples if MSpec.randomize?
|
148
|
+
MSpec.actions :enter, description
|
149
|
+
|
150
|
+
if protect "before :all", pre(:all)
|
151
|
+
@examples.each do |state|
|
152
|
+
@state = state
|
153
|
+
example = state.example
|
154
|
+
MSpec.actions :before, state
|
155
|
+
|
156
|
+
if protect "before :each", pre(:each)
|
157
|
+
MSpec.clear_expectations
|
158
|
+
if example
|
159
|
+
passed = protect nil, example
|
160
|
+
MSpec.actions :example, state, example
|
161
|
+
protect nil, @expectation_missing unless MSpec.expectation? or not passed
|
162
|
+
end
|
163
|
+
protect "after :each", post(:each)
|
164
|
+
protect "Mock.verify_count", @mock_verify
|
80
165
|
end
|
81
|
-
protect "after :each", @after
|
82
|
-
protect "Mock.verify_count", @mock_verify
|
83
|
-
end
|
84
166
|
|
167
|
+
protect "Mock.cleanup", @mock_cleanup
|
168
|
+
MSpec.actions :after, state
|
169
|
+
@state = nil
|
170
|
+
end
|
171
|
+
protect "after :all", post(:all)
|
172
|
+
else
|
85
173
|
protect "Mock.cleanup", @mock_cleanup
|
86
|
-
MSpec.actions :after, state
|
87
|
-
@state = nil
|
88
174
|
end
|
89
|
-
|
90
|
-
|
91
|
-
protect "Mock.cleanup", @mock_cleanup
|
175
|
+
|
176
|
+
MSpec.actions :leave
|
92
177
|
end
|
93
178
|
|
94
|
-
MSpec.
|
179
|
+
MSpec.register_current nil
|
180
|
+
children.each { |child| child.process }
|
95
181
|
end
|
96
182
|
end
|
data/lib/mspec/runner/example.rb
CHANGED
@@ -3,35 +3,32 @@ require 'mspec/runner/mspec'
|
|
3
3
|
# Holds some of the state of the example (i.e. +it+ block) that is
|
4
4
|
# being evaluated. See also +ContextState+.
|
5
5
|
class ExampleState
|
6
|
-
|
7
|
-
@describe = describe
|
8
|
-
@it = it
|
9
|
-
@unfiltered = nil
|
10
|
-
end
|
6
|
+
attr_reader :context, :it, :example
|
11
7
|
|
12
|
-
def
|
13
|
-
@
|
8
|
+
def initialize(context, it, example=nil)
|
9
|
+
@context = context
|
10
|
+
@it = it
|
11
|
+
@example = example
|
14
12
|
end
|
15
13
|
|
16
|
-
def
|
17
|
-
@
|
14
|
+
def context=(context)
|
15
|
+
@description = nil
|
16
|
+
@context = context
|
18
17
|
end
|
19
18
|
|
20
|
-
def
|
21
|
-
@description
|
19
|
+
def describe
|
20
|
+
@context.description
|
22
21
|
end
|
23
22
|
|
24
|
-
def
|
25
|
-
|
26
|
-
incl = MSpec.retrieve(:include) || []
|
27
|
-
excl = MSpec.retrieve(:exclude) || []
|
28
|
-
@unfiltered = incl.empty? || incl.any? { |f| f === description }
|
29
|
-
@unfiltered &&= excl.empty? || !excl.any? { |f| f === description }
|
30
|
-
end
|
31
|
-
@unfiltered
|
23
|
+
def description
|
24
|
+
@description ||= "#{describe} #{@it}"
|
32
25
|
end
|
33
26
|
|
34
27
|
def filtered?
|
35
|
-
|
28
|
+
incl = MSpec.retrieve(:include) || []
|
29
|
+
excl = MSpec.retrieve(:exclude) || []
|
30
|
+
included = incl.empty? || incl.any? { |f| f === description }
|
31
|
+
included &&= excl.empty? || !excl.any? { |f| f === description }
|
32
|
+
not included
|
36
33
|
end
|
37
34
|
end
|