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.
@@ -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