crspec 0.1.2 → 0.1.3
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/CHANGELOG.md +86 -0
- data/README.md +1 -1
- data/lib/crspec/cli.rb +86 -5
- data/lib/crspec/configuration.rb +62 -9
- data/lib/crspec/dsl.rb +66 -2
- data/lib/crspec/example.rb +60 -6
- data/lib/crspec/example_group.rb +363 -63
- data/lib/crspec/execution_context.rb +10 -6
- data/lib/crspec/expectations.rb +62 -2
- data/lib/crspec/file_fixtures.rb +18 -0
- data/lib/crspec/formatters/progress_formatter.rb +37 -14
- data/lib/crspec/matchers.rb +642 -0
- data/lib/crspec/mock/argument_matchers.rb +157 -0
- data/lib/crspec/mock/double.rb +246 -13
- data/lib/crspec/mock/interceptor.rb +32 -9
- data/lib/crspec/mock/space.rb +14 -0
- data/lib/crspec/process_runner.rb +332 -0
- data/lib/crspec/rails/assets_shim.rb +33 -0
- data/lib/crspec/rails/database_isolation.rb +179 -8
- data/lib/crspec/rails/parallel.rb +29 -29
- data/lib/crspec/rails/request_helpers.rb +70 -14
- data/lib/crspec/rails/system_server.rb +7 -0
- data/lib/crspec/rails/warden_shim.rb +18 -0
- data/lib/crspec/runner.rb +143 -29
- data/lib/crspec/shared_examples.rb +70 -0
- data/lib/crspec/status_persistence.rb +46 -0
- data/lib/crspec/transpiler/cli.rb +106 -15
- data/lib/crspec/transpiler/rewriter.rb +179 -25
- data/lib/crspec/version.rb +1 -1
- data/lib/crspec.rb +6 -0
- data/lib/rspec/core.rb +2 -0
- data/lib/rspec/expectations.rb +2 -0
- data/lib/rspec/mocks.rb +2 -0
- data/lib/rspec/rails.rb +2 -0
- data/lib/rspec.rb +2 -0
- metadata +27 -1
data/lib/crspec/example_group.rb
CHANGED
|
@@ -4,22 +4,28 @@ require_relative "expectations"
|
|
|
4
4
|
require_relative "example"
|
|
5
5
|
require_relative "mock/double"
|
|
6
6
|
require_relative "configuration"
|
|
7
|
+
require_relative "file_fixtures"
|
|
8
|
+
require_relative "shared_examples"
|
|
7
9
|
|
|
8
10
|
module Crspec
|
|
9
11
|
class ExampleGroup
|
|
10
12
|
include Expectations
|
|
11
13
|
include Mock::DSL
|
|
14
|
+
include SharedDSL
|
|
12
15
|
|
|
13
|
-
attr_reader :description, :metadata, :parent, :examples, :children, :before_hooks, :after_hooks
|
|
16
|
+
attr_reader :description, :metadata, :parent, :examples, :children, :before_hooks, :after_hooks, :around_hooks
|
|
17
|
+
attr_accessor :described_module
|
|
14
18
|
|
|
15
19
|
def initialize(description, metadata = {}, parent = nil)
|
|
16
20
|
@description = description
|
|
21
|
+
@described_module = description.is_a?(Module) ? description : nil
|
|
17
22
|
@metadata = metadata.freeze
|
|
18
23
|
@parent = parent
|
|
19
24
|
@examples = []
|
|
20
25
|
@children = []
|
|
21
26
|
@before_hooks = []
|
|
22
27
|
@after_hooks = []
|
|
28
|
+
@around_hooks = []
|
|
23
29
|
@let_blocks = {}
|
|
24
30
|
@eager_lets = []
|
|
25
31
|
@included_modules = []
|
|
@@ -31,24 +37,111 @@ module Crspec
|
|
|
31
37
|
group
|
|
32
38
|
end
|
|
33
39
|
|
|
34
|
-
def describe(
|
|
35
|
-
|
|
40
|
+
def describe(*args, **kwargs, &block)
|
|
41
|
+
metadata = kwargs.dup
|
|
42
|
+
metadata.merge!(args.pop) if args.last.is_a?(Hash)
|
|
43
|
+
described = args.first.is_a?(Module) ? args.first : nil
|
|
44
|
+
description = described && args.size == 1 ? described : args.map(&:to_s).join(" ")
|
|
45
|
+
child = self.class.new(description, metadata, self)
|
|
46
|
+
child.described_module = described
|
|
47
|
+
child.instance_eval(&block) if block
|
|
36
48
|
@children << child
|
|
37
49
|
child
|
|
38
50
|
end
|
|
39
51
|
alias context describe
|
|
40
52
|
|
|
53
|
+
def xdescribe(*args, **kwargs, &block)
|
|
54
|
+
child = describe(*args, **kwargs, &block)
|
|
55
|
+
child.mark_pending!
|
|
56
|
+
child
|
|
57
|
+
end
|
|
58
|
+
alias xcontext xdescribe
|
|
59
|
+
|
|
60
|
+
def fdescribe(*args, **kwargs, &block)
|
|
61
|
+
child = describe(*args, **kwargs, &block)
|
|
62
|
+
child.mark_focused!
|
|
63
|
+
child
|
|
64
|
+
end
|
|
65
|
+
alias fcontext fdescribe
|
|
66
|
+
|
|
41
67
|
def it(description = nil, metadata = {}, &block)
|
|
42
|
-
example = Example.new(description || "unnamed example", metadata, self, block
|
|
68
|
+
example = Example.new(description || "unnamed example", metadata, self, block,
|
|
69
|
+
pending: pending?)
|
|
43
70
|
@examples << example
|
|
44
71
|
example
|
|
45
72
|
end
|
|
46
73
|
alias specify it
|
|
47
74
|
|
|
75
|
+
def xit(description = nil, metadata = {}, &block)
|
|
76
|
+
example = Example.new(description || "unnamed example", metadata, self, block,
|
|
77
|
+
pending: true)
|
|
78
|
+
@examples << example
|
|
79
|
+
example
|
|
80
|
+
end
|
|
81
|
+
alias pending xit
|
|
82
|
+
alias skip xit
|
|
83
|
+
|
|
84
|
+
def fit(description = nil, metadata = {}, &block)
|
|
85
|
+
example = it(description, metadata, &block)
|
|
86
|
+
example.focused = true
|
|
87
|
+
example
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def mark_pending!
|
|
91
|
+
@pending = true
|
|
92
|
+
@examples.each { |ex| ex.instance_variable_set(:@pending, true) }
|
|
93
|
+
@children.each(&:mark_pending!)
|
|
94
|
+
self
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def pending?
|
|
98
|
+
!!@pending || (parent ? parent.pending? : false)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def mark_focused!
|
|
102
|
+
@focused = true
|
|
103
|
+
@examples.each { |ex| ex.focused = true }
|
|
104
|
+
@children.each(&:mark_focused!)
|
|
105
|
+
self
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def focused?
|
|
109
|
+
!!@focused
|
|
110
|
+
end
|
|
111
|
+
|
|
48
112
|
def include(mod)
|
|
49
113
|
@included_modules << mod
|
|
50
114
|
end
|
|
51
115
|
|
|
116
|
+
def render_views(*args)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def private(*args)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def public(*args)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def protected(*args)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def attr_reader(*names)
|
|
129
|
+
names.each do |name|
|
|
130
|
+
let(name) { instance_variable_get("@#{name}") }
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def attr_writer(*names)
|
|
135
|
+
names.each do |name|
|
|
136
|
+
define_method("#{name}=") { |val| instance_variable_set("@#{name}", val) }
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def attr_accessor(*names)
|
|
141
|
+
attr_reader(*names)
|
|
142
|
+
attr_writer(*names)
|
|
143
|
+
end
|
|
144
|
+
|
|
52
145
|
def let(name, &block)
|
|
53
146
|
@let_blocks[name.to_sym] = block
|
|
54
147
|
end
|
|
@@ -67,79 +160,276 @@ module Crspec
|
|
|
67
160
|
end
|
|
68
161
|
end
|
|
69
162
|
|
|
70
|
-
|
|
71
|
-
|
|
163
|
+
FORBIDDEN_HOOK_SCOPES = %i[all context suite].freeze
|
|
164
|
+
|
|
165
|
+
def before(*args, **kwargs, &block)
|
|
166
|
+
reject_serial_scope!(:before, args)
|
|
167
|
+
@before_hooks << { block: block, filters: args, kwfilters: kwargs }
|
|
72
168
|
end
|
|
73
169
|
|
|
74
|
-
def after(
|
|
75
|
-
|
|
170
|
+
def after(*args, **kwargs, &block)
|
|
171
|
+
reject_serial_scope!(:after, args)
|
|
172
|
+
@after_hooks.unshift({ block: block, filters: args, kwfilters: kwargs })
|
|
76
173
|
end
|
|
77
174
|
|
|
78
|
-
def
|
|
79
|
-
|
|
80
|
-
|
|
175
|
+
def reject_serial_scope!(hook_name, args)
|
|
176
|
+
scope = args.first
|
|
177
|
+
return unless scope.is_a?(Symbol) && FORBIDDEN_HOOK_SCOPES.include?(scope)
|
|
81
178
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
curr = curr.parent
|
|
87
|
-
end
|
|
179
|
+
raise MigrationError, <<~MSG
|
|
180
|
+
#{hook_name}(:#{scope}) is not supported by Crspec: shared state across
|
|
181
|
+
examples is inherently serial and breaks the P processes x N threads x
|
|
182
|
+
M fibers execution model.
|
|
88
183
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
184
|
+
Migrate to per-example setup instead:
|
|
185
|
+
- use before(:each) if the setup is cheap
|
|
186
|
+
- use let/let! for lazily-built (memoized per example) values
|
|
187
|
+
|
|
188
|
+
Run `crspec-transpile --analyze` on your suite to find and rewrite all
|
|
189
|
+
occurrences automatically.
|
|
190
|
+
MSG
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def around(*args, **kwargs, &block)
|
|
194
|
+
@around_hooks << { block: block, filters: args, kwfilters: kwargs }
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def finalize!
|
|
198
|
+
ancestor_metadata
|
|
199
|
+
ancestor_let_blocks
|
|
200
|
+
ancestor_eager_lets
|
|
201
|
+
ancestor_included_modules
|
|
202
|
+
described_class
|
|
203
|
+
%i[before after around].each { |type| raw_hooks_for(type) }
|
|
204
|
+
|
|
205
|
+
example_types = examples.map { |ex| effective_type_for(ex) }.uniq
|
|
206
|
+
example_types.each do |type|
|
|
207
|
+
%i[before after around].each { |hook_type| filtered_hooks(hook_type, type) }
|
|
208
|
+
modules_for_type(type)
|
|
209
|
+
example_class_for(type)
|
|
92
210
|
end
|
|
93
211
|
|
|
94
|
-
|
|
212
|
+
children.each(&:finalize!)
|
|
213
|
+
self
|
|
214
|
+
end
|
|
95
215
|
|
|
96
|
-
|
|
216
|
+
def ancestor_hooks(type, example = nil)
|
|
217
|
+
filtered_hooks(type, example ? effective_type_for(example) : ancestor_metadata[:type])
|
|
97
218
|
end
|
|
98
219
|
|
|
99
220
|
def ancestor_let_blocks
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
221
|
+
@ancestor_let_blocks ||= begin
|
|
222
|
+
blocks = {}
|
|
223
|
+
ancestor_chain.each do |grp|
|
|
224
|
+
blocks.merge!(grp.instance_variable_get(:@let_blocks))
|
|
225
|
+
end
|
|
226
|
+
unless blocks.key?(:subject)
|
|
227
|
+
desc_cls = described_class
|
|
228
|
+
if desc_cls.is_a?(Class)
|
|
229
|
+
blocks[:subject] = proc { desc_cls.new rescue nil }
|
|
230
|
+
elsif desc_cls
|
|
231
|
+
blocks[:subject] = proc { desc_cls }
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
blocks.freeze
|
|
109
235
|
end
|
|
110
|
-
blocks
|
|
111
236
|
end
|
|
112
237
|
|
|
113
238
|
def ancestor_eager_lets
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
eager.concat(curr.instance_variable_get(:@eager_lets))
|
|
118
|
-
curr = curr.parent
|
|
119
|
-
end
|
|
120
|
-
eager
|
|
239
|
+
@ancestor_eager_lets ||= ancestor_chain.flat_map do |grp|
|
|
240
|
+
grp.instance_variable_get(:@eager_lets)
|
|
241
|
+
end.freeze
|
|
121
242
|
end
|
|
122
243
|
|
|
123
244
|
def ancestor_included_modules
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
245
|
+
@ancestor_included_modules ||= ancestor_chain.flat_map do |grp|
|
|
246
|
+
grp.instance_variable_get(:@included_modules) || []
|
|
247
|
+
end.freeze
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def ancestor_metadata
|
|
251
|
+
@ancestor_metadata ||= begin
|
|
252
|
+
meta = {}
|
|
253
|
+
ancestor_chain.each do |grp|
|
|
254
|
+
meta.merge!(grp.metadata || {})
|
|
255
|
+
end
|
|
256
|
+
meta.freeze
|
|
130
257
|
end
|
|
131
|
-
|
|
132
|
-
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def modules_for_example(example)
|
|
261
|
+
modules_for_type(effective_type_for(example))
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def described_class
|
|
265
|
+
return @described_class if defined?(@described_class)
|
|
266
|
+
|
|
267
|
+
@described_class = begin
|
|
268
|
+
found = nil
|
|
269
|
+
curr = self
|
|
270
|
+
while curr
|
|
271
|
+
if curr.described_module
|
|
272
|
+
found = curr.described_module
|
|
273
|
+
break
|
|
274
|
+
end
|
|
275
|
+
curr = curr.parent
|
|
276
|
+
end
|
|
277
|
+
found
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def effective_type_for(example)
|
|
282
|
+
meta = example.metadata || {}
|
|
283
|
+
meta.key?(:type) ? meta[:type] : ancestor_metadata[:type]
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
def run_eager_lets(instance)
|
|
287
|
+
ancestor_eager_lets.each do |name|
|
|
288
|
+
instance.send(name)
|
|
133
289
|
end
|
|
134
|
-
mods
|
|
135
290
|
end
|
|
136
291
|
|
|
137
292
|
def create_instance(example)
|
|
138
|
-
|
|
293
|
+
klass = example_class_for(effective_type_for(example))
|
|
294
|
+
inst = klass.new(example)
|
|
295
|
+
|
|
296
|
+
klass.setup_callbacks.each do |cb|
|
|
297
|
+
if cb.is_a?(Symbol) || cb.is_a?(String)
|
|
298
|
+
inst.send(cb)
|
|
299
|
+
elsif cb.respond_to?(:call)
|
|
300
|
+
inst.instance_exec(&cb)
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
inst
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
private
|
|
308
|
+
|
|
309
|
+
def ancestor_chain
|
|
310
|
+
@ancestor_chain ||= begin
|
|
311
|
+
chain = []
|
|
312
|
+
curr = self
|
|
313
|
+
while curr
|
|
314
|
+
chain.unshift(curr)
|
|
315
|
+
curr = curr.parent
|
|
316
|
+
end
|
|
317
|
+
chain.freeze
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
def raw_hooks_for(type)
|
|
322
|
+
@raw_hooks_for ||= {}
|
|
323
|
+
@raw_hooks_for[type] ||= begin
|
|
324
|
+
raw_hooks = []
|
|
325
|
+
if type == :before
|
|
326
|
+
raw_hooks.concat(Crspec.configuration.before_hooks)
|
|
327
|
+
elsif type == :around
|
|
328
|
+
raw_hooks.concat(Crspec.configuration.around_hooks)
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
ancestor_chain.each do |grp|
|
|
332
|
+
list = case type
|
|
333
|
+
when :before then grp.before_hooks
|
|
334
|
+
when :after then grp.after_hooks
|
|
335
|
+
when :around then grp.around_hooks
|
|
336
|
+
end
|
|
337
|
+
raw_hooks.concat(list)
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
raw_hooks.concat(Crspec.configuration.after_hooks) if type == :after
|
|
341
|
+
raw_hooks.freeze
|
|
342
|
+
end
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
def filtered_hooks(hook_type, meta_type)
|
|
346
|
+
@filtered_hooks ||= {}
|
|
347
|
+
@filtered_hooks[[hook_type, meta_type]] ||= raw_hooks_for(hook_type).filter_map do |hook|
|
|
348
|
+
if hook.is_a?(Hash)
|
|
349
|
+
blk = hook[:block]
|
|
350
|
+
kw = hook[:kwfilters] || {}
|
|
351
|
+
filters = hook[:filters] || []
|
|
352
|
+
|
|
353
|
+
target_type = kw[:type] || (filters.first.is_a?(Hash) && filters.first[:type])
|
|
354
|
+
next if target_type && meta_type != target_type
|
|
355
|
+
|
|
356
|
+
blk
|
|
357
|
+
else
|
|
358
|
+
hook
|
|
359
|
+
end
|
|
360
|
+
end.freeze
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
def modules_for_type(meta_type)
|
|
364
|
+
@modules_for_type ||= {}
|
|
365
|
+
@modules_for_type[meta_type] ||= begin
|
|
366
|
+
all_entries = Crspec.configuration.included_modules + ancestor_included_modules
|
|
367
|
+
|
|
368
|
+
all_entries.filter_map do |entry|
|
|
369
|
+
if entry.is_a?(Hash)
|
|
370
|
+
mod = entry[:module]
|
|
371
|
+
kw = entry[:kwfilters] || {}
|
|
372
|
+
filters = entry[:filters] || []
|
|
373
|
+
|
|
374
|
+
target_type = kw[:type] || (filters.first.is_a?(Hash) && filters.first[:type])
|
|
375
|
+
next if target_type && meta_type != target_type
|
|
376
|
+
|
|
377
|
+
mod
|
|
378
|
+
else
|
|
379
|
+
entry
|
|
380
|
+
end
|
|
381
|
+
end.uniq.freeze
|
|
382
|
+
end
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
def example_class_for(meta_type)
|
|
386
|
+
@example_class_for ||= {}
|
|
387
|
+
@example_class_for[meta_type] ||= build_example_class(meta_type)
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
def build_example_class(meta_type)
|
|
391
|
+
modules_to_include = modules_for_type(meta_type)
|
|
392
|
+
desc_cls = described_class
|
|
139
393
|
|
|
140
394
|
klass = Class.new do
|
|
395
|
+
define_singleton_method(:spec_type) { meta_type }
|
|
396
|
+
|
|
397
|
+
class << self
|
|
398
|
+
attr_writer :controller_class
|
|
399
|
+
|
|
400
|
+
def controller_class
|
|
401
|
+
@controller_class
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
def setup(*args, &block)
|
|
405
|
+
@setup_callbacks ||= []
|
|
406
|
+
@setup_callbacks << (block || args.first)
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
def teardown(*args, &block)
|
|
410
|
+
@teardown_callbacks ||= []
|
|
411
|
+
@teardown_callbacks << (block || args.first)
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
def setup_callbacks
|
|
415
|
+
@setup_callbacks || []
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
def teardown_callbacks
|
|
419
|
+
@teardown_callbacks || []
|
|
420
|
+
end
|
|
421
|
+
end
|
|
422
|
+
|
|
141
423
|
include Expectations
|
|
142
424
|
include Mock::DSL
|
|
425
|
+
include FileFixtures
|
|
426
|
+
|
|
427
|
+
if meta_type == :controller
|
|
428
|
+
include ActionController::TestCase::Behavior if defined?(ActionController::TestCase::Behavior)
|
|
429
|
+
elsif meta_type == :request
|
|
430
|
+
include ActionDispatch::Integration::Runner if defined?(ActionDispatch::Integration::Runner)
|
|
431
|
+
include ActionDispatch::IntegrationTest::Behavior if defined?(ActionDispatch::IntegrationTest::Behavior)
|
|
432
|
+
end
|
|
143
433
|
|
|
144
434
|
modules_to_include.each do |mod|
|
|
145
435
|
include mod
|
|
@@ -147,8 +437,25 @@ module Crspec
|
|
|
147
437
|
|
|
148
438
|
attr_reader :__crspec_example__
|
|
149
439
|
|
|
150
|
-
|
|
440
|
+
define_method(:initialize) do |example|
|
|
151
441
|
@__crspec_example__ = example
|
|
442
|
+
meta_type = self.class.spec_type
|
|
443
|
+
is_controller = meta_type == :controller || (desc_cls.is_a?(Class) && desc_cls.ancestors.map(&:to_s).include?("ActionController::Metal"))
|
|
444
|
+
if is_controller
|
|
445
|
+
ctrl_cls = desc_cls
|
|
446
|
+
begin
|
|
447
|
+
@controller = ctrl_cls.new if ctrl_cls.is_a?(Class)
|
|
448
|
+
rescue StandardError
|
|
449
|
+
# ignore
|
|
450
|
+
end
|
|
451
|
+
if defined?(ActionDispatch::TestRequest)
|
|
452
|
+
@request = ActionDispatch::TestRequest.create rescue nil
|
|
453
|
+
@response = ActionDispatch::TestResponse.new rescue nil
|
|
454
|
+
end
|
|
455
|
+
if defined?(::Rails) && ::Rails.application
|
|
456
|
+
@routes = ::Rails.application.routes
|
|
457
|
+
end
|
|
458
|
+
end
|
|
152
459
|
end
|
|
153
460
|
|
|
154
461
|
def execution_context
|
|
@@ -156,13 +463,14 @@ module Crspec
|
|
|
156
463
|
end
|
|
157
464
|
alias current_context execution_context
|
|
158
465
|
|
|
159
|
-
|
|
160
|
-
|
|
466
|
+
define_method(:described_class) do
|
|
467
|
+
desc_cls
|
|
161
468
|
end
|
|
162
469
|
end
|
|
163
470
|
|
|
164
|
-
|
|
165
|
-
|
|
471
|
+
klass.controller_class = desc_cls if desc_cls.is_a?(Class)
|
|
472
|
+
|
|
473
|
+
ancestor_let_blocks.each do |let_name, let_proc|
|
|
166
474
|
current_proc = let_proc
|
|
167
475
|
klass.define_method(let_name) do
|
|
168
476
|
ExecutionContext.current.fetch_memoized(let_name) do
|
|
@@ -171,15 +479,7 @@ module Crspec
|
|
|
171
479
|
end
|
|
172
480
|
end
|
|
173
481
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
# Evaluate eager lets
|
|
177
|
-
eager_lets = ancestor_eager_lets
|
|
178
|
-
eager_lets.each do |name|
|
|
179
|
-
inst.send(name)
|
|
180
|
-
end
|
|
181
|
-
|
|
182
|
-
inst
|
|
482
|
+
klass
|
|
183
483
|
end
|
|
184
484
|
end
|
|
185
485
|
end
|
|
@@ -14,13 +14,17 @@ module Crspec
|
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def self.isolate(example_id, metadata = {})
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
Fiber
|
|
17
|
+
new_context = new(example_id, metadata, Fiber[STORAGE_KEY])
|
|
18
|
+
old_context = Fiber[STORAGE_KEY]
|
|
19
|
+
old_space = Fiber[Mock::Space::STORAGE_KEY]
|
|
20
|
+
Fiber[STORAGE_KEY] = new_context
|
|
21
|
+
Fiber[Mock::Space::STORAGE_KEY] = nil
|
|
22
|
+
begin
|
|
22
23
|
yield new_context
|
|
23
|
-
|
|
24
|
+
ensure
|
|
25
|
+
Fiber[STORAGE_KEY] = old_context
|
|
26
|
+
Fiber[Mock::Space::STORAGE_KEY] = old_space
|
|
27
|
+
end
|
|
24
28
|
end
|
|
25
29
|
|
|
26
30
|
def initialize(example_id, metadata = {}, parent = nil)
|
data/lib/crspec/expectations.rb
CHANGED
|
@@ -5,6 +5,42 @@ require_relative "matchers"
|
|
|
5
5
|
module Crspec
|
|
6
6
|
class ExpectationNotMetError < StandardError; end
|
|
7
7
|
|
|
8
|
+
class MultipleExpectationsNotMetError < ExpectationNotMetError
|
|
9
|
+
attr_reader :failures
|
|
10
|
+
|
|
11
|
+
def initialize(failures)
|
|
12
|
+
@failures = failures
|
|
13
|
+
summary = failures.each_with_index.map { |msg, i| " #{i + 1}) #{msg}" }.join("\n")
|
|
14
|
+
super("Got #{failures.size} failure(s):\n#{summary}")
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Fiber-local failure accumulator backing aggregate_failures.
|
|
19
|
+
module FailureAggregator
|
|
20
|
+
STORAGE_KEY = :crspec_failure_aggregator
|
|
21
|
+
|
|
22
|
+
def self.active?
|
|
23
|
+
!Fiber[STORAGE_KEY].nil?
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.record(message)
|
|
27
|
+
Fiber[STORAGE_KEY] << message
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.aggregate
|
|
31
|
+
previous = Fiber[STORAGE_KEY]
|
|
32
|
+
Fiber[STORAGE_KEY] = []
|
|
33
|
+
begin
|
|
34
|
+
yield
|
|
35
|
+
failures = Fiber[STORAGE_KEY]
|
|
36
|
+
raise MultipleExpectationsNotMetError, failures if failures.size > 1
|
|
37
|
+
raise ExpectationNotMetError, failures.first if failures.size == 1
|
|
38
|
+
ensure
|
|
39
|
+
Fiber[STORAGE_KEY] = previous
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
8
44
|
class ExpectationTarget
|
|
9
45
|
def initialize(actual)
|
|
10
46
|
@actual = actual
|
|
@@ -14,7 +50,10 @@ module Crspec
|
|
|
14
50
|
matcher ||= block
|
|
15
51
|
return matcher.setup_expect(@actual) if matcher.respond_to?(:setup_expect)
|
|
16
52
|
|
|
17
|
-
|
|
53
|
+
unless matcher.matches?(@actual)
|
|
54
|
+
fail_with(matcher.failure_message)
|
|
55
|
+
return false
|
|
56
|
+
end
|
|
18
57
|
|
|
19
58
|
true
|
|
20
59
|
end
|
|
@@ -23,22 +62,43 @@ module Crspec
|
|
|
23
62
|
matcher ||= block
|
|
24
63
|
if matcher.matches?(@actual)
|
|
25
64
|
msg = matcher.respond_to?(:failure_message_when_negated) ? matcher.failure_message_when_negated : "Expected matcher not to match"
|
|
26
|
-
|
|
65
|
+
fail_with(msg)
|
|
66
|
+
return false
|
|
27
67
|
end
|
|
28
68
|
true
|
|
29
69
|
end
|
|
30
70
|
|
|
31
71
|
alias to_not not_to
|
|
72
|
+
|
|
73
|
+
private
|
|
74
|
+
|
|
75
|
+
def fail_with(message)
|
|
76
|
+
if FailureAggregator.active?
|
|
77
|
+
FailureAggregator.record(message)
|
|
78
|
+
else
|
|
79
|
+
raise ExpectationNotMetError, message
|
|
80
|
+
end
|
|
81
|
+
end
|
|
32
82
|
end
|
|
33
83
|
|
|
34
84
|
module Expectations
|
|
35
85
|
include Matchers
|
|
36
86
|
|
|
87
|
+
ExpectationNotMetError = Crspec::ExpectationNotMetError
|
|
88
|
+
|
|
37
89
|
UNDEFINED = Object.new.freeze
|
|
38
90
|
|
|
39
91
|
def expect(value = UNDEFINED, &block)
|
|
40
92
|
actual = value.equal?(UNDEFINED) ? block : value
|
|
41
93
|
ExpectationTarget.new(actual)
|
|
42
94
|
end
|
|
95
|
+
|
|
96
|
+
def is_expected
|
|
97
|
+
expect(subject)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def aggregate_failures(_label = nil, &block)
|
|
101
|
+
FailureAggregator.aggregate(&block)
|
|
102
|
+
end
|
|
43
103
|
end
|
|
44
104
|
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pathname"
|
|
4
|
+
|
|
5
|
+
module Crspec
|
|
6
|
+
module FileFixtures
|
|
7
|
+
def file_fixture_path
|
|
8
|
+
Crspec.configuration.file_fixture_path
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def file_fixture(fixture_name)
|
|
12
|
+
path = Pathname.new(File.join(file_fixture_path, fixture_name))
|
|
13
|
+
return path if path.file?
|
|
14
|
+
|
|
15
|
+
raise ArgumentError, "the directory '#{file_fixture_path}' does not contain a file named '#{fixture_name}'"
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|