spectre-core 1.8.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.
- checksums.yaml +7 -0
- data/exe/spectre +487 -0
- data/lib/spectre.rb +434 -0
- data/lib/spectre/assertion.rb +277 -0
- data/lib/spectre/bag.rb +19 -0
- data/lib/spectre/curl.rb +368 -0
- data/lib/spectre/database/postgres.rb +78 -0
- data/lib/spectre/diagnostic.rb +29 -0
- data/lib/spectre/environment.rb +26 -0
- data/lib/spectre/ftp.rb +195 -0
- data/lib/spectre/helpers.rb +64 -0
- data/lib/spectre/http.rb +343 -0
- data/lib/spectre/http/basic_auth.rb +22 -0
- data/lib/spectre/http/keystone.rb +98 -0
- data/lib/spectre/logger.rb +144 -0
- data/lib/spectre/logger/console.rb +142 -0
- data/lib/spectre/logger/file.rb +96 -0
- data/lib/spectre/mixin.rb +41 -0
- data/lib/spectre/mysql.rb +97 -0
- data/lib/spectre/reporter/console.rb +103 -0
- data/lib/spectre/reporter/junit.rb +98 -0
- data/lib/spectre/resources.rb +46 -0
- data/lib/spectre/ssh.rb +149 -0
- metadata +140 -0
data/lib/spectre.rb
ADDED
@@ -0,0 +1,434 @@
|
|
1
|
+
module Spectre
|
2
|
+
module Version
|
3
|
+
MAJOR = 1
|
4
|
+
MINOR = 8
|
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.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
|
@@ -0,0 +1,277 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
require_relative 'logger'
|
3
|
+
|
4
|
+
|
5
|
+
module Spectre
|
6
|
+
module Assertion
|
7
|
+
class ::Object
|
8
|
+
def should_be(val)
|
9
|
+
raise AssertionFailure.new("The value '#{self.to_s.trim}' should be '#{val.to_s.trim}'", val, self) unless self.to_s == val.to_s
|
10
|
+
end
|
11
|
+
|
12
|
+
def should_be_empty
|
13
|
+
raise AssertionFailure.new("The value '#{self.to_s.trim}' should be empty", nil, self) unless self == nil
|
14
|
+
end
|
15
|
+
|
16
|
+
def should_not_be(val)
|
17
|
+
raise AssertionFailure.new("The value '#{self.to_s.trim}' should not be '#{val.to_s.trim}'", val, self) unless self.to_s != val.to_s
|
18
|
+
end
|
19
|
+
|
20
|
+
def should_not_exist
|
21
|
+
raise AssertionFailure.new("The value '#{self.to_s.trim}' should not exist, but it does", val, self) unless self.to_s != nil
|
22
|
+
end
|
23
|
+
|
24
|
+
def should_not_be_empty
|
25
|
+
raise AssertionFailure.new('The value should not be empty', 'nothing', self) unless self != nil
|
26
|
+
end
|
27
|
+
|
28
|
+
def or other
|
29
|
+
OrEvaluation.new self, other
|
30
|
+
end
|
31
|
+
|
32
|
+
def and other
|
33
|
+
AndEvaluation.new self, other
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
class ::NilClass
|
39
|
+
def should_be(val)
|
40
|
+
raise AssertionFailure.new("There is nothing, but the value should be '#{val.to_s.trim}'", val, nil) unless val == nil
|
41
|
+
end
|
42
|
+
|
43
|
+
def should_be_empty
|
44
|
+
end
|
45
|
+
|
46
|
+
def should_not_be(val)
|
47
|
+
raise AssertionFailure.new(val, 'nil') unless val != nil
|
48
|
+
end
|
49
|
+
|
50
|
+
def should_not_exist
|
51
|
+
end
|
52
|
+
|
53
|
+
def should_not_be_empty
|
54
|
+
raise AssertionFailure.new('not empty', 'nil')
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
class ::Hash
|
60
|
+
def should_contain(other)
|
61
|
+
raise AssertionFailure.new(other, self) unless self.merge(other) == self
|
62
|
+
end
|
63
|
+
|
64
|
+
def should_not_contain(other)
|
65
|
+
raise AssertionFailure.new(other, self) unless self.merge(other) != self
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
class ::Array
|
71
|
+
def should_contain(val)
|
72
|
+
list = self
|
73
|
+
|
74
|
+
if val.is_a? Hash and self.all? { |x| x.is_a? OpenStruct or x.is_a? Hash }
|
75
|
+
list = self.map { |x| OpenStruct.new(x) }
|
76
|
+
val = OpenStruct.new(val)
|
77
|
+
end
|
78
|
+
|
79
|
+
raise AssertionFailure.new("The list [#{list.join(', ').trim}] should contain '#{val.trim}'", val, list) unless list.include? val
|
80
|
+
end
|
81
|
+
|
82
|
+
def should_not_contain(val)
|
83
|
+
list = self
|
84
|
+
|
85
|
+
if val.is_a? Hash and self.all? { |x| x.is_a? OpenStruct or x.is_a? Hash }
|
86
|
+
list = self.map { |x| OpenStruct.new(x) }
|
87
|
+
val = OpenStruct.new(val)
|
88
|
+
end
|
89
|
+
|
90
|
+
raise AssertionFailure.new("The list [#{list.join(', ').trim}] should not contain '#{val.trim}'", val, list) if list.include? val
|
91
|
+
end
|
92
|
+
|
93
|
+
def should_be_empty
|
94
|
+
raise AssertionFailure.new('empty list', self) unless self.length == 0
|
95
|
+
end
|
96
|
+
|
97
|
+
def should_not_be_empty
|
98
|
+
raise AssertionFailure.new('no empty list', self) unless self.length > 0
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
|
103
|
+
class ::String
|
104
|
+
def should_be(val)
|
105
|
+
raise AssertionFailure.new("The text '#{self.trim}' should be '#{val.to_s.trim}'", val, self) unless self == val
|
106
|
+
end
|
107
|
+
|
108
|
+
def should_be_empty
|
109
|
+
raise AssertionFailure.new("The text '#{self.trim}' should be empty", nil, self) unless self.empty?
|
110
|
+
end
|
111
|
+
|
112
|
+
def should_not_be(val)
|
113
|
+
raise AssertionFailure.new("The text '#{self.trim}' should not be '#{val.to_s.trim}'", val, self) unless self != val
|
114
|
+
end
|
115
|
+
|
116
|
+
def should_not_be_empty
|
117
|
+
raise AssertionFailure.new('The text should not be empty', 'nothing', self) unless not self.empty?
|
118
|
+
end
|
119
|
+
|
120
|
+
def should_contain(value)
|
121
|
+
predicate = proc { |x| self.include? x.to_s }
|
122
|
+
evaluation = SingleEvaluation.new value
|
123
|
+
success = evaluation.call(predicate)
|
124
|
+
|
125
|
+
return if success
|
126
|
+
|
127
|
+
raise AssertionFailure.new("The text '#{self.to_s.trim}' should contain #{evaluation.to_s}", evaluation, self)
|
128
|
+
end
|
129
|
+
|
130
|
+
def should_not_contain(val)
|
131
|
+
raise AssertionFailure.new("The text '#{self.trim}' should not contain '#{val.trim}'", val, self) if self.include? val
|
132
|
+
end
|
133
|
+
|
134
|
+
def should_match(regex)
|
135
|
+
raise AssertionFailure.new("The text '#{self.trim}' should match '#{val}'", regex, self) unless self.match(regex)
|
136
|
+
end
|
137
|
+
|
138
|
+
def should_not_match(regex)
|
139
|
+
raise AssertionFailure.new("The text '#{self.trim}' should not match '#{val}'", regex, self) if self.match(regex)
|
140
|
+
end
|
141
|
+
|
142
|
+
alias :| :or
|
143
|
+
alias :& :and
|
144
|
+
end
|
145
|
+
|
146
|
+
|
147
|
+
class Evaluation
|
148
|
+
def initialize value, other
|
149
|
+
@value = value
|
150
|
+
@other = other
|
151
|
+
end
|
152
|
+
|
153
|
+
def eval_assertion predicate, val
|
154
|
+
if val.is_a? Evaluation
|
155
|
+
val.call(predicate)
|
156
|
+
else
|
157
|
+
predicate.call(val)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
alias :| :or
|
162
|
+
alias :& :and
|
163
|
+
end
|
164
|
+
|
165
|
+
|
166
|
+
class SingleEvaluation < Evaluation
|
167
|
+
def initialize value
|
168
|
+
super value, nil
|
169
|
+
end
|
170
|
+
|
171
|
+
def call predicate
|
172
|
+
eval_assertion(predicate, @value)
|
173
|
+
end
|
174
|
+
|
175
|
+
def to_s
|
176
|
+
@value.to_s
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
|
181
|
+
class OrEvaluation < Evaluation
|
182
|
+
def initialize value, other
|
183
|
+
super value, other
|
184
|
+
end
|
185
|
+
|
186
|
+
def call predicate
|
187
|
+
eval_assertion(predicate, @value) or eval_assertion(predicate, @other)
|
188
|
+
end
|
189
|
+
|
190
|
+
def to_s
|
191
|
+
"(#{@value.to_s} or #{@other.to_s})"
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
|
196
|
+
class AndEvaluation < Evaluation
|
197
|
+
def initialize value, other
|
198
|
+
super value, other
|
199
|
+
end
|
200
|
+
|
201
|
+
def call predicate
|
202
|
+
eval_assertion(predicate, @value) and eval_assertion(predicate, @other)
|
203
|
+
end
|
204
|
+
|
205
|
+
def to_s
|
206
|
+
"(#{@value.to_s} and #{@other.to_s})"
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
|
211
|
+
class AssertionFailure < ExpectationFailure
|
212
|
+
attr_reader :expected, :actual
|
213
|
+
|
214
|
+
def initialize message, expected=nil, actual=nil, expectation=nil
|
215
|
+
super message, expectation
|
216
|
+
@expected = expected
|
217
|
+
@actual = actual
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
|
222
|
+
class << self
|
223
|
+
@@success = nil
|
224
|
+
|
225
|
+
def eval_assertion predicate, val
|
226
|
+
if val.is_a? Proc
|
227
|
+
val.call(predicate)
|
228
|
+
else
|
229
|
+
predicate.call(val)
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
def expect desc
|
234
|
+
begin
|
235
|
+
Logger.log_process("expect #{desc}")
|
236
|
+
yield
|
237
|
+
Logger.log_status(desc, Logger::Status::OK)
|
238
|
+
|
239
|
+
rescue Interrupt => e
|
240
|
+
raise e
|
241
|
+
|
242
|
+
rescue AssertionFailure => e
|
243
|
+
Logger.log_status(desc, Logger::Status::FAILED)
|
244
|
+
raise AssertionFailure.new(e.message, e.expected, e.actual, desc), cause: nil
|
245
|
+
|
246
|
+
rescue Exception => e
|
247
|
+
Logger.log_status(desc, Logger::Status::ERROR)
|
248
|
+
raise AssertionFailure.new("An unexpected error occured during expectation: #{e.message}", nil, nil, desc), cause: e
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
def observe desc = nil
|
253
|
+
begin
|
254
|
+
Logger.log_info("observing #{desc}") if desc
|
255
|
+
yield
|
256
|
+
@@success = true
|
257
|
+
|
258
|
+
rescue Interrupt => e
|
259
|
+
raise e
|
260
|
+
|
261
|
+
rescue Exception => e
|
262
|
+
@@success = false
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
def success?
|
267
|
+
@@success
|
268
|
+
end
|
269
|
+
|
270
|
+
def fail_with message
|
271
|
+
raise AssertionFailure.new(message)
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
Spectre.delegate :expect, :observe, :success?, :fail_with, to: self
|
276
|
+
end
|
277
|
+
end
|