spectre-core 1.11.0 → 1.12.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/lib/spectre.rb CHANGED
@@ -1,438 +1,440 @@
1
- module Spectre
2
- module Version
3
- MAJOR = 1
4
- MINOR = 11
5
- TINY = 0
6
- end
7
-
8
- VERSION = [Version::MAJOR, Version::MINOR, Version::TINY].compact * '.'
9
-
10
-
11
- ###########################################
12
- # Custom Exceptions
13
- ###########################################
14
-
15
-
16
- class ExpectationFailure < Exception
17
- attr_reader :expectation
18
-
19
- def initialize message, expectation
20
- super message
21
- @expectation = expectation
22
- end
23
- end
24
-
25
-
26
- ###########################################
27
- # Internal Classes
28
- ###########################################
29
-
30
-
31
- # https://www.dan-manges.com/blog/ruby-dsls-instance-eval-with-delegation
32
- class DslClass
33
- def _evaluate &block
34
- @__bound_self__ = eval('self', block.binding)
35
- instance_eval(&block)
36
- end
37
-
38
- def _execute args, &block
39
- @__bound_self__ = eval('self', block.binding)
40
- instance_exec(args, &block)
41
- end
42
-
43
- def method_missing method, *args, **kwargs, &block
44
- if @__bound_self__.respond_to? method
45
- @__bound_self__.send(method, *args, **kwargs, &block)
46
- else
47
- Delegator.redirect(method, *args, **kwargs, &block)
48
- end
49
- end
50
- end
51
-
52
-
53
- class Subject
54
- attr_reader :name, :desc, :specs
55
-
56
- def initialize desc
57
- @desc = desc
58
- @specs = []
59
- @name = desc.downcase.gsub(/[^a-z0-9]+/, '_')
60
- end
61
-
62
- def add_spec desc, tags, data, block, context, file
63
- name = @name + '-' + (@specs.length+1).to_s
64
- @specs << Spec.new(name, self, desc, tags, data, block, context, file)
65
- end
66
- end
67
-
68
-
69
- class Spec
70
- attr_reader :name, :subject, :context, :desc, :tags, :data, :block, :file
71
-
72
- def initialize name, subject, desc, tags, data, block, context, file
73
- @name = name
74
- @context = context
75
- @data = data
76
- @subject = subject
77
- @desc = desc
78
- @tags = tags
79
- @block = block
80
- @file = file
81
- end
82
-
83
- def full_desc
84
- @subject.desc + ' ' + desc
85
- end
86
- end
87
-
88
-
89
- class RunInfo
90
- attr_accessor :spec, :data, :started, :finished, :error, :failure, :skipped, :log, :properties
91
-
92
- def initialize spec, data=nil
93
- @spec = spec
94
- @data = data
95
- @started = nil
96
- @finished = nil
97
- @error = nil
98
- @failure = nil
99
- @skipped = false
100
- @log = []
101
- @properties = {}
102
- end
103
-
104
- def duration
105
- @finished - @started
106
- end
107
-
108
- def skipped?
109
- @skipped
110
- end
111
-
112
- def failed?
113
- @error != nil
114
- end
115
- end
116
-
117
-
118
- class Runner
119
- @@current
120
-
121
- def self.current
122
- @@current
123
- end
124
-
125
- def run specs
126
- runs = []
127
-
128
- specs.group_by { |x| x.subject }.each do |subject, spec_group|
129
- Logger.log_subject subject do
130
- spec_group.group_by { |x| x.context }.each do |context, specs|
131
- Logger.log_context(context) do
132
- runs.concat run_context(context, specs)
133
- end
134
- end
135
- end
136
- end
137
-
138
- runs
139
- end
140
-
141
- private
142
-
143
- def run_context context, specs
144
- runs = []
145
-
146
- if context.__setup_blocks.count > 0
147
- setup_run = run_blocks('setup', context, context.__setup_blocks)
148
- runs << setup_run
149
- return runs if setup_run.error or setup_run.failure
150
- end
151
-
152
- begin
153
- specs.each do |spec|
154
- if spec.data.any?
155
- spec.data.each do |data|
156
- Logger.log_spec(spec, data) do
157
- runs << run_spec(spec, data)
158
- end
159
- end
160
- else
161
- Logger.log_spec(spec) do
162
- runs << run_spec(spec)
163
- end
164
- end
165
- end
166
- ensure
167
- if context.__teardown_blocks.count > 0
168
- runs << run_blocks('teardown', context, context.__teardown_blocks)
169
- end
170
- end
171
-
172
- runs
173
- end
174
-
175
- def run_blocks name, context, blocks
176
- ctx = SpecContext.new context.__subject, name
177
- spec = Spec.new name, context.__subject, name, [], nil, nil, ctx, nil
178
-
179
- run_info = RunInfo.new spec
180
-
181
- @@current = run_info
182
-
183
- run_info.started = Time.now
184
-
185
- Logger.log_context ctx do
186
- begin
187
- blocks.each do |block|
188
- block.call
189
- end
190
-
191
- run_info.finished = Time.now
192
-
193
- rescue ExpectationFailure => e
194
- run_info.failure = e
195
-
196
- rescue Exception => e
197
- run_info.error = e
198
- Logger.log_error spec, e
199
-
200
- end
201
- end
202
-
203
- run_info.finished = Time.now
204
-
205
- @@current = nil
206
-
207
- run_info
208
- end
209
-
210
- def run_spec spec, data=nil
211
- run_info = RunInfo.new(spec, data)
212
-
213
- @@current = run_info
214
-
215
- run_info.started = Time.now
216
-
217
- begin
218
- if spec.context.__before_blocks.count > 0
219
- before_ctx = SpecContext.new(spec.subject, 'before')
220
-
221
- Logger.log_context before_ctx do
222
- spec.context.__before_blocks.each do |block|
223
- block.call(data)
224
- end
225
- end
226
- end
227
-
228
- spec.block.call(data)
229
-
230
- rescue ExpectationFailure => e
231
- run_info.failure = e
232
-
233
- rescue Interrupt
234
- run_info.skipped = true
235
- Logger.log_skipped spec
236
-
237
- rescue Exception => e
238
- run_info.error = e
239
- Logger.log_error spec, e
240
-
241
- ensure
242
- if spec.context.__after_blocks.count > 0
243
- after_ctx = SpecContext.new(spec.subject, 'after')
244
-
245
- Logger.log_context after_ctx do
246
- begin
247
- spec.context.__after_blocks.each do |block|
248
- block.call
249
- end
250
-
251
- run_info.finished = Time.now
252
-
253
- rescue ExpectationFailure => e
254
- run_info.failure = e
255
-
256
- rescue Exception => e
257
- run_info.error = e
258
- Logger.log_error spec, e
259
-
260
- end
261
- end
262
- end
263
- end
264
-
265
- run_info.finished = Time.now
266
-
267
- @@current = nil
268
-
269
- run_info
270
- end
271
- end
272
-
273
-
274
- ###########################################
275
- # DSL Classes
276
- ###########################################
277
-
278
-
279
- class SpecContext < DslClass
280
- attr_reader :__subject, :__desc, :__before_blocks, :__after_blocks, :__setup_blocks, :__teardown_blocks
281
-
282
- def initialize subject, desc=nil
283
- @__subject = subject
284
- @__desc = desc
285
-
286
- @__before_blocks = []
287
- @__after_blocks = []
288
- @__setup_blocks = []
289
- @__teardown_blocks = []
290
- end
291
-
292
- def it desc, tags: [], with: [], &block
293
-
294
- # Get the file, where the spec is defined.
295
- # Nasty, but it works
296
- # Maybe there is another way, but this works for now
297
- spec_file = nil
298
- begin
299
- raise
300
- rescue => e
301
- spec_file = e.backtrace
302
- .select { |file| !file.include? 'lib/spectre' }
303
- .first
304
- .match(/(.*\.rb):\d+/)
305
- .captures
306
- .first
307
- end
308
-
309
- raise "`with' has to be an Array" unless with.is_a? Array
310
-
311
- data = with.map { |x| x.is_a?(Hash) ? OpenStruct.new(x) : x }
312
-
313
- @__subject.add_spec(desc, tags, data, block, self, spec_file)
314
- end
315
-
316
- def before &block
317
- @__before_blocks << block
318
- end
319
-
320
- def after &block
321
- @__after_blocks << block
322
- end
323
-
324
- def setup &block
325
- @__setup_blocks << block
326
- end
327
-
328
- def teardown &block
329
- @__teardown_blocks << block
330
- end
331
-
332
- def context desc=nil, &block
333
- ctx = SpecContext.new(@__subject, desc)
334
- ctx._evaluate &block
335
- end
336
- end
337
-
338
-
339
- ###########################################
340
- # Core Modules
341
- ###########################################
342
-
343
-
344
- module Delegator
345
- @@mappings = {}
346
-
347
- def self.delegate(*methods, target)
348
- methods.each do |method_name|
349
- define_method(method_name) do |*args, &block|
350
- return super(*args, &block) if respond_to? method_name
351
- target.send(method_name, *args, &block)
352
- end
353
-
354
- @@mappings[method_name] = target
355
-
356
- private method_name
357
- end
358
- end
359
-
360
- def self.redirect method_name, *args, **kwargs, &block
361
- target = @@mappings[method_name]
362
- raise "No method or variable '#{method_name}' defined" if !target
363
- target.send(method_name, *args, **kwargs, &block)
364
- end
365
- end
366
-
367
-
368
- class << self
369
- @@subjects = []
370
- @@modules = []
371
-
372
- attr_reader :file_log, :logger
373
-
374
-
375
- def specs spec_filter=[], tags=[]
376
- @@subjects
377
- .map { |x| x.specs }
378
- .flatten
379
- .select do |spec|
380
- (spec_filter.empty? or spec_filter.any? { |x| spec.name.match('^' + x.gsub('*', '.*') + '$') }) and (tags.empty? or tags.any? { |x| has_tag(spec.tags, x) })
381
- end
382
- end
383
-
384
-
385
- def has_tag tags, tag_exp
386
- tags = tags.map { |x| x.to_s }
387
- all_tags = tag_exp.split '+'
388
- included_tags = all_tags.select { |x| !x.start_with? '!' }
389
- excluded_tags = all_tags.select { |x| x.start_with? '!' }.map { |x| x[1..-1] }
390
- included_tags & tags == included_tags and excluded_tags & tags == []
391
- end
392
-
393
-
394
- def delegate *method_names, to: nil
395
- Spectre::Delegator.delegate *method_names, to
396
- end
397
-
398
-
399
- def register &block
400
- @@modules << block
401
- end
402
-
403
-
404
- def configure config
405
- @@modules.each do |block|
406
- block.call(config)
407
- end
408
- end
409
-
410
-
411
- ###########################################
412
- # Global Functions
413
- ###########################################
414
-
415
-
416
- def describe desc, &block
417
- subject = @@subjects.find { |x| x.desc == desc }
418
-
419
- if !subject
420
- subject = Subject.new(desc)
421
- @@subjects << subject
422
- end
423
-
424
- ctx = SpecContext.new(subject)
425
- ctx._evaluate &block
426
- end
427
-
428
- def property key, val
429
- Spectre::Runner.current.properties[key] = val
430
- end
431
-
432
- end
433
-
434
- delegate :describe, :property, to: Spectre
435
- end
436
-
437
-
438
- extend Spectre::Delegator
1
+ module Spectre
2
+ module Version
3
+ MAJOR = 1
4
+ MINOR = 12
5
+ TINY = 0
6
+ end
7
+
8
+ VERSION = [Version::MAJOR, Version::MINOR, Version::TINY].compact * '.'
9
+
10
+
11
+ class ::Hash
12
+ def deep_merge!(second)
13
+ merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge!(v2, &merger) : v2 }
14
+ self.merge!(second, &merger)
15
+ end
16
+
17
+ def deep_clone
18
+ Marshal.load(Marshal.dump(self))
19
+ end
20
+ end
21
+
22
+ class ::Object
23
+ def to_h
24
+ self.instance_variables.each_with_object({}) do |var, hash|
25
+ hash[var.to_s.delete("@")] = self.instance_variable_get(var)
26
+ end
27
+ end
28
+ end
29
+
30
+
31
+ ###########################################
32
+ # Custom Exceptions
33
+ ###########################################
34
+
35
+
36
+ class ExpectationFailure < Exception
37
+ attr_reader :expectation
38
+
39
+ def initialize message, expectation
40
+ super message
41
+ @expectation = expectation
42
+ end
43
+ end
44
+
45
+
46
+ ###########################################
47
+ # Internal Classes
48
+ ###########################################
49
+
50
+
51
+ # https://www.dan-manges.com/blog/ruby-dsls-instance-eval-with-delegation
52
+ class DslClass
53
+ def _evaluate &block
54
+ @__bound_self__ = eval('self', block.binding)
55
+ instance_eval(&block)
56
+ end
57
+
58
+ def _execute *args, &block
59
+ @__bound_self__ = eval('self', block.binding)
60
+ instance_exec(*args, &block)
61
+ end
62
+
63
+ def method_missing method, *args, **kwargs, &block
64
+ if @__bound_self__.respond_to? method
65
+ @__bound_self__.send(method, *args, **kwargs, &block)
66
+ else
67
+ Delegator.redirect(method, *args, **kwargs, &block)
68
+ end
69
+ end
70
+ end
71
+
72
+ class Subject
73
+ attr_reader :name, :desc, :specs
74
+
75
+ def initialize desc
76
+ @desc = desc
77
+ @specs = []
78
+ @name = desc.downcase.gsub(/[^a-z0-9]+/, '_')
79
+ end
80
+
81
+ def add_spec desc, tags, data, block, context, file
82
+ name = @name + '-' + (@specs.length+1).to_s
83
+ @specs << Spec.new(name, self, desc, tags, data, block, context, file)
84
+ end
85
+ end
86
+
87
+ class Spec
88
+ attr_reader :name, :subject, :context, :desc, :tags, :data, :block, :file
89
+
90
+ def initialize name, subject, desc, tags, data, block, context, file
91
+ @name = name
92
+ @context = context
93
+ @data = data
94
+ @subject = subject
95
+ @desc = desc
96
+ @tags = tags
97
+ @block = block
98
+ @file = file
99
+ end
100
+
101
+ def full_desc
102
+ @subject.desc + ' ' + desc
103
+ end
104
+ end
105
+
106
+ class RunInfo
107
+ attr_accessor :spec, :data, :started, :finished, :error, :failure, :skipped, :log, :properties
108
+
109
+ def initialize spec, data=nil
110
+ @spec = spec
111
+ @data = data
112
+ @started = nil
113
+ @finished = nil
114
+ @error = nil
115
+ @failure = nil
116
+ @skipped = false
117
+ @log = []
118
+ @properties = {}
119
+ end
120
+
121
+ def duration
122
+ @finished - @started
123
+ end
124
+
125
+ def skipped?
126
+ @skipped
127
+ end
128
+
129
+ def failed?
130
+ @error != nil
131
+ end
132
+ end
133
+
134
+ class Runner
135
+ @@current
136
+
137
+ def self.current
138
+ @@current
139
+ end
140
+
141
+ def run specs
142
+ runs = []
143
+
144
+ specs.group_by { |x| x.subject }.each do |subject, spec_group|
145
+ Logger.log_subject subject do
146
+ spec_group.group_by { |x| x.context }.each do |context, specs|
147
+ Logger.log_context(context) do
148
+ runs.concat run_context(context, specs)
149
+ end
150
+ end
151
+ end
152
+ end
153
+
154
+ runs
155
+ end
156
+
157
+ private
158
+
159
+ def run_context context, specs
160
+ runs = []
161
+
162
+ if context.__setup_blocks.count > 0
163
+ setup_run = run_blocks('setup', context, context.__setup_blocks)
164
+ runs << setup_run
165
+ return runs if setup_run.error or setup_run.failure
166
+ end
167
+
168
+ begin
169
+ specs.each do |spec|
170
+ raise "Multi data definition (`with' parameter) of '#{spec.subject.desc} #{spec.desc}' has to be an `Array'" unless !spec.data.nil? and spec.data.is_a? Array
171
+
172
+ if spec.data.any?
173
+ spec.data
174
+ .map { |x| x.is_a?(Hash) ? OpenStruct.new(x) : x }
175
+ .each do |data|
176
+ Logger.log_spec(spec, data) do
177
+ runs << run_spec(spec, data)
178
+ end
179
+ end
180
+ else
181
+ Logger.log_spec(spec) do
182
+ runs << run_spec(spec)
183
+ end
184
+ end
185
+ end
186
+ ensure
187
+ if context.__teardown_blocks.count > 0
188
+ runs << run_blocks('teardown', context, context.__teardown_blocks)
189
+ end
190
+ end
191
+
192
+ runs
193
+ end
194
+
195
+ def run_blocks name, context, blocks
196
+ ctx = SpecContext.new context.__subject, name
197
+ spec = Spec.new name, context.__subject, name, [], nil, nil, ctx, nil
198
+
199
+ run_info = RunInfo.new spec
200
+
201
+ @@current = run_info
202
+
203
+ run_info.started = Time.now
204
+
205
+ Logger.log_context ctx do
206
+ begin
207
+ blocks.each do |block|
208
+ block.call
209
+ end
210
+
211
+ run_info.finished = Time.now
212
+ rescue ExpectationFailure => e
213
+ run_info.failure = e
214
+ rescue Exception => e
215
+ run_info.error = e
216
+ Logger.log_error spec, e
217
+ end
218
+ end
219
+
220
+ run_info.finished = Time.now
221
+
222
+ @@current = nil
223
+
224
+ run_info
225
+ end
226
+
227
+ def run_spec spec, data=nil
228
+ run_info = RunInfo.new(spec, data)
229
+
230
+ @@current = run_info
231
+
232
+ run_info.started = Time.now
233
+
234
+ begin
235
+ if spec.context.__before_blocks.count > 0
236
+ before_ctx = SpecContext.new(spec.subject, 'before')
237
+
238
+ Logger.log_context before_ctx do
239
+ spec.context.__before_blocks.each do |block|
240
+ block.call(data)
241
+ end
242
+ end
243
+ end
244
+
245
+ spec.block.call(data)
246
+ rescue ExpectationFailure => e
247
+ run_info.failure = e
248
+ rescue Interrupt
249
+ run_info.skipped = true
250
+ Logger.log_skipped spec
251
+ rescue Exception => e
252
+ run_info.error = e
253
+ Logger.log_error spec, e
254
+ ensure
255
+ if spec.context.__after_blocks.count > 0
256
+ after_ctx = SpecContext.new(spec.subject, 'after')
257
+
258
+ Logger.log_context after_ctx do
259
+ begin
260
+ spec.context.__after_blocks.each do |block|
261
+ block.call
262
+ end
263
+
264
+ run_info.finished = Time.now
265
+ rescue ExpectationFailure => e
266
+ run_info.failure = e
267
+ rescue Exception => e
268
+ run_info.error = e
269
+ Logger.log_error spec, e
270
+ end
271
+ end
272
+ end
273
+ end
274
+
275
+ run_info.finished = Time.now
276
+
277
+ @@current = nil
278
+
279
+ run_info
280
+ end
281
+ end
282
+
283
+
284
+ ###########################################
285
+ # DSL Classes
286
+ ###########################################
287
+
288
+
289
+ class SpecContext < DslClass
290
+ attr_reader :__subject, :__desc, :__before_blocks, :__after_blocks, :__setup_blocks, :__teardown_blocks
291
+
292
+ def initialize subject, desc=nil
293
+ @__subject = subject
294
+ @__desc = desc
295
+
296
+ @__before_blocks = []
297
+ @__after_blocks = []
298
+ @__setup_blocks = []
299
+ @__teardown_blocks = []
300
+ end
301
+
302
+ def it desc, tags: [], with: [], &block
303
+ # Get the file, where the spec is defined.
304
+ # Nasty, but it works
305
+ # Maybe there is another way, but this works for now
306
+ spec_file = nil
307
+ begin
308
+ raise
309
+ rescue => e
310
+ spec_file = e.backtrace
311
+ .select { |file| !file.include? 'lib/spectre' }
312
+ .first
313
+ .match(/(.*\.rb):\d+/)
314
+ .captures
315
+ .first
316
+ end
317
+
318
+ @__subject.add_spec(desc, tags, with, block, self, spec_file)
319
+ end
320
+
321
+ def before &block
322
+ @__before_blocks << block
323
+ end
324
+
325
+ def after &block
326
+ @__after_blocks << block
327
+ end
328
+
329
+ def setup &block
330
+ @__setup_blocks << block
331
+ end
332
+
333
+ def teardown &block
334
+ @__teardown_blocks << block
335
+ end
336
+
337
+ def context desc=nil, &block
338
+ ctx = SpecContext.new(@__subject, desc)
339
+ ctx._evaluate &block
340
+ end
341
+ end
342
+
343
+
344
+ ###########################################
345
+ # Core Modules
346
+ ###########################################
347
+
348
+
349
+ module Delegator
350
+ @@mappings = {}
351
+
352
+ def self.delegate(*methods, target)
353
+ methods.each do |method_name|
354
+ define_method(method_name) do |*args, &block|
355
+ return super(*args, &block) if respond_to? method_name
356
+
357
+ target.send(method_name, *args, &block)
358
+ end
359
+
360
+ @@mappings[method_name] = target
361
+
362
+ private method_name
363
+ end
364
+ end
365
+
366
+ def self.redirect method_name, *args, **kwargs, &block
367
+ target = @@mappings[method_name] || Kernel
368
+ raise "no variable or method '#{method_name}' found" unless target.respond_to? method_name
369
+
370
+ target.send(method_name, *args, **kwargs, &block)
371
+ end
372
+ end
373
+
374
+
375
+ class << self
376
+ @@subjects = []
377
+ @@modules = []
378
+
379
+ attr_reader :file_log, :logger
380
+
381
+
382
+ def specs spec_filter=[], tags=[]
383
+ @@subjects
384
+ .map { |x| x.specs }
385
+ .flatten
386
+ .select do |spec|
387
+ (spec_filter.empty? or spec_filter.any? { |x| spec.name.match('^' + x.gsub('*', '.*') + '$') }) and (tags.empty? or tags.any? { |x| tag?(spec.tags, x) })
388
+ end
389
+ end
390
+
391
+ def tag? tags, tag_exp
392
+ tags = tags.map { |x| x.to_s }
393
+ all_tags = tag_exp.split '+'
394
+ included_tags = all_tags.select { |x| !x.start_with? '!' }
395
+ excluded_tags = all_tags.select { |x| x.start_with? '!' }.map { |x| x[1..-1] }
396
+ included_tags & tags == included_tags and excluded_tags & tags == []
397
+ end
398
+
399
+ def delegate *method_names, to: nil
400
+ Spectre::Delegator.delegate *method_names, to
401
+ end
402
+
403
+ def register &block
404
+ @@modules << block
405
+ end
406
+
407
+ def configure config
408
+ @@modules.each do |block|
409
+ block.call(config)
410
+ end
411
+ end
412
+
413
+
414
+ ###########################################
415
+ # Global Functions
416
+ ###########################################
417
+
418
+
419
+ def describe desc, &block
420
+ subject = @@subjects.find { |x| x.desc == desc }
421
+
422
+ unless subject
423
+ subject = Subject.new(desc)
424
+ @@subjects << subject
425
+ end
426
+
427
+ ctx = SpecContext.new(subject)
428
+ ctx._evaluate &block
429
+ end
430
+
431
+ def property key, val
432
+ Spectre::Runner.current.properties[key] = val
433
+ end
434
+ end
435
+
436
+ delegate :describe, :property, to: Spectre
437
+ end
438
+
439
+
440
+ extend Spectre::Delegator