spectre-core 1.12.0 → 1.12.3

Sign up to get free protection for your applications and to get access to all the features.
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 = 3
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