probatio 0.9.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -1
- data/README.md +301 -0
- data/exe/proba +245 -1
- data/lib/probatio/assertions.rb +311 -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 = '0.
|
18
|
+
VERSION = '1.0.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
|
|