probatio 0.9.0 → 1.1.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 +4 -4
- data/CHANGELOG.md +11 -1
- data/README.md +307 -0
- data/exe/proba +245 -1
- data/lib/probatio/assertions.rb +324 -0
- data/lib/probatio/debug.rb +35 -0
- data/lib/probatio/examples/a_plugin.rb +14 -0
- data/lib/probatio/examples/a_test.rb +126 -0
- data/lib/probatio/mangle.rb +42 -0
- data/lib/probatio/more.rb +151 -0
- data/lib/probatio/plug.rb +72 -0
- data/lib/probatio/plugins.rb +253 -0
- data/lib/probatio/waiters.rb +44 -0
- data/lib/probatio.rb +773 -157
- data/probatio.gemspec +2 -7
- metadata +17 -8
data/lib/probatio.rb
CHANGED
@@ -2,196 +2,453 @@
|
|
2
2
|
#
|
3
3
|
# probatio.rb
|
4
4
|
|
5
|
+
require 'pp'
|
6
|
+
require 'set'
|
5
7
|
require 'stringio'
|
8
|
+
require 'io/console'
|
9
|
+
|
10
|
+
require 'colorato'
|
11
|
+
|
12
|
+
require 'probatio/debug'
|
13
|
+
require 'probatio/more'
|
6
14
|
|
7
15
|
|
8
16
|
module Probatio
|
9
17
|
|
10
|
-
VERSION = '
|
18
|
+
VERSION = '1.1.0'
|
11
19
|
|
12
20
|
class << self
|
13
21
|
|
22
|
+
attr_reader :seed, :rng
|
23
|
+
attr_reader :map
|
24
|
+
|
25
|
+
def c; $_PROBATIO_COLOURS; end
|
26
|
+
|
14
27
|
def run(run_opts)
|
15
28
|
|
16
|
-
|
29
|
+
@seed = run_opts[:seed]
|
30
|
+
@rng = Random.new(@seed)
|
31
|
+
|
32
|
+
Probatio.despatch(:pre, run_opts)
|
33
|
+
|
34
|
+
#
|
35
|
+
# construct tree
|
17
36
|
|
18
|
-
root_group = Group.new(nil, '_', {},
|
37
|
+
root_group = Group.new(nil, __FILE__, '_', {}, nil)
|
38
|
+
|
39
|
+
run_opts[:dirs] ||= []
|
40
|
+
run_opts[:files] ||= []
|
41
|
+
|
42
|
+
run_opts[:filez] = []
|
43
|
+
run_opts[:filen] = []
|
44
|
+
|
45
|
+
helpers = locate(run_opts, '*_helper.rb', '*_helpers.rb')
|
46
|
+
setups = locate(run_opts, 'setup.rb', '*_setup.rb')
|
47
|
+
|
48
|
+
dbg_s do
|
49
|
+
" / dirs: #{run_opts[:dirs].inspect}\n" +
|
50
|
+
" / files: #{run_opts[:files].inspect}\n" +
|
51
|
+
" / helpers: #{helpers.inspect}\n" +
|
52
|
+
" / setups: #{setups.inspect}\n"
|
53
|
+
end
|
19
54
|
|
20
|
-
|
55
|
+
# helpers and setups...
|
21
56
|
|
22
|
-
|
57
|
+
helpers.each do |path|
|
23
58
|
|
24
59
|
read_helper_file(root_group, path)
|
25
60
|
end
|
26
61
|
|
27
|
-
|
62
|
+
setups.each do |path|
|
28
63
|
|
64
|
+
run_opts[:filez] << path
|
29
65
|
read_test_file(root_group, path)
|
30
66
|
end
|
31
67
|
|
68
|
+
# tests from dirs...
|
69
|
+
|
70
|
+
run_opts[:dirs].each do |dir|
|
71
|
+
|
72
|
+
( Dir[File.join(dir, '**', '*_test.rb')] +
|
73
|
+
Dir[File.join(dir, '**', '*_tests.rb')]
|
74
|
+
|
75
|
+
).each do |path|
|
76
|
+
|
77
|
+
run_opts[:filez] << path
|
78
|
+
read_test_file(root_group, path)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# tests from files...
|
83
|
+
|
84
|
+
run_opts[:files].each do |path|
|
85
|
+
|
86
|
+
colons = path.split(':')
|
87
|
+
fpath = colons.shift
|
88
|
+
|
89
|
+
if colons.empty?
|
90
|
+
run_opts[:filez] << path
|
91
|
+
else
|
92
|
+
colons.each do |lnum|
|
93
|
+
lnum = lnum.match?(/^\d+$/) ? lnum.to_i : false
|
94
|
+
run_opts[:filen] << [ fpath, lnum ] if lnum
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
read_test_file(root_group, fpath)
|
99
|
+
end
|
100
|
+
|
101
|
+
run_opts[:filen] = rework_filen(root_group, run_opts)
|
102
|
+
|
103
|
+
dbg_s { Cerata.vertical_h_to_s(run_opts, ' run_opts| ') }
|
104
|
+
|
105
|
+
#
|
106
|
+
# print or map
|
107
|
+
|
108
|
+
if run_opts[:print]
|
109
|
+
|
110
|
+
puts root_group.to_s
|
111
|
+
|
112
|
+
exit 0
|
113
|
+
end
|
114
|
+
|
115
|
+
if run_opts[:map]
|
116
|
+
|
117
|
+
root_group.map.each do |path, groups|
|
118
|
+
puts ". #{Probatio.c.green(path)}"
|
119
|
+
groups.each do |l0, l1, g|
|
120
|
+
puts " #{Probatio.c.dark_grey('%4d %4d')} %s" % [ l0, l1, g.head ]
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
exit 0
|
125
|
+
end
|
126
|
+
|
127
|
+
#
|
128
|
+
# run
|
129
|
+
|
130
|
+
dbg_s { "---\n" + root_group.to_s + "\n---\n" }
|
131
|
+
|
132
|
+
Probatio.despatch(:start, root_group, run_opts)
|
133
|
+
|
32
134
|
root_group.run(run_opts)
|
33
135
|
|
34
|
-
Probatio.despatch(:over, run_opts)
|
136
|
+
Probatio.despatch(:over, root_group, run_opts)
|
137
|
+
|
138
|
+
Probatio.despatch(:exit, root_group, run_opts)
|
139
|
+
# some plugin will catch that and do `exit 0` or `exit 1`...
|
35
140
|
end
|
36
141
|
|
37
142
|
def init
|
38
143
|
|
144
|
+
@read = Set.new
|
145
|
+
|
39
146
|
@plugins = []
|
147
|
+
@plugouts = nil
|
40
148
|
end
|
41
149
|
|
42
|
-
def
|
150
|
+
def despatch(event_name, *details)
|
43
151
|
|
44
|
-
|
152
|
+
#p [ :despatch, event_name ]
|
153
|
+
en = event_name.to_sym
|
154
|
+
me = "on_#{event_name}"
|
155
|
+
ev = Probatio::Event.new(en, details)
|
156
|
+
#p [ :despatch, event_name, ev.delta ]
|
45
157
|
|
46
|
-
|
47
|
-
end
|
158
|
+
dbg_m { ' ' + [ :despatch, en, ev.node && ev.node.full_name ].inspect }
|
48
159
|
|
49
|
-
|
160
|
+
@plugouts ||= @plugins.reverse
|
50
161
|
|
51
|
-
|
52
|
-
m = "on_#{event_name}"
|
53
|
-
ev = Probatio::Event.new(event_name.to_sym, details)
|
162
|
+
(ev.leave? ? @plugouts : @plugins).each do |plugin|
|
54
163
|
|
55
|
-
|
56
|
-
plugin.send(
|
164
|
+
plugin.record(ev) if plugin.respond_to?(:record)
|
165
|
+
plugin.send(me, ev) if plugin.respond_to?(me)
|
57
166
|
end
|
58
167
|
end
|
59
168
|
|
169
|
+
def epath; '.probatio-environments.rb'; end
|
170
|
+
def opath; '.probatio-output.rb'; end
|
171
|
+
def tpath; '.test-point'; end
|
172
|
+
|
60
173
|
protected
|
61
174
|
|
62
175
|
def read_helper_file(group, path)
|
63
176
|
|
177
|
+
return if @read.include?(path); @read.add(path)
|
178
|
+
|
64
179
|
Kernel.load(path)
|
65
180
|
end
|
66
181
|
|
67
182
|
def read_test_file(group, path)
|
68
183
|
|
184
|
+
return if @read.include?(path); @read.add(path)
|
185
|
+
|
69
186
|
group.add_file(path)
|
70
187
|
end
|
71
|
-
end
|
72
188
|
|
73
|
-
|
189
|
+
def rework_filen(root_group, run_opts)
|
74
190
|
|
75
|
-
|
191
|
+
run_opts[:filen]
|
192
|
+
.inject([]) { |a, fn| a.concat(rework_fn(root_group.map, fn)) }
|
193
|
+
end
|
76
194
|
|
77
|
-
|
78
|
-
attr_accessor :path
|
195
|
+
def rework_fn(map, fn)
|
79
196
|
|
80
|
-
|
197
|
+
fmap = map[fn[0]]
|
198
|
+
fline = fn[1]
|
81
199
|
|
82
|
-
|
200
|
+
n = fmap.find { |l0, l1, n| fline >= l0 && (fline <= l1 || l1 < 1) }
|
83
201
|
|
84
|
-
|
85
|
-
|
202
|
+
return [ fn ] if n && n[2].is_a?(Probatio::Test)
|
203
|
+
|
204
|
+
# we don't have a test...
|
205
|
+
|
206
|
+
n2 = n[2]
|
207
|
+
n2 = n2.parent unless n2.is_a?(Probatio::Group)
|
208
|
+
|
209
|
+
# we have a group, lists all its tests located in the given file...
|
210
|
+
|
211
|
+
n2.all_tests
|
212
|
+
.select { |t| t.path == fn[0] }
|
213
|
+
.collect { |t| t.path_and_line }
|
214
|
+
end
|
215
|
+
|
216
|
+
def locate(run_opts, *suffixes)
|
217
|
+
|
218
|
+
(
|
219
|
+
run_opts[:dirs].inject([]) { |a, d|
|
220
|
+
a.concat(do_locate(d, suffixes)) } +
|
221
|
+
run_opts[:files].inject([]) { |a, f|
|
222
|
+
a.concat(do_locate(File.dirname(f), suffixes)) }
|
223
|
+
).uniq.sort
|
224
|
+
end
|
225
|
+
|
226
|
+
def do_locate(dir, suffixes)
|
227
|
+
|
228
|
+
return [] if dir == '.'
|
229
|
+
|
230
|
+
fs = suffixes
|
231
|
+
.inject([]) { |a, suf| a.concat(Dir[File.join(dir, '**', suf)]) }
|
232
|
+
|
233
|
+
fs.any? ? fs : do_locate(File.dirname(dir), suffixes)
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
class Node
|
238
|
+
|
239
|
+
attr_reader :parent, :path, :opts, :block, :children
|
240
|
+
|
241
|
+
def root; @parent ? @parent.root : self; end
|
242
|
+
def map; @parent ? @parent.map : (@map ||= {}); end
|
243
|
+
|
244
|
+
def initialize(parent, path, name, opts, block)
|
245
|
+
|
246
|
+
@parent = parent
|
86
247
|
@path = path
|
248
|
+
@name = name
|
249
|
+
@opts = opts
|
250
|
+
@block = block
|
87
251
|
|
88
252
|
@children = []
|
89
253
|
|
90
|
-
|
254
|
+
map_self
|
91
255
|
end
|
92
256
|
|
93
|
-
def
|
257
|
+
def filename; @filename ||= File.basename(@path); end
|
94
258
|
|
95
|
-
|
259
|
+
def type; self.class.name.split('::').last.downcase; end
|
260
|
+
def test?; type == 'test'; end
|
261
|
+
|
262
|
+
def depth; parent ? parent.depth + 1 : 0; end
|
263
|
+
|
264
|
+
def name; @name || type; end
|
265
|
+
def array_name; parent ? parent.array_name + [ name ] : [ name ]; end
|
266
|
+
def full_name; array_name.join(' '); end
|
267
|
+
|
268
|
+
def pending?; @opts[:pending]; end
|
269
|
+
|
270
|
+
def line
|
271
|
+
|
272
|
+
(@block.respond_to?(:source_location) ? @block.source_location : [])[1]
|
96
273
|
end
|
97
274
|
|
98
|
-
def
|
275
|
+
def last_line
|
99
276
|
|
100
|
-
|
277
|
+
lln = map[path].find { |l0, l1, n| n == self }
|
278
|
+
|
279
|
+
l = lln && lln[1]
|
280
|
+
l && l > 0 ? l : 9_999_999
|
281
|
+
end
|
282
|
+
|
283
|
+
def path_and_line
|
101
284
|
|
102
|
-
|
285
|
+
[ @path, line ]
|
286
|
+
end
|
287
|
+
|
288
|
+
def location
|
289
|
+
|
290
|
+
"#{@path}:#{line}"
|
103
291
|
end
|
104
292
|
|
105
293
|
def to_s(opts={})
|
106
294
|
|
295
|
+
col = Probatio.c
|
107
296
|
out = opts[:out] || StringIO.new
|
108
|
-
|
109
|
-
gos = @group_opts.any? ? ' ' + @group_opts.inspect : ''
|
297
|
+
opts1 = opts.merge(out: out)
|
110
298
|
|
111
|
-
|
112
|
-
"#{ind}group #{@name.inspect}#{gos}\n"
|
299
|
+
pali = location; pali = pali.chop if pali.end_with?(':')
|
113
300
|
|
114
|
-
|
115
|
-
|
116
|
-
|
301
|
+
out << ' ' * depth
|
302
|
+
out << col.yellow(type)
|
303
|
+
out << (@name ? ' ' + @name.inspect : '')
|
304
|
+
out << (@opts.any? ? ' ' + @opts.inspect : '')
|
305
|
+
out << ' ' << col.dark_grey(pali)
|
306
|
+
out << "\n"
|
117
307
|
|
118
|
-
opts[:
|
308
|
+
@children.each { |c| c.to_s(opts1) } unless opts[:head]
|
309
|
+
|
310
|
+
opts[:out] ? nil : out.string.strip
|
119
311
|
end
|
120
312
|
|
121
|
-
def head; to_s(head: true); end
|
313
|
+
def head(opts={}); to_s(opts.merge(head: true)).strip; end
|
122
314
|
|
123
|
-
def
|
315
|
+
def trail(opts={})
|
124
316
|
|
125
|
-
|
126
|
-
|
127
|
-
# on_setup, on_teardown
|
128
|
-
# on_before, on_after
|
129
|
-
Probatio.despatch(:group_enter, self)
|
317
|
+
out = opts[:out] || StringIO.new
|
318
|
+
opts1 = opts.merge(out: out, head: true)
|
130
319
|
|
131
|
-
|
320
|
+
parent.trail(opts1) if parent
|
321
|
+
to_s(opts1)
|
132
322
|
|
133
|
-
|
323
|
+
opts[:out] ? nil : out.string.strip
|
324
|
+
end
|
134
325
|
|
135
|
-
|
326
|
+
def skip?(run_opts); false; end
|
136
327
|
|
137
|
-
|
328
|
+
def groups
|
138
329
|
|
139
|
-
|
330
|
+
@children.select { |c| c.is_a?(Probatio::Group) }
|
331
|
+
end
|
140
332
|
|
141
|
-
|
142
|
-
|
333
|
+
protected
|
334
|
+
|
335
|
+
def exclude?(run_opts); false; end
|
336
|
+
|
337
|
+
def map_self
|
338
|
+
|
339
|
+
l =
|
340
|
+
@block_source_location ? @block_source_location[1] :
|
341
|
+
@block ? @block.source_location[1] :
|
342
|
+
0
|
343
|
+
|
344
|
+
f = (map[path] ||= [])
|
345
|
+
|
346
|
+
f0 = f.last
|
347
|
+
f0[1] = (l == 0 ? f0[0] : l - 1) if f0
|
348
|
+
|
349
|
+
f << [ l, 0, self ]
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
class Section < Node
|
354
|
+
|
355
|
+
def path=(pat)
|
356
|
+
|
357
|
+
@path0 ||= @path
|
358
|
+
@path = pat
|
359
|
+
end
|
360
|
+
# so it can be set when groups are "re-opened"...
|
361
|
+
|
362
|
+
def initialize(parent_group, path, name, opts, block)
|
363
|
+
|
364
|
+
@block_source_location = block && block.source_location
|
365
|
+
|
366
|
+
super(parent_group, path, name, opts, nil)
|
367
|
+
|
368
|
+
parent_group.add_section(self, block) \
|
369
|
+
if self.class == Probatio::Section
|
370
|
+
end
|
371
|
+
|
372
|
+
def add_block(block)
|
373
|
+
|
374
|
+
instance_eval(&block) if block
|
375
|
+
end
|
376
|
+
|
377
|
+
def add_file(path)
|
143
378
|
|
144
|
-
|
379
|
+
@path = path
|
380
|
+
|
381
|
+
instance_eval(File.read(path), path, 1)
|
382
|
+
end
|
383
|
+
|
384
|
+
def run(run_opts)
|
385
|
+
|
386
|
+
return Probatio.despatch(:group_excluded, self) \
|
387
|
+
if exclude?(run_opts)
|
145
388
|
|
146
|
-
|
389
|
+
return Probatio.despatch(:group_pending, self) \
|
390
|
+
if opts[:pending]
|
391
|
+
|
392
|
+
return Probatio.despatch(:group_skipped, self) \
|
393
|
+
if skip?(run_opts)
|
394
|
+
|
395
|
+
Probatio.despatch(:group_enter, self)
|
396
|
+
|
397
|
+
(
|
398
|
+
setups +
|
399
|
+
shuffle(tests_and_groups) +
|
400
|
+
teardowns
|
401
|
+
).each { |c| c.run(run_opts) }
|
147
402
|
|
148
403
|
Probatio.despatch(:group_leave, self)
|
149
404
|
end
|
150
405
|
|
151
406
|
def setup(opts={}, &block)
|
152
|
-
@children << Probatio::Setup.new(self, nil, opts,
|
407
|
+
@children << Probatio::Setup.new(self, @path, nil, opts, block)
|
153
408
|
end
|
154
409
|
def teardown(opts={}, &block)
|
155
|
-
@children << Probatio::Teardown.new(self, nil, opts,
|
410
|
+
@children << Probatio::Teardown.new(self, @path, nil, opts, block)
|
156
411
|
end
|
157
412
|
def before(opts={}, &block)
|
158
|
-
@children << Probatio::Before.new(self, nil, opts,
|
413
|
+
@children << Probatio::Before.new(self, @path, nil, opts, block)
|
159
414
|
end
|
160
415
|
def after(opts={}, &block)
|
161
|
-
@children << Probatio::After.new(self, nil, opts,
|
416
|
+
@children << Probatio::After.new(self, @path, nil, opts, block)
|
162
417
|
end
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
if g = @children.find { |e| e.is_a?(Probatio::Group) && e.name == name }
|
167
|
-
g.path = @path
|
168
|
-
g.add_block(block)
|
169
|
-
else
|
170
|
-
@children << Probatio::Group.new(self, name, opts, @path, block)
|
171
|
-
end
|
418
|
+
def around(opts={}, &block)
|
419
|
+
@children << Probatio::Around.new(self, @path, nil, opts, block)
|
172
420
|
end
|
173
421
|
|
174
|
-
def
|
422
|
+
def arounds
|
175
423
|
|
176
|
-
@
|
424
|
+
(@parent ? @parent.arounds : []) +
|
425
|
+
group_sections.select { |s| s.is_a?(Probatio::Around) } +
|
426
|
+
@children.select { |c| c.is_a?(Probatio::Around) }
|
177
427
|
end
|
178
428
|
|
179
429
|
def befores
|
180
430
|
|
181
431
|
(@parent ? @parent.befores : []) +
|
432
|
+
group_sections.select { |s| s.is_a?(Probatio::Before) } +
|
182
433
|
@children.select { |c| c.is_a?(Probatio::Before) }
|
183
434
|
end
|
184
435
|
|
185
436
|
def afters
|
186
437
|
|
187
|
-
(
|
188
|
-
|
438
|
+
(
|
439
|
+
(@parent ? @parent.afters : []) +
|
440
|
+
group_sections.select { |c| c.is_a?(Probatio::After) } +
|
441
|
+
@children.select { |c| c.is_a?(Probatio::After) }
|
442
|
+
).reverse
|
189
443
|
end
|
190
444
|
|
191
445
|
ATTRS = %i[ @parent @name @group_opts @path @children ].freeze
|
192
446
|
|
193
447
|
def context(h={})
|
194
448
|
|
449
|
+
fail ArgumentError.new('Probatio says "trailing RSpec context?"') \
|
450
|
+
unless h.is_a?(Hash)
|
451
|
+
|
195
452
|
instance_variables
|
196
453
|
.each { |k|
|
197
454
|
h[k] = instance_variable_get(k) unless ATTRS.include?(k) }
|
@@ -200,49 +457,142 @@ module Probatio
|
|
200
457
|
h
|
201
458
|
end
|
202
459
|
|
460
|
+
METHS = %i[
|
461
|
+
_group _section
|
462
|
+
_setup _teardown _before _after
|
463
|
+
_test
|
464
|
+
].freeze
|
465
|
+
|
466
|
+
def method_missing(name, *args, &block)
|
467
|
+
|
468
|
+
if METHS.include?(name)
|
469
|
+
|
470
|
+
opts = args.find { |a| a.is_a?(Hash) }
|
471
|
+
args << {} unless opts; opts = args.last
|
472
|
+
opts[:pending] = true
|
473
|
+
|
474
|
+
send(name.to_s[1..-1], *args, &block)
|
475
|
+
|
476
|
+
else
|
477
|
+
|
478
|
+
super
|
479
|
+
end
|
480
|
+
end
|
481
|
+
|
482
|
+
def skip?(run_opts)
|
483
|
+
|
484
|
+
opts[:pending] ||
|
485
|
+
tests_and_groups.all? { |n| n.skip?(run_opts) }
|
486
|
+
end
|
487
|
+
|
488
|
+
def all_tests
|
489
|
+
|
490
|
+
tests + groups.inject([]) { |a, g| a.concat(g.all_tests) }
|
491
|
+
end
|
492
|
+
|
493
|
+
def add_section(section, block)
|
494
|
+
|
495
|
+
s = ((@sections ||= {})[section.name] ||= section)
|
496
|
+
s.add_block(block)
|
497
|
+
end
|
498
|
+
|
203
499
|
protected
|
204
500
|
|
205
|
-
def setups
|
206
|
-
|
501
|
+
def setups
|
502
|
+
|
503
|
+
group_sections.select { |c| c.is_a?(Probatio::Setup) } +
|
504
|
+
@children.select { |c| c.is_a?(Probatio::Setup) }
|
505
|
+
end
|
506
|
+
|
507
|
+
def teardowns
|
508
|
+
|
509
|
+
group_sections.select { |c| c.is_a?(Probatio::Teardown) } +
|
510
|
+
@children.select { |c| c.is_a?(Probatio::Teardown) }
|
511
|
+
end
|
512
|
+
|
513
|
+
def tests_and_groups
|
207
514
|
|
208
|
-
def test_and_groups
|
209
515
|
@children.select { |c|
|
210
516
|
c.is_a?(Probatio::Test) || c.is_a?(Probatio::Group) }
|
211
517
|
end
|
518
|
+
|
212
519
|
def tests; @children.select { |c| c.is_a?(Probatio::Test) }; end
|
213
520
|
def groups; @children.select { |c| c.is_a?(Probatio::Group) }; end
|
214
|
-
end
|
215
521
|
|
216
|
-
|
522
|
+
def shuffle(a)
|
217
523
|
|
218
|
-
|
524
|
+
case Probatio.seed
|
525
|
+
when 0 then a
|
526
|
+
when -1 then a.reverse
|
527
|
+
else a.shuffle(random: Probatio.rng)
|
528
|
+
end
|
529
|
+
end
|
219
530
|
|
220
|
-
def
|
531
|
+
def section_drill
|
221
532
|
|
222
|
-
@parent
|
223
|
-
@
|
224
|
-
@opts = opts
|
225
|
-
@path = path
|
226
|
-
@block = block
|
533
|
+
(@parent ? @parent.section_drill : []) +
|
534
|
+
(@sections || {}).values
|
227
535
|
end
|
228
536
|
|
229
|
-
def
|
537
|
+
def group_sections
|
230
538
|
|
231
|
-
|
539
|
+
@_group_sections ||=
|
540
|
+
section_drill
|
541
|
+
.inject([]) { |a, s| a.concat(s.children) if s.name == name; a }
|
232
542
|
end
|
543
|
+
end
|
233
544
|
|
234
|
-
|
545
|
+
class Group < Section
|
235
546
|
|
236
|
-
|
237
|
-
os = @opts.any? ? ' ' + @opts.inspect : ''
|
238
|
-
_, l = block.source_location
|
547
|
+
def group(*names, &block)
|
239
548
|
|
240
|
-
|
241
|
-
|
549
|
+
opts = names.last.is_a?(Hash) ? names.pop : {}
|
550
|
+
|
551
|
+
names = names
|
552
|
+
.collect { |s| s.to_s.split(/\s*(?:\||;|<|>)\s*/) }
|
553
|
+
.flatten(1)
|
554
|
+
|
555
|
+
last_name = names.last
|
556
|
+
|
557
|
+
names.inject(self) do |g, name|
|
558
|
+
|
559
|
+
gg = g.groups.find { |e| e.name == name }
|
560
|
+
|
561
|
+
if gg
|
562
|
+
gg.path = @path
|
563
|
+
else
|
564
|
+
gg = Probatio::Group.new(g, @path, name, opts, block)
|
565
|
+
g.children << gg
|
566
|
+
end
|
567
|
+
|
568
|
+
gg.add_block(block) if name == last_name
|
569
|
+
|
570
|
+
gg
|
571
|
+
end
|
242
572
|
end
|
243
573
|
|
574
|
+
def section(name, opts={}, &block)
|
575
|
+
|
576
|
+
@children << Probatio::Section.new(self, @path, name.to_s, opts, block) \
|
577
|
+
unless opts[:pending]
|
578
|
+
end
|
579
|
+
|
580
|
+
def test(name, opts={}, &block)
|
581
|
+
|
582
|
+
@children << Probatio::Test.new(self, @path, name.to_s, opts, block)
|
583
|
+
end
|
584
|
+
end
|
585
|
+
|
586
|
+
class Leaf < Node
|
587
|
+
|
244
588
|
def run(run_opts)
|
245
589
|
|
590
|
+
return Probatio.despatch("#{type}_excluded", self) \
|
591
|
+
if exclude?(run_opts)
|
592
|
+
|
593
|
+
return Probatio.despatch("#{type}_pending", self) \
|
594
|
+
if @opts[:pending]
|
595
|
+
|
246
596
|
Probatio.despatch("#{type}_enter", self)
|
247
597
|
|
248
598
|
@parent.instance_eval(&@block)
|
@@ -251,144 +601,410 @@ module Probatio
|
|
251
601
|
end
|
252
602
|
end
|
253
603
|
|
254
|
-
class Setup <
|
255
|
-
class Teardown <
|
604
|
+
class Setup < Leaf; end
|
605
|
+
class Teardown < Leaf; end
|
256
606
|
|
257
|
-
class Before <
|
258
|
-
class After <
|
607
|
+
class Before < Leaf; end
|
608
|
+
class After < Leaf; end
|
609
|
+
class Around < Leaf; end
|
259
610
|
|
260
|
-
class Test <
|
611
|
+
class Test < Leaf
|
261
612
|
|
262
|
-
|
613
|
+
alias group parent
|
263
614
|
|
264
|
-
def
|
615
|
+
def run(run_opts)
|
265
616
|
|
266
|
-
|
617
|
+
return Probatio.despatch(:test_excluded, self) \
|
618
|
+
if exclude?(run_opts)
|
619
|
+
|
620
|
+
return Probatio.despatch(:test_pending, self) \
|
621
|
+
if opts[:pending]
|
622
|
+
|
623
|
+
Probatio::Context.new(group, self).run(run_opts)
|
624
|
+
end
|
625
|
+
|
626
|
+
def skip?(run_opts)
|
627
|
+
|
628
|
+
opts[:pending] ||
|
629
|
+
exclude?(run_opts)
|
630
|
+
end
|
631
|
+
|
632
|
+
protected
|
633
|
+
|
634
|
+
def exclude?(run_opts)
|
635
|
+
|
636
|
+
return true if in_setup?
|
637
|
+
|
638
|
+
if incs = run_opts[:includes]
|
639
|
+
return true unless incs.find { |e| do_match?(e) }
|
640
|
+
elsif exes = run_opts[:excludes]
|
641
|
+
return true if exes.find { |e| do_match?(e) }
|
642
|
+
end
|
643
|
+
|
644
|
+
fz = run_opts[:filez]
|
645
|
+
return false if fz && fz.include?(@path)
|
646
|
+
|
647
|
+
fns = run_opts[:filen]
|
648
|
+
return true if fns && ! fns.find { |pa, li| path_and_line_match?(pa, li) }
|
649
|
+
|
650
|
+
false
|
267
651
|
end
|
268
652
|
|
269
|
-
def
|
653
|
+
def do_match?(pattern_or_string)
|
270
654
|
|
271
|
-
|
655
|
+
full_name.match?(pattern_or_string)
|
656
|
+
end
|
272
657
|
|
273
|
-
|
658
|
+
def path_and_line_match?(fpath, fline)
|
659
|
+
|
660
|
+
#p [ path, line, last_line, '<-->', fpath, fline ]
|
661
|
+
line &&
|
662
|
+
path == fpath &&
|
663
|
+
fline >= line && fline <= last_line
|
664
|
+
end
|
274
665
|
|
275
|
-
|
666
|
+
def in_setup?
|
276
667
|
|
277
|
-
|
278
|
-
ensure
|
279
|
-
Probatio.despatch("#{child.type}_leave", self, child, run_opts)
|
668
|
+
filename == 'setup.rb' || filename.end_with?('_setup.rb')
|
280
669
|
end
|
670
|
+
end
|
281
671
|
|
282
|
-
|
672
|
+
class Context
|
283
673
|
|
284
|
-
|
674
|
+
attr_reader :__group
|
675
|
+
attr_reader :__test
|
285
676
|
|
286
|
-
|
287
|
-
|
288
|
-
|
677
|
+
def initialize(group, test)
|
678
|
+
|
679
|
+
@__group = group
|
680
|
+
@__test = test
|
681
|
+
|
682
|
+
group.context.each { |k, v| instance_variable_set(k, v) }
|
289
683
|
end
|
290
684
|
|
291
|
-
def
|
685
|
+
def block; @__block; end
|
292
686
|
|
293
|
-
|
687
|
+
def run(run_opts)
|
294
688
|
|
295
|
-
|
296
|
-
|
689
|
+
_run(@__group.arounds + [ :do_run ], run_opts)
|
690
|
+
end
|
297
691
|
|
298
|
-
|
299
|
-
|
692
|
+
def __test_name; @__test.name; end
|
693
|
+
def __group_name; @__group.name; end
|
694
|
+
|
695
|
+
protected
|
696
|
+
|
697
|
+
def _run(arounds, run_opts)
|
698
|
+
|
699
|
+
if (ar = arounds.shift).is_a?(Probatio::Around)
|
700
|
+
do_run(ar, run_opts) { _run(arounds, run_opts) }
|
701
|
+
else
|
702
|
+
@__group.befores.each { |bf| do_run(bf, run_opts) }
|
703
|
+
do_run(@__test, run_opts)
|
704
|
+
@__group.afters.each { |af| do_run(af, run_opts) }
|
300
705
|
end
|
301
706
|
end
|
302
707
|
|
303
|
-
|
708
|
+
def do_run(child, run_opts, &block)
|
304
709
|
|
305
|
-
|
710
|
+
fail ArgumentError.new("invalid child opts #{child.opts.inspect}") \
|
711
|
+
unless child.opts.is_a?(Hash)
|
306
712
|
|
307
|
-
|
308
|
-
|
309
|
-
block.call
|
310
|
-
rescue => err
|
311
|
-
err
|
312
|
-
end
|
713
|
+
return Probatio.despatch("#{child.type}_pending", self, child, run_opts) \
|
714
|
+
if child.opts[:pending] || child.block.nil?
|
313
715
|
|
314
|
-
|
716
|
+
begin
|
315
717
|
|
316
|
-
|
718
|
+
@__child = child
|
719
|
+
@__block = block
|
317
720
|
|
318
|
-
|
721
|
+
Probatio.despatch("#{child.type}_enter", self, child, run_opts)
|
319
722
|
|
320
|
-
|
723
|
+
r =
|
724
|
+
run_opts[:dry] ? nil :
|
725
|
+
instance_eval(&child.block)
|
321
726
|
|
322
|
-
|
323
|
-
|
727
|
+
Probatio.despatch(:test_succeed, self, child) \
|
728
|
+
if r != :pending && child.type == 'test'
|
729
|
+
|
730
|
+
rescue AssertionError
|
731
|
+
|
732
|
+
#Probatio.despatch(:test_fail, self, child)
|
733
|
+
# done in the assertion implementation...
|
734
|
+
|
735
|
+
rescue StandardError => serr
|
736
|
+
|
737
|
+
class << serr; include Probatio::ExtraErrorMethods; end
|
738
|
+
serr.test = child
|
324
739
|
|
325
|
-
|
740
|
+
Probatio.despatch(:test_fail, self, child, serr)
|
326
741
|
|
327
|
-
|
742
|
+
ensure
|
743
|
+
|
744
|
+
Probatio.despatch("#{child.type}_leave", self, child, run_opts)
|
745
|
+
end
|
328
746
|
end
|
747
|
+
|
748
|
+
require 'probatio/assertions'
|
749
|
+
#
|
750
|
+
# where assert_* methods are defined...
|
751
|
+
|
752
|
+
require 'probatio/waiters'
|
753
|
+
#
|
754
|
+
# where wait_* methods are defined...
|
329
755
|
end
|
330
756
|
|
331
757
|
class AssertionError < StandardError
|
332
758
|
|
759
|
+
attr_reader :assertion, :arguments, :test, :file, :line
|
333
760
|
attr_accessor :nested_error
|
334
761
|
|
335
|
-
|
762
|
+
alias path file
|
763
|
+
|
764
|
+
def initialize(assertion, arguments, error_or_message, test, file, line)
|
765
|
+
|
766
|
+
@assertion = assertion
|
767
|
+
@arguments = arguments
|
768
|
+
|
769
|
+
@test = test
|
770
|
+
|
771
|
+
@file = file
|
772
|
+
@line = line
|
336
773
|
|
337
774
|
if error_or_message.is_a?(String)
|
338
|
-
|
775
|
+
@msg = error_or_message
|
339
776
|
else
|
340
|
-
|
777
|
+
@msg = "error while asserting: " + error_or_message.message
|
341
778
|
@nested_error = error_or_message
|
342
779
|
end
|
780
|
+
|
781
|
+
super(@msg)
|
782
|
+
end
|
783
|
+
|
784
|
+
def location
|
785
|
+
|
786
|
+
[ @file, @line ]
|
787
|
+
end
|
788
|
+
|
789
|
+
def loc
|
790
|
+
|
791
|
+
location.map(&:to_s).join(':')
|
792
|
+
end
|
793
|
+
|
794
|
+
def to_s
|
795
|
+
|
796
|
+
"#{self.class.name}: #{@msg}"
|
797
|
+
end
|
798
|
+
|
799
|
+
def trail
|
800
|
+
|
801
|
+
@test.trail + "\n" +
|
802
|
+
Probatio.c.red("#{' ' * (test.depth + 1)}#{loc} --> #{@msg}")
|
803
|
+
end
|
804
|
+
|
805
|
+
def source_line
|
806
|
+
|
807
|
+
@source_line ||=
|
808
|
+
File.readlines(@file)[@line - 1]
|
809
|
+
end
|
810
|
+
|
811
|
+
def source_lines
|
812
|
+
|
813
|
+
@source_lines ||=
|
814
|
+
Probatio::AssertionError.select_source_lines(@file, @line)
|
815
|
+
end
|
816
|
+
|
817
|
+
def summary(indent='')
|
818
|
+
|
819
|
+
tw = Probatio.term_width - 4 - indent.length
|
820
|
+
|
821
|
+
as =
|
822
|
+
@arguments.find { |a| a.inspect.length > tw } ?
|
823
|
+
@arguments.collect { |a|
|
824
|
+
if (s0 = a.inspect).length < tw
|
825
|
+
"\n#{indent} " + s0
|
826
|
+
else
|
827
|
+
s1 = StringIO.new; PP.pp(a, s1, tw)
|
828
|
+
qualify_argument(a) + "\n" +
|
829
|
+
indent + s1.string.gsub(/^(.*)$/) { " #{$1}" }
|
830
|
+
end } :
|
831
|
+
@arguments.collect(&:inspect)
|
832
|
+
|
833
|
+
s = StringIO.new
|
834
|
+
s << indent << @assertion << ':'
|
835
|
+
as.each_with_index { |a, i| s << "\n#{indent} %d: %s" % [ i, a ] }
|
836
|
+
|
837
|
+
s.string
|
838
|
+
end
|
839
|
+
|
840
|
+
class << self
|
841
|
+
|
842
|
+
def select_source_lines(path, line)
|
843
|
+
|
844
|
+
File.readlines(path).each_with_index.to_a[line - 1..-1]
|
845
|
+
.map { |l, i| [ i + 1, l.rstrip ] }
|
846
|
+
.take_while { |_, l|
|
847
|
+
l = l.strip
|
848
|
+
l.length > 0 && l != 'end' && l != '}' }
|
849
|
+
end
|
850
|
+
end
|
851
|
+
|
852
|
+
protected
|
853
|
+
|
854
|
+
def qualify_argument(a)
|
855
|
+
|
856
|
+
'<' +
|
857
|
+
a.class.to_s +
|
858
|
+
(a.respond_to?(:size) ? " size:#{a.size}" : '') +
|
859
|
+
'>'
|
860
|
+
end
|
861
|
+
end
|
862
|
+
|
863
|
+
module ExtraErrorMethods
|
864
|
+
|
865
|
+
attr_accessor :test
|
866
|
+
|
867
|
+
def path; test.path; end
|
868
|
+
def location; [ path, line ]; end
|
869
|
+
def loc; location.map(&:to_s).join(':'); end
|
870
|
+
|
871
|
+
def trail
|
872
|
+
|
873
|
+
msg = "#{self.class}: #{self.message.inspect}"
|
874
|
+
|
875
|
+
@test.trail + "\n" +
|
876
|
+
Probatio.c.red("#{' ' * (test.depth + 1)}#{loc} --> #{msg}")
|
877
|
+
end
|
878
|
+
|
879
|
+
def source_lines
|
880
|
+
|
881
|
+
@source_lines ||=
|
882
|
+
Probatio::AssertionError.select_source_lines(test.path, line)
|
883
|
+
end
|
884
|
+
|
885
|
+
def summary(indent='')
|
886
|
+
|
887
|
+
o = StringIO.new
|
888
|
+
|
889
|
+
o << self.class.name << ': ' << self.message.inspect << "\n"
|
890
|
+
|
891
|
+
i = backtrace.index { |l| l.match?(/\/lib\/probatio\.rb:/) } || -1
|
892
|
+
|
893
|
+
backtrace[0..i]
|
894
|
+
.inject(o) { |o, l| o << indent << l << "\n" }
|
895
|
+
|
896
|
+
o.string
|
897
|
+
end
|
898
|
+
|
899
|
+
def line
|
900
|
+
|
901
|
+
backtrace.each do |l|
|
902
|
+
|
903
|
+
ss = l.split(':')
|
904
|
+
|
905
|
+
next unless ss.find { |e| e == test.path }
|
906
|
+
return ss.find { |e| e.match?(/^\d+$/) }.to_i
|
907
|
+
end
|
908
|
+
|
909
|
+
-1
|
343
910
|
end
|
344
911
|
end
|
345
912
|
|
346
913
|
class Event
|
347
914
|
|
348
|
-
attr_reader :
|
915
|
+
attr_reader :tstamp, :delta
|
916
|
+
attr_reader :name, :opts, :context, :group, :leaf, :error
|
917
|
+
attr_accessor :leave_delta
|
349
918
|
|
350
919
|
def initialize(name, details)
|
351
920
|
|
352
|
-
@
|
921
|
+
@tstamp, @delta = Probatio.monow_and_delta
|
922
|
+
|
923
|
+
@name = name.to_s
|
353
924
|
|
354
925
|
details.each do |d|
|
355
926
|
case d
|
356
927
|
when Hash then @opts = d
|
928
|
+
when Exception then @error = d
|
929
|
+
when Probatio::Leaf then @leaf = d
|
357
930
|
when Probatio::Group then @group = d
|
358
|
-
when Probatio::Child then @child = d
|
359
931
|
when Probatio::Context then @context = d
|
360
932
|
else fail ArgumentError.new("cannot fathom #{d.class} #{d.inspect}")
|
361
933
|
end
|
362
934
|
end
|
363
935
|
end
|
364
|
-
end
|
365
|
-
end
|
366
936
|
|
367
|
-
|
937
|
+
def direction; @direction ||= name.split('_').last.to_sym; end
|
938
|
+
def node; @leaf || @group; end
|
939
|
+
def depth; node.depth rescue 0; end
|
368
940
|
|
369
|
-
|
941
|
+
def type; @name.split('_').first; end
|
942
|
+
#
|
943
|
+
# which, in the case of assertion != self.node.type ...
|
944
|
+
|
945
|
+
def node_full_name; node && node.full_name; end
|
946
|
+
|
947
|
+
def enter?; direction == :enter; end
|
948
|
+
def leave?; direction == :leave; end
|
949
|
+
|
950
|
+
def determine_leave_delta
|
951
|
+
|
952
|
+
lev = Probatio.recorder_plugin.test_leave_event(node)
|
953
|
+
|
954
|
+
lev && lev.leave_delta
|
955
|
+
end
|
956
|
+
|
957
|
+
def delta_s
|
958
|
+
|
959
|
+
led = determine_leave_delta
|
960
|
+
led ? Probatio.to_time_s(led) : '?'
|
961
|
+
end
|
370
962
|
|
371
|
-
def
|
963
|
+
def location
|
372
964
|
|
373
|
-
|
374
|
-
|
965
|
+
(error && error.respond_to?(:location) && error.location) ||
|
966
|
+
(node && node.location)
|
375
967
|
end
|
376
968
|
|
377
|
-
def
|
378
|
-
|
379
|
-
|
969
|
+
def path
|
970
|
+
|
971
|
+
node && node.path
|
380
972
|
end
|
381
973
|
|
382
|
-
def
|
383
|
-
|
384
|
-
|
974
|
+
def to_s
|
975
|
+
|
976
|
+
led = determine_leave_delta
|
977
|
+
|
978
|
+
o = StringIO.new
|
979
|
+
o << "<event"
|
980
|
+
o << "\n name=#{name.inspect}"
|
981
|
+
o << "\n node=#{node.full_name.inspect}" if node
|
982
|
+
o << "\n node_type=#{node.type.inspect}" if node
|
983
|
+
o << "\n error=#{error.to_s.inspect}" if error
|
984
|
+
o << "\n location=#{location.map(&:to_s).join(':').inspect}" if node
|
985
|
+
o << "\n delta=\"#{Probatio.to_time_s(delta)}\"" if delta
|
986
|
+
o << "\n leave_delta=\"#{Probatio.to_time_s(led)}\"" if led
|
987
|
+
o << " />"
|
988
|
+
|
989
|
+
o.string
|
385
990
|
end
|
386
991
|
|
387
|
-
def
|
388
|
-
|
992
|
+
def to_h
|
993
|
+
|
994
|
+
{ n: name, p: location[0], l: location[1], t: delta_s }
|
389
995
|
end
|
390
996
|
end
|
391
997
|
end
|
392
998
|
|
393
|
-
|
999
|
+
|
1000
|
+
Probatio.init
|
1001
|
+
|
1002
|
+
|
1003
|
+
require 'probatio/plug'
|
1004
|
+
#
|
1005
|
+
# when Probatio.plug and friends are defined
|
1006
|
+
|
1007
|
+
require 'probatio/plugins'
|
1008
|
+
#
|
1009
|
+
# plugins that listen to dispatches, report, and summarize
|
394
1010
|
|