spectroscope 0.1.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/.ruby +53 -0
- data/COPYING.rdoc +36 -0
- data/HISTORY.md +10 -0
- data/LICENSE.txt +25 -0
- data/README.md +77 -0
- data/lib/spectroscope.rb +68 -0
- data/lib/spectroscope.yml +53 -0
- data/lib/spectroscope/context.rb +431 -0
- data/lib/spectroscope/example.rb +243 -0
- data/lib/spectroscope/hooks.rb +104 -0
- data/lib/spectroscope/world.rb +7 -0
- data/spec/hooks_spec.rb +83 -0
- data/spec/let_spec.rb +34 -0
- data/spec/scope_spec.rb +15 -0
- data/spec/shared_example_spec.rb +34 -0
- data/spec/subject_spec.rb +26 -0
- metadata +128 -0
@@ -0,0 +1,243 @@
|
|
1
|
+
module Spectroscope
|
2
|
+
|
3
|
+
# Example behavior.
|
4
|
+
#
|
5
|
+
# This is the `it` in your specs.
|
6
|
+
#
|
7
|
+
class Example
|
8
|
+
|
9
|
+
#
|
10
|
+
# Defines a specification procedure.
|
11
|
+
#
|
12
|
+
def initialize(options={}, &procedure)
|
13
|
+
@parent = options[:parent]
|
14
|
+
@label = options[:label]
|
15
|
+
@hooks = options[:hooks]
|
16
|
+
@skip = options[:skip]
|
17
|
+
@tags = options[:tags]
|
18
|
+
@keys = options[:keys]
|
19
|
+
@topic = options[:topic]
|
20
|
+
|
21
|
+
@procedure = procedure || lambda{ raise NotImplementedError } # pending
|
22
|
+
|
23
|
+
@tested = false
|
24
|
+
end
|
25
|
+
|
26
|
+
public
|
27
|
+
|
28
|
+
#
|
29
|
+
# The parent testcase to which this test belongs.
|
30
|
+
#
|
31
|
+
attr :parent
|
32
|
+
|
33
|
+
#
|
34
|
+
# Before and after advice.
|
35
|
+
#
|
36
|
+
attr :hooks
|
37
|
+
|
38
|
+
#
|
39
|
+
# Description of example.
|
40
|
+
#
|
41
|
+
attr :label
|
42
|
+
|
43
|
+
#
|
44
|
+
# List of identifying tags attached to example. These should
|
45
|
+
# be a list of Symbols, with an optional Symbol=>Object tail element.
|
46
|
+
#
|
47
|
+
attr :tags
|
48
|
+
|
49
|
+
#
|
50
|
+
# A map of Symbol => Object, which is taken from the end of `tags`.
|
51
|
+
# Unlike tags, keys allow key-value relationships, rather than just
|
52
|
+
# symbolic names.
|
53
|
+
#
|
54
|
+
def keys
|
55
|
+
Hash === tags.last ? tags.last : {}
|
56
|
+
end
|
57
|
+
|
58
|
+
#
|
59
|
+
# In RSpec data keys are called metadata.
|
60
|
+
#
|
61
|
+
alias_method :metadata, :keys
|
62
|
+
|
63
|
+
#
|
64
|
+
# Test procedure, in which test assertions should be made.
|
65
|
+
#
|
66
|
+
attr :procedure
|
67
|
+
|
68
|
+
#
|
69
|
+
# RubyTest supports `type` to describe the way in which the underlying
|
70
|
+
# framework represents tests.
|
71
|
+
#
|
72
|
+
def type
|
73
|
+
'it'
|
74
|
+
end
|
75
|
+
|
76
|
+
#
|
77
|
+
# Skip this spec?
|
78
|
+
#
|
79
|
+
def skip?
|
80
|
+
@skip
|
81
|
+
end
|
82
|
+
|
83
|
+
#
|
84
|
+
# Set +true+ to skip, or String to skip with reason.
|
85
|
+
#
|
86
|
+
def skip=(reason)
|
87
|
+
@skip = reason
|
88
|
+
end
|
89
|
+
|
90
|
+
#
|
91
|
+
#
|
92
|
+
#
|
93
|
+
def tested?
|
94
|
+
@tested
|
95
|
+
end
|
96
|
+
|
97
|
+
#
|
98
|
+
#
|
99
|
+
#
|
100
|
+
def tested=(boolean)
|
101
|
+
@tested = !!boolean
|
102
|
+
end
|
103
|
+
|
104
|
+
#
|
105
|
+
# Return label string.
|
106
|
+
#
|
107
|
+
# @return [String]
|
108
|
+
#
|
109
|
+
def to_s
|
110
|
+
label.to_s
|
111
|
+
end
|
112
|
+
|
113
|
+
#
|
114
|
+
# Ruby Test looks for `topic` as the desciption of the subject.
|
115
|
+
#
|
116
|
+
def topic
|
117
|
+
@topic.to_s
|
118
|
+
end
|
119
|
+
|
120
|
+
#
|
121
|
+
# The shared It::Scope from the parent.
|
122
|
+
#
|
123
|
+
def scope
|
124
|
+
@scope ||= Scope.new(parent)
|
125
|
+
end
|
126
|
+
|
127
|
+
#
|
128
|
+
#def arguments
|
129
|
+
# @arguments
|
130
|
+
#end
|
131
|
+
|
132
|
+
#
|
133
|
+
#def arguments=(args)
|
134
|
+
# @arguments = args
|
135
|
+
#end
|
136
|
+
|
137
|
+
#
|
138
|
+
# If +match+ is a Regexp or String, match against label.
|
139
|
+
# If +match+ is a Hash, match against keys.
|
140
|
+
# If +match+ is a Symbol, match against tags.
|
141
|
+
#
|
142
|
+
def match?(match)
|
143
|
+
case match
|
144
|
+
when Regexp, String
|
145
|
+
match === label
|
146
|
+
when Hash
|
147
|
+
match.any?{ |k,m| m === keys[k] }
|
148
|
+
else
|
149
|
+
tags.include?(match.to_sym)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
#
|
154
|
+
# Execute example.
|
155
|
+
#
|
156
|
+
def call
|
157
|
+
parent.run(self) do
|
158
|
+
hooks.run(self, :before, :each, scope) #if hooks
|
159
|
+
scope.instance_exec(&procedure) # TODO: can it take any argument(s) ?
|
160
|
+
hooks.run(self, :after, :each, scope) #if hooks
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
#
|
165
|
+
# Example can be converted to a Proc object.
|
166
|
+
#
|
167
|
+
# @return [Proc]
|
168
|
+
#
|
169
|
+
def to_proc
|
170
|
+
lambda{ call }
|
171
|
+
end
|
172
|
+
|
173
|
+
|
174
|
+
# Example::Scope is used by Context to create a shared scope
|
175
|
+
# for running examples.
|
176
|
+
#
|
177
|
+
class Scope < World
|
178
|
+
|
179
|
+
#
|
180
|
+
# @param [Context] group
|
181
|
+
#
|
182
|
+
def initialize(group)
|
183
|
+
@_group = group
|
184
|
+
extend group.scope
|
185
|
+
#include group.scope
|
186
|
+
#extend self
|
187
|
+
@_let ||= {}
|
188
|
+
end
|
189
|
+
|
190
|
+
#
|
191
|
+
# @raise [NotImplementedError] Example is a "todo".
|
192
|
+
#
|
193
|
+
def pending(message=nil)
|
194
|
+
raise NotImplementedError.new(message)
|
195
|
+
end
|
196
|
+
|
197
|
+
#
|
198
|
+
#
|
199
|
+
#
|
200
|
+
def subject
|
201
|
+
@_subject ||= (
|
202
|
+
if defined?(super)
|
203
|
+
super || described_class.new
|
204
|
+
else
|
205
|
+
described_class.new
|
206
|
+
end
|
207
|
+
)
|
208
|
+
end
|
209
|
+
|
210
|
+
private
|
211
|
+
|
212
|
+
#
|
213
|
+
# Handle implicit subject.
|
214
|
+
#
|
215
|
+
def method_missing(s, *a, &b)
|
216
|
+
subject.__send__(s, *a, &b) # public_send
|
217
|
+
end
|
218
|
+
|
219
|
+
#if method_defined?(:should)
|
220
|
+
# # Handle implicit subject on should.
|
221
|
+
# # @todo Same for method_missing ?
|
222
|
+
# def should(*a,&b)
|
223
|
+
# subject.should(*a,&b)
|
224
|
+
# end
|
225
|
+
#end
|
226
|
+
|
227
|
+
#
|
228
|
+
# @todo Maybe deprecate. It seems silly.
|
229
|
+
#
|
230
|
+
def described_class
|
231
|
+
case @_group.subject
|
232
|
+
when Class
|
233
|
+
@_group.subject
|
234
|
+
else
|
235
|
+
raise NoMethodError, "undefined method `described_class` for #{self}"
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
end
|
240
|
+
|
241
|
+
end
|
242
|
+
|
243
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
module Spectroscope
|
2
|
+
|
3
|
+
# Context advice.
|
4
|
+
#
|
5
|
+
class Hooks
|
6
|
+
|
7
|
+
#
|
8
|
+
# New case instance.
|
9
|
+
#
|
10
|
+
def initialize
|
11
|
+
@hooks_all = {:before=>[], :after=>[]}
|
12
|
+
@hooks_each = {:before=>[], :after=>[]}
|
13
|
+
end
|
14
|
+
|
15
|
+
#
|
16
|
+
def initialize_copy(original)
|
17
|
+
original.hooks_all do |tense, hooks|
|
18
|
+
hooks_all[tense] = hooks.clone
|
19
|
+
end
|
20
|
+
original.hooks_each do |tense, hooks|
|
21
|
+
hooks_each[tense] = hooks.clone
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
#
|
26
|
+
def add(tense, range, *tags, &proc)
|
27
|
+
case range
|
28
|
+
when :all
|
29
|
+
raise ArgumentError, "tagged #{tense}-all advice is useless" unless tags.empty?
|
30
|
+
@hooks_all[tense] << Hook.new(tense, range, *tags, &proc)
|
31
|
+
when :each
|
32
|
+
@hooks_each[tense] << Hook.new(tense, range, *tags, &proc)
|
33
|
+
else
|
34
|
+
raise ArgumentError, "range must be :all or :each"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
#
|
39
|
+
def run(target, tense, range, scope)
|
40
|
+
case range
|
41
|
+
when :all
|
42
|
+
hooks_all[tense].each do |hook|
|
43
|
+
scope.instance_eval(&hook)
|
44
|
+
end
|
45
|
+
when :each
|
46
|
+
hooks_each[tense].each do |hook|
|
47
|
+
scope.instance_eval(&hook) if hook.match?(target)
|
48
|
+
end
|
49
|
+
else
|
50
|
+
raise ArgumentError, "range must be :all or :each"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
#
|
57
|
+
#def validate(tense, scope)
|
58
|
+
# raise ArgumentError unless tense == :before or tense == :after
|
59
|
+
# raise ArgumentError unless scope == :all or tense == :each
|
60
|
+
#end
|
61
|
+
|
62
|
+
protected
|
63
|
+
|
64
|
+
#
|
65
|
+
attr :hooks_all
|
66
|
+
|
67
|
+
#
|
68
|
+
attr :hooks_each
|
69
|
+
|
70
|
+
# Encapsulates a hook procedure along with it's match tags.
|
71
|
+
#
|
72
|
+
class Hook
|
73
|
+
|
74
|
+
## A brief description of the advice (optional).
|
75
|
+
#attr :subject
|
76
|
+
|
77
|
+
def initialize(tense, scope, *tags, &proc)
|
78
|
+
@tense = tense
|
79
|
+
@scope = scope
|
80
|
+
@tags = tags
|
81
|
+
@proc = proc
|
82
|
+
end
|
83
|
+
|
84
|
+
#
|
85
|
+
# Check for matching subjects or tags for each advice.
|
86
|
+
#
|
87
|
+
def match?(it)
|
88
|
+
return true if @tags.empty?
|
89
|
+
|
90
|
+
@tags.any? do |t|
|
91
|
+
it.match?(t)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
#
|
96
|
+
def to_proc
|
97
|
+
@proc
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
data/spec/hooks_spec.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
describe "hooks" do
|
2
|
+
|
3
|
+
describe "before advice" do
|
4
|
+
|
5
|
+
before :each do
|
6
|
+
@x = 1
|
7
|
+
end
|
8
|
+
|
9
|
+
it "has setup" do
|
10
|
+
@x.should == 1
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
describe "after advice" do
|
17
|
+
|
18
|
+
it "does something" do
|
19
|
+
@x = 1
|
20
|
+
end
|
21
|
+
|
22
|
+
after :each do
|
23
|
+
raise unless @x == 1
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
describe "can have more than one before and after" do
|
30
|
+
|
31
|
+
before :each do
|
32
|
+
@x = 1
|
33
|
+
end
|
34
|
+
|
35
|
+
before :each do
|
36
|
+
@y = 2
|
37
|
+
end
|
38
|
+
|
39
|
+
it "has setup" do
|
40
|
+
@x.should == 1
|
41
|
+
@y.should == 2
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
describe "advice can be filtered on label" do
|
48
|
+
|
49
|
+
it "is general" do
|
50
|
+
@special.should == nil
|
51
|
+
end
|
52
|
+
|
53
|
+
it "is special" do
|
54
|
+
@special.should == true
|
55
|
+
end
|
56
|
+
|
57
|
+
before :each, /special/ do
|
58
|
+
@special = true
|
59
|
+
end
|
60
|
+
|
61
|
+
after :each, /special/ do
|
62
|
+
@special = nil
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
describe "advice is inherited by sub-examples" do
|
68
|
+
|
69
|
+
before :each do
|
70
|
+
@z = 3
|
71
|
+
end
|
72
|
+
|
73
|
+
describe "sub-example" do
|
74
|
+
|
75
|
+
it "has setup" do
|
76
|
+
@z.should == 3
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
data/spec/let_spec.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
describe Spectroscope::Context::Scope do
|
2
|
+
|
3
|
+
describe "#let" do
|
4
|
+
count = 0
|
5
|
+
|
6
|
+
let(:counter) { count += 1 }
|
7
|
+
|
8
|
+
it "memoizes the value" do
|
9
|
+
counter.should == 1
|
10
|
+
counter.should == 1
|
11
|
+
end
|
12
|
+
|
13
|
+
it "is not cached across examples" do
|
14
|
+
counter.should == 2
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "#let!" do
|
19
|
+
order = []
|
20
|
+
count = 0
|
21
|
+
|
22
|
+
let!(:counter) do
|
23
|
+
order << :let!
|
24
|
+
count += 1
|
25
|
+
end
|
26
|
+
|
27
|
+
it "calls the helper method in a before hook" do
|
28
|
+
order << :example
|
29
|
+
order.should == [:let!, :example]
|
30
|
+
counter.should == 1
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|