mspec 1.3.1 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|