spectre-core 1.12.0 → 1.12.1

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