spectroscope 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -0,0 +1,7 @@
1
+ module Spectroscope
2
+
3
+ #
4
+ class World < Module
5
+ end
6
+
7
+ end
@@ -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
@@ -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