spectre-core 1.8.4 → 1.12.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/spectre.rb CHANGED
@@ -1,434 +1,440 @@
1
- module Spectre
2
- module Version
3
- MAJOR = 1
4
- MINOR = 8
5
- TINY = 4
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.length > 0
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
- @__subject.add_spec(desc, tags, with, block, self, spec_file)
310
- end
311
-
312
- def before &block
313
- @__before_blocks << block
314
- end
315
-
316
- def after &block
317
- @__after_blocks << block
318
- end
319
-
320
- def setup &block
321
- @__setup_blocks << block
322
- end
323
-
324
- def teardown &block
325
- @__teardown_blocks << block
326
- end
327
-
328
- def context desc=nil, &block
329
- ctx = SpecContext.new(@__subject, desc)
330
- ctx._evaluate &block
331
- end
332
- end
333
-
334
-
335
- ###########################################
336
- # Core Modules
337
- ###########################################
338
-
339
-
340
- module Delegator
341
- @@mappings = {}
342
-
343
- def self.delegate(*methods, target)
344
- methods.each do |method_name|
345
- define_method(method_name) do |*args, &block|
346
- return super(*args, &block) if respond_to? method_name
347
- target.send(method_name, *args, &block)
348
- end
349
-
350
- @@mappings[method_name] = target
351
-
352
- private method_name
353
- end
354
- end
355
-
356
- def self.redirect method_name, *args, **kwargs, &block
357
- target = @@mappings[method_name]
358
- raise "No method or variable '#{method_name}' defined" if !target
359
- target.send(method_name, *args, **kwargs, &block)
360
- end
361
- end
362
-
363
-
364
- class << self
365
- @@subjects = []
366
- @@modules = []
367
-
368
- attr_reader :file_log, :logger
369
-
370
-
371
- def specs spec_filter=[], tags=[]
372
- @@subjects
373
- .map { |x| x.specs }
374
- .flatten
375
- .select do |spec|
376
- (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) })
377
- end
378
- end
379
-
380
-
381
- def has_tag tags, tag_exp
382
- tags = tags.map { |x| x.to_s }
383
- all_tags = tag_exp.split '+'
384
- included_tags = all_tags.select { |x| !x.start_with? '!' }
385
- excluded_tags = all_tags.select { |x| x.start_with? '!' }.map { |x| x[1..-1] }
386
- included_tags & tags == included_tags and excluded_tags & tags == []
387
- end
388
-
389
-
390
- def delegate *method_names, to: nil
391
- Spectre::Delegator.delegate *method_names, to
392
- end
393
-
394
-
395
- def register &block
396
- @@modules << block
397
- end
398
-
399
-
400
- def configure config
401
- @@modules.each do |block|
402
- block.call(config)
403
- end
404
- end
405
-
406
-
407
- ###########################################
408
- # Global Functions
409
- ###########################################
410
-
411
-
412
- def describe desc, &block
413
- subject = @@subjects.find { |x| x.desc == desc }
414
-
415
- if !subject
416
- subject = Subject.new(desc)
417
- @@subjects << subject
418
- end
419
-
420
- ctx = SpecContext.new(subject)
421
- ctx._evaluate &block
422
- end
423
-
424
- def property key, val
425
- Spectre::Runner.current.properties[key] = val
426
- end
427
-
428
- end
429
-
430
- delegate :describe, :property, to: Spectre
431
- end
432
-
433
-
434
- 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