xspec 0.0.2 → 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.
- checksums.yaml +4 -4
- data/README.md +22 -50
- data/bin/xspec +4 -2
- data/lib/xspec.rb +18 -11
- data/lib/xspec/data_structures.rb +15 -15
- data/lib/xspec/defaults.rb +6 -6
- data/lib/xspec/dsl.rb +5 -0
- data/lib/xspec/evaluators.rb +316 -22
- data/lib/xspec/notifiers.rb +2 -0
- data/lib/xspec/schedulers.rb +39 -0
- data/spec/integration/rspec_expectations_spec.rb +3 -3
- data/spec/spec_helper.rb +0 -2
- data/spec/unit/assertion_spec.rb +7 -7
- data/spec/unit/doubles_spec.rb +93 -115
- data/xspec.gemspec +2 -2
- metadata +4 -4
- data/lib/xspec/assertion_contexts.rb +0 -382
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: xspec
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Xavier Shay
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-07-
|
11
|
+
date: 2014-07-29 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: yeah
|
14
14
|
email:
|
@@ -21,13 +21,13 @@ files:
|
|
21
21
|
- README.md
|
22
22
|
- bin/xspec
|
23
23
|
- lib/xspec.rb
|
24
|
-
- lib/xspec/assertion_contexts.rb
|
25
24
|
- lib/xspec/autorun.rb
|
26
25
|
- lib/xspec/data_structures.rb
|
27
26
|
- lib/xspec/defaults.rb
|
28
27
|
- lib/xspec/dsl.rb
|
29
28
|
- lib/xspec/evaluators.rb
|
30
29
|
- lib/xspec/notifiers.rb
|
30
|
+
- lib/xspec/schedulers.rb
|
31
31
|
- spec/all_specs.rb
|
32
32
|
- spec/integration/rspec_expectations_spec.rb
|
33
33
|
- spec/spec_helper.rb
|
@@ -49,7 +49,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
49
49
|
requirements:
|
50
50
|
- - ">="
|
51
51
|
- !ruby/object:Gem::Version
|
52
|
-
version: 1.
|
52
|
+
version: 2.1.0
|
53
53
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
54
54
|
requirements:
|
55
55
|
- - ">="
|
@@ -1,382 +0,0 @@
|
|
1
|
-
# # Assertion Contexts
|
2
|
-
|
3
|
-
# Assertion contexts are composed together into a context stack. The final
|
4
|
-
# stack has a single API method `call`, which is sent the unit of work to be
|
5
|
-
# executed and must return an array of `Failure` objects. It should not allow
|
6
|
-
# code-level exceptions to be raised, though should not block system exceptions
|
7
|
-
# (`SignalException`, `NoMemoryError`, etc).
|
8
|
-
module XSpec
|
9
|
-
module AssertionContext
|
10
|
-
# A stack is typically book-ended by the top and bottom contexts, so this
|
11
|
-
# helper is the most commond way to build up a custom stack.
|
12
|
-
def self.stack(&block)
|
13
|
-
Module.new do
|
14
|
-
include Bottom
|
15
|
-
instance_exec &block
|
16
|
-
include Top
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
# The bottom context executes the unit of work, with no error handling or
|
21
|
-
# extra behaviour. By separating this, all other contexts layered on top of
|
22
|
-
# this one can just call `super`, making them easy to compose.
|
23
|
-
module Bottom
|
24
|
-
def call(unit_of_work)
|
25
|
-
instance_exec(&unit_of_work.block)
|
26
|
-
[]
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
# The top should be included as the final module in a context stack. It is
|
31
|
-
# a catch all to make sure all standard exceptions have been handled and
|
32
|
-
# do not leak outside the stack.
|
33
|
-
module Top
|
34
|
-
def call(unit_of_work)
|
35
|
-
super
|
36
|
-
rescue => e
|
37
|
-
[CodeException.new(unit_of_work, e.message, e.backtrace)]
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
# ### Simple Assertions
|
42
|
-
#
|
43
|
-
# This simple context provides very straight-forward assertion methods.
|
44
|
-
module Simple
|
45
|
-
class AssertionFailed < RuntimeError
|
46
|
-
attr_reader :message, :backtrace
|
47
|
-
|
48
|
-
def initialize(message, backtrace)
|
49
|
-
@message = message
|
50
|
-
@backtrace = backtrace
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
def call(unit_of_work)
|
55
|
-
super
|
56
|
-
rescue AssertionFailed => e
|
57
|
-
[Failure.new(unit_of_work, e.message, e.backtrace)]
|
58
|
-
end
|
59
|
-
|
60
|
-
def assert(proposition, message=nil)
|
61
|
-
unless proposition
|
62
|
-
message ||= 'assertion failed'
|
63
|
-
|
64
|
-
_raise message
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
def assert_equal(expected, actual)
|
69
|
-
unless expected == actual
|
70
|
-
message ||= <<-EOS.chomp
|
71
|
-
want: #{expected.inspect}
|
72
|
-
got: #{actual.inspect}
|
73
|
-
EOS
|
74
|
-
|
75
|
-
_raise message
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
def assert_include(expected, output)
|
80
|
-
assert output.include?(expected),
|
81
|
-
"#{expected.inspect} not present in: #{output.inspect}"
|
82
|
-
end
|
83
|
-
|
84
|
-
def fail(message = nil)
|
85
|
-
message ||= 'failed'
|
86
|
-
|
87
|
-
_raise message
|
88
|
-
end
|
89
|
-
|
90
|
-
private
|
91
|
-
|
92
|
-
def _raise(message)
|
93
|
-
raise AssertionFailed.new(message, caller)
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
# ### Doubles
|
98
|
-
#
|
99
|
-
# The doubles module provides test doubles that can be used in-place of
|
100
|
-
# real objects.
|
101
|
-
module Doubles
|
102
|
-
DoubleFailure = Class.new(RuntimeError)
|
103
|
-
|
104
|
-
def call(unit_of_work)
|
105
|
-
super
|
106
|
-
rescue DoubleFailure => e
|
107
|
-
[Failure.new(unit_of_work, e.message, e.backtrace)]
|
108
|
-
end
|
109
|
-
|
110
|
-
# It can be configured with a few options:
|
111
|
-
#
|
112
|
-
# * `auto_verify` calls `assert_exhausted` on all created doubles after a
|
113
|
-
# unit of work executes successfully to ensure that all expectations that
|
114
|
-
# were set were actually called.
|
115
|
-
# * `strict` forbids doubling of classes that have not been loaded. This
|
116
|
-
# should generally be enabled when doing a full spec run, and disabled
|
117
|
-
# when running specs in isolation.
|
118
|
-
#
|
119
|
-
# The `with` method returns a module that can be included in a stack.
|
120
|
-
def self.with(*opts)
|
121
|
-
modules = [self] + opts.map {|x| {
|
122
|
-
auto_verify: AutoVerify,
|
123
|
-
strict: Strict
|
124
|
-
}.fetch(x) }
|
125
|
-
|
126
|
-
|
127
|
-
Module.new do
|
128
|
-
modules.each do |m|
|
129
|
-
include m
|
130
|
-
end
|
131
|
-
end
|
132
|
-
end
|
133
|
-
|
134
|
-
# An instance double stands in for an instance of the given class
|
135
|
-
# reference, given as a string. The class does not need to be loaded, but
|
136
|
-
# if it is then only public instance methods defined on the class are
|
137
|
-
# able to be expected.
|
138
|
-
def instance_double(klass)
|
139
|
-
_double(klass, InstanceReference)
|
140
|
-
end
|
141
|
-
|
142
|
-
# Simarly, a class double validates that class responds to all expected
|
143
|
-
# methods, if that class has been loaded.
|
144
|
-
def class_double(klass)
|
145
|
-
_double(klass, ClassReference)
|
146
|
-
end
|
147
|
-
|
148
|
-
# If the doubled class has not been loaded, a null object reference is
|
149
|
-
# used that allows expecting of all methods.
|
150
|
-
def _double(klass, type)
|
151
|
-
ref = if self.class.const_defined?(klass)
|
152
|
-
type.new(self.class.const_get(klass))
|
153
|
-
else
|
154
|
-
StringReference.new(klass)
|
155
|
-
end
|
156
|
-
|
157
|
-
Double.new(ref)
|
158
|
-
end
|
159
|
-
|
160
|
-
# To set up an expectation on a double, call the expected method an
|
161
|
-
# arguments on the proxy object returned by `expect`. If a return value
|
162
|
-
# is desired, it can be supplied as a block, for example:
|
163
|
-
# `expect(double).some_method(1, 2) { "return value" }`
|
164
|
-
def expect(obj)
|
165
|
-
Recorder.new(obj, :_expect)
|
166
|
-
end
|
167
|
-
|
168
|
-
# An allowed method will never be validated by `assert_exhausted`.
|
169
|
-
# Matching expectations are always given precendence to allows.
|
170
|
-
def allow(obj)
|
171
|
-
Recorder.new(obj, :_allow)
|
172
|
-
end
|
173
|
-
|
174
|
-
class Recorder
|
175
|
-
def initialize(double, method)
|
176
|
-
@double = double
|
177
|
-
@method = method
|
178
|
-
end
|
179
|
-
|
180
|
-
def method_missing(*args, &ret)
|
181
|
-
@double.__send__(@method, args, &(ret || ->{}))
|
182
|
-
end
|
183
|
-
end
|
184
|
-
|
185
|
-
# Since the double object inherits from `BasicObject`, virtually every
|
186
|
-
# method call will be routed through `method_missing`. From there, the
|
187
|
-
# call can be checked against the expectations that were setup at the
|
188
|
-
# beginning of a spec.
|
189
|
-
class Double < BasicObject
|
190
|
-
def initialize(klass)
|
191
|
-
@klass = klass
|
192
|
-
@expected = []
|
193
|
-
@allowed = []
|
194
|
-
end
|
195
|
-
|
196
|
-
def method_missing(*actual_args)
|
197
|
-
expected_index = @expected.find_index {|expected_args, ret|
|
198
|
-
expected_args == actual_args
|
199
|
-
}
|
200
|
-
|
201
|
-
matching_message = if expected_index
|
202
|
-
@expected.delete_at(expected_index)
|
203
|
-
else
|
204
|
-
@allowed.detect {|expected_args, ret|
|
205
|
-
expected_args == actual_args
|
206
|
-
}
|
207
|
-
end
|
208
|
-
|
209
|
-
if matching_message
|
210
|
-
matching_message[1].call
|
211
|
-
else
|
212
|
-
name, rest = *actual_args
|
213
|
-
::Kernel.raise DoubleFailure, "Unexpectedly received: %s(%s)" % [
|
214
|
-
name,
|
215
|
-
[*rest].map(&:inspect).join(", ")
|
216
|
-
]
|
217
|
-
end
|
218
|
-
end
|
219
|
-
|
220
|
-
# The methods needed on this object to set it up and verify it are
|
221
|
-
# prefixed by `_` to try to ensure they don't clash with any method
|
222
|
-
# expectations. While not fail-safe, users should only be using
|
223
|
-
# expectations for a public API, and `_` is traditionally only used
|
224
|
-
# for private methods (if at all).
|
225
|
-
def _expect(args, &ret)
|
226
|
-
@klass.validate_call! args
|
227
|
-
|
228
|
-
@expected << [args, ret]
|
229
|
-
end
|
230
|
-
|
231
|
-
def _allow(args, &ret)
|
232
|
-
@klass.validate_call! args
|
233
|
-
|
234
|
-
@allowed << [args, ret]
|
235
|
-
end
|
236
|
-
|
237
|
-
def _verify
|
238
|
-
return if @expected.empty?
|
239
|
-
|
240
|
-
::Kernel.raise DoubleFailure, "%s double did not receive:\n%s" % [
|
241
|
-
@klass.to_s,
|
242
|
-
@expected.map {|(name, *args), _|
|
243
|
-
" %s(%s)" % [name, args.map(&:inspect).join(", ")]
|
244
|
-
}.join("\n")
|
245
|
-
]
|
246
|
-
end
|
247
|
-
end
|
248
|
-
|
249
|
-
# A reference can be thought of as a "backing object" for a double. It
|
250
|
-
# provides an API to validate that a method being expected actually
|
251
|
-
# exists - the implementation is different for the different types of
|
252
|
-
# doubles.
|
253
|
-
class Reference
|
254
|
-
def initialize(klass)
|
255
|
-
@klass = klass
|
256
|
-
end
|
257
|
-
|
258
|
-
def validate_call!(args)
|
259
|
-
end
|
260
|
-
|
261
|
-
def to_s
|
262
|
-
@klass.to_s
|
263
|
-
end
|
264
|
-
end
|
265
|
-
|
266
|
-
# A string reference is the "null object" of references, allowing all
|
267
|
-
# methods to be expected. It is used when nothing is known about the
|
268
|
-
# referenced class (such as when it has not been loaded).
|
269
|
-
class StringReference < Reference
|
270
|
-
end
|
271
|
-
|
272
|
-
# Class and Instance references are backed by loaded classes, and
|
273
|
-
# restrict the messages that can be expected on a double.
|
274
|
-
class ClassReference < Reference
|
275
|
-
def validate_call!(args)
|
276
|
-
name, rest = *args
|
277
|
-
|
278
|
-
unless @klass.respond_to?(name)
|
279
|
-
raise DoubleFailure,
|
280
|
-
"#{@klass}.#{name} is unimplemented or not public"
|
281
|
-
end
|
282
|
-
end
|
283
|
-
end
|
284
|
-
|
285
|
-
class InstanceReference < Reference
|
286
|
-
def validate_call!(args)
|
287
|
-
name, rest = *args
|
288
|
-
|
289
|
-
unless @klass.public_instance_methods.include?(name)
|
290
|
-
raise DoubleFailure,
|
291
|
-
"#{@klass}##{name} is unimplemented or not public"
|
292
|
-
end
|
293
|
-
end
|
294
|
-
end
|
295
|
-
|
296
|
-
# The `:strict` option mixes in this `Strict` module, which raises rather
|
297
|
-
# than create `StringReference`s for unknown classes.
|
298
|
-
module Strict
|
299
|
-
def _double(klass, type)
|
300
|
-
ref = if self.class.const_defined?(klass)
|
301
|
-
type.new(self.class.const_get(klass))
|
302
|
-
else
|
303
|
-
raise DoubleFailure, "#{klass} is not a valid class name"
|
304
|
-
end
|
305
|
-
|
306
|
-
super
|
307
|
-
end
|
308
|
-
end
|
309
|
-
|
310
|
-
# An assertion is provided to validate that all expected methods were
|
311
|
-
# called on a double.
|
312
|
-
def assert_exhausted(obj)
|
313
|
-
obj._verify
|
314
|
-
end
|
315
|
-
|
316
|
-
# Most of the time, `assert_exhausted` will not be called directly, since
|
317
|
-
# the `:auto_verify` option can be used to call it by default on all
|
318
|
-
# doubles. That option mixes in this `AutoVerify` module to augment
|
319
|
-
# methods necessary for this behaviour.
|
320
|
-
module AutoVerify
|
321
|
-
def initialize
|
322
|
-
@doubles = []
|
323
|
-
end
|
324
|
-
|
325
|
-
def call(unit_of_work)
|
326
|
-
result = super
|
327
|
-
|
328
|
-
if result.empty?
|
329
|
-
@doubles.each do |double|
|
330
|
-
assert_exhausted double
|
331
|
-
end
|
332
|
-
end
|
333
|
-
|
334
|
-
result
|
335
|
-
rescue DoubleFailure => e
|
336
|
-
[Failure.new(unit_of_work, e.message, e.backtrace)]
|
337
|
-
end
|
338
|
-
|
339
|
-
def class_double(klass)
|
340
|
-
x = super
|
341
|
-
@doubles << x
|
342
|
-
x
|
343
|
-
end
|
344
|
-
|
345
|
-
def instance_double(klass)
|
346
|
-
x = super
|
347
|
-
@doubles << x
|
348
|
-
x
|
349
|
-
end
|
350
|
-
end
|
351
|
-
end
|
352
|
-
|
353
|
-
# ### RSpec Integration
|
354
|
-
#
|
355
|
-
# This RSpec adapter shows two useful techniques: dynamic library loading
|
356
|
-
# which removes RSpec as a direct dependency, and use of the `mixin`
|
357
|
-
# method to further extend the target context.
|
358
|
-
module RSpecExpectations
|
359
|
-
def self.included(context)
|
360
|
-
begin
|
361
|
-
require 'rspec/expectations'
|
362
|
-
require 'rspec/matchers'
|
363
|
-
rescue LoadError
|
364
|
-
raise "RSpec is not available, cannot use RSpec assertion context."
|
365
|
-
end
|
366
|
-
|
367
|
-
context.include(RSpec::Matchers)
|
368
|
-
end
|
369
|
-
|
370
|
-
def call(unit_of_work)
|
371
|
-
super
|
372
|
-
rescue RSpec::Expectations::ExpectationNotMetError => e
|
373
|
-
[Failure.new(unit_of_work, e.message, e.backtrace)]
|
374
|
-
end
|
375
|
-
end
|
376
|
-
|
377
|
-
DEFAULT = stack do
|
378
|
-
include Simple
|
379
|
-
include Doubles.with(:auto_verify)
|
380
|
-
end
|
381
|
-
end
|
382
|
-
end
|