tapout 0.3.2 → 0.4.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.
- data/.ruby +12 -3
- data/.yardopts +6 -5
- data/HISTORY.rdoc +16 -0
- data/README.rdoc +18 -16
- data/TAP-YJ.md +15 -15
- data/bin/tapout +1 -1
- data/demo/applique/ae.rb +1 -0
- data/{spec → demo}/applique/env.rb +0 -0
- data/{spec → demo}/issues/applique/env.rb +0 -0
- data/{spec → demo}/issues/default_reporter.rdoc +2 -2
- data/{spec → demo}/perl_adapter.rdoc +3 -3
- data/{spec → demo}/reporters/applique/cli.rb +1 -1
- data/{spec → demo}/reporters/fixtures/tapy.yml +0 -0
- data/{spec → demo}/reporters/reporters.rdoc +0 -0
- data/lib/tapout.rb +5 -101
- data/lib/tapout.yml +12 -3
- data/lib/tapout/adapters/perl.rb +1 -1
- data/lib/tapout/cli.rb +98 -0
- data/lib/tapout/config.rb +103 -0
- data/lib/tapout/parsers/json.rb +2 -2
- data/lib/tapout/parsers/perl.rb +1 -1
- data/lib/tapout/parsers/yaml.rb +3 -2
- data/lib/tapout/reporters.rb +2 -1
- data/lib/tapout/reporters/abstract.rb +223 -31
- data/lib/tapout/reporters/breakdown_reporter.rb +49 -57
- data/lib/tapout/reporters/dot_reporter.rb +52 -33
- data/lib/tapout/reporters/html_reporter.rb +3 -1
- data/lib/tapout/reporters/markdown_reporter.rb +101 -0
- data/lib/tapout/reporters/outline_reporter.rb +68 -25
- data/lib/tapout/reporters/pretty_reporter.rb +65 -86
- data/lib/tapout/reporters/progress_reporter.rb +47 -22
- data/lib/tapout/reporters/runtime_reporter.rb +223 -0
- data/lib/tapout/reporters/tap_reporter.rb +1 -1
- data/lib/tapout/reporters/turn_reporter.rb +26 -65
- data/lib/tapout/version.rb +2 -2
- metadata +34 -19
- data/test/unit/test-progressbar.rb +0 -5
@@ -0,0 +1,103 @@
|
|
1
|
+
module Tapout
|
2
|
+
|
3
|
+
#
|
4
|
+
#
|
5
|
+
def self.config
|
6
|
+
@config ||= Config.new
|
7
|
+
end
|
8
|
+
|
9
|
+
#
|
10
|
+
#
|
11
|
+
class Config
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
initialize_defaults
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize_defaults
|
18
|
+
@trace = nil
|
19
|
+
@lines = 3
|
20
|
+
|
21
|
+
@minimal = false
|
22
|
+
|
23
|
+
@highlight = [:bold]
|
24
|
+
@fadelight = [:dark]
|
25
|
+
|
26
|
+
@pass = [:green]
|
27
|
+
@fail = [:red]
|
28
|
+
@error = [:red]
|
29
|
+
@todo = [:yellow]
|
30
|
+
@omit = [:yellow]
|
31
|
+
end
|
32
|
+
|
33
|
+
#
|
34
|
+
attr :trace
|
35
|
+
|
36
|
+
#
|
37
|
+
def trace=(depth)
|
38
|
+
@trace = depth.to_i
|
39
|
+
end
|
40
|
+
|
41
|
+
#
|
42
|
+
attr :lines
|
43
|
+
|
44
|
+
#
|
45
|
+
def lines=(count)
|
46
|
+
@lines = count.to_i
|
47
|
+
end
|
48
|
+
|
49
|
+
#
|
50
|
+
def minimal?
|
51
|
+
@minimal
|
52
|
+
end
|
53
|
+
|
54
|
+
#
|
55
|
+
def minimal=(boolean)
|
56
|
+
@minimal = boolean ? true : false
|
57
|
+
end
|
58
|
+
|
59
|
+
# ANSI highlight
|
60
|
+
attr :highlight
|
61
|
+
|
62
|
+
def highlight=(ansi)
|
63
|
+
@highlight = [ansi].flatten
|
64
|
+
end
|
65
|
+
|
66
|
+
# ANSI pass
|
67
|
+
attr :pass
|
68
|
+
|
69
|
+
def pass=(ansi)
|
70
|
+
@pass = [ansi].flatten
|
71
|
+
end
|
72
|
+
|
73
|
+
# ANSI fail
|
74
|
+
attr :fail
|
75
|
+
|
76
|
+
def fail=(ansi)
|
77
|
+
@fail = [ansi].flatten
|
78
|
+
end
|
79
|
+
|
80
|
+
# ANSI err
|
81
|
+
attr :error
|
82
|
+
|
83
|
+
def error=(ansi)
|
84
|
+
@error = [ansi].flatten
|
85
|
+
end
|
86
|
+
|
87
|
+
# ANSI todo
|
88
|
+
attr :todo
|
89
|
+
|
90
|
+
def todo=(ansi)
|
91
|
+
@todo = [ansi].flatten
|
92
|
+
end
|
93
|
+
|
94
|
+
# ANSI omit
|
95
|
+
attr :omit
|
96
|
+
|
97
|
+
def omit=(ansi)
|
98
|
+
@omit = [ansi].flatten
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
data/lib/tapout/parsers/json.rb
CHANGED
data/lib/tapout/parsers/perl.rb
CHANGED
data/lib/tapout/parsers/yaml.rb
CHANGED
data/lib/tapout/reporters.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'tapout/reporters/abstract'
|
2
2
|
|
3
|
+
require 'tapout/reporters/runtime_reporter'
|
3
4
|
require 'tapout/reporters/dot_reporter'
|
4
5
|
require 'tapout/reporters/pretty_reporter'
|
5
6
|
require 'tapout/reporters/turn_reporter'
|
@@ -8,4 +9,4 @@ require 'tapout/reporters/outline_reporter'
|
|
8
9
|
require 'tapout/reporters/progress_reporter'
|
9
10
|
require 'tapout/reporters/breakdown_reporter'
|
10
11
|
require 'tapout/reporters/tap_reporter'
|
11
|
-
|
12
|
+
require 'tapout/reporters/markdown_reporter'
|
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'ansi'
|
2
2
|
require 'abbrev'
|
3
3
|
|
4
|
-
module
|
4
|
+
module Tapout
|
5
5
|
|
6
6
|
# Namespace for Report Formats.
|
7
7
|
module Reporters
|
@@ -26,7 +26,6 @@ module TapOut
|
|
26
26
|
# The Abstract class serves as a base class for all reporters. Reporters
|
27
27
|
# must sublcass Abstract in order to be added the the Reporters Index.
|
28
28
|
#
|
29
|
-
# TODO: Simplify this class and have the sublcasses handle more of the load.
|
30
29
|
class Abstract
|
31
30
|
|
32
31
|
# When Abstract is inherited it saves a reference to it in `Reporters.index`.
|
@@ -36,6 +35,8 @@ module TapOut
|
|
36
35
|
Reporters.index[name] = subclass
|
37
36
|
end
|
38
37
|
|
38
|
+
#old_sync, @@out.sync = @@out.sync, true if io.respond_to? :sync=
|
39
|
+
|
39
40
|
# New reporter.
|
40
41
|
def initialize
|
41
42
|
@passed = []
|
@@ -56,6 +57,7 @@ module TapOut
|
|
56
57
|
|
57
58
|
# Handle header.
|
58
59
|
def start_suite(entry)
|
60
|
+
@start_time = Time.now
|
59
61
|
end
|
60
62
|
|
61
63
|
# At the start of a new test case.
|
@@ -87,10 +89,13 @@ module TapOut
|
|
87
89
|
end
|
88
90
|
|
89
91
|
# Handle test with skip or pending status.
|
90
|
-
def
|
92
|
+
def todo(entry)
|
91
93
|
@skipped << entry
|
92
94
|
end
|
93
95
|
|
96
|
+
# Same as todo.
|
97
|
+
alias_method :skip, :todo
|
98
|
+
|
94
99
|
# Handle an arbitray note.
|
95
100
|
def note(entry)
|
96
101
|
end
|
@@ -144,7 +149,7 @@ module TapOut
|
|
144
149
|
when 'omit'
|
145
150
|
omit(entry)
|
146
151
|
when 'todo', 'skip', 'pending'
|
147
|
-
|
152
|
+
todo(entry)
|
148
153
|
end
|
149
154
|
finish_test(entry)
|
150
155
|
when 'tally'
|
@@ -160,46 +165,92 @@ module TapOut
|
|
160
165
|
@exit_code
|
161
166
|
end
|
162
167
|
|
168
|
+
# Calculate the lapsed time, the rate of testing and average time per test.
|
169
|
+
#
|
170
|
+
# @return [Array<Float>] Lapsed time, rate and average.
|
171
|
+
def time_tally(entry)
|
172
|
+
total = @passed.size + @failed.size + @raised.size + @skipped.size + @omitted.size
|
173
|
+
total = entry['counts']['total'] || total
|
174
|
+
|
175
|
+
time = (entry['time'] || (Time.now - @start_time)).to_f
|
176
|
+
rate = total / time
|
177
|
+
avg = time / total
|
178
|
+
|
179
|
+
return time, rate, avg
|
180
|
+
end
|
181
|
+
|
182
|
+
# Return the total counts given a tally or final entry.
|
183
|
+
#
|
184
|
+
# @return [Array<Integer>] The total, fail, error, todo and omit counts.
|
185
|
+
def count_tally(entry)
|
186
|
+
total = @passed.size + @failed.size + @raised.size + @skipped.size + @omitted.size
|
187
|
+
total = entry['counts']['total'] || total
|
188
|
+
|
189
|
+
if counts = entry['counts']
|
190
|
+
pass = counts['pass'] || @passed.size
|
191
|
+
fail = counts['fail'] || @failed.size
|
192
|
+
error = counts['error'] || @raised.size
|
193
|
+
todo = counts['todo'] || @skipped.size
|
194
|
+
omit = counts['omit'] || @omitted.size
|
195
|
+
else
|
196
|
+
pass, fail, error, todo, omit = *[@passed, @failed, @raised, @skipped, @omitted].map{ |e| e.size }
|
197
|
+
end
|
198
|
+
|
199
|
+
return total, pass, fail, error, todo, omit
|
200
|
+
end
|
201
|
+
|
163
202
|
# Generate a tally message given a tally or final entry.
|
164
203
|
#
|
165
204
|
# @return [String] tally message
|
166
205
|
def tally_message(entry)
|
167
|
-
|
206
|
+
sums = count_tally(entry)
|
168
207
|
|
169
|
-
|
170
|
-
total = entry['counts']['total'] || total
|
171
|
-
count_fail = entry['counts']['fail'] || 0
|
172
|
-
count_error = entry['counts']['error'] || 0
|
173
|
-
else
|
174
|
-
count_fail = @failed.size
|
175
|
-
count_error = @raised.size
|
176
|
-
end
|
208
|
+
total, pass, fail, error, todo, omit = *sums
|
177
209
|
|
178
|
-
|
179
|
-
|
210
|
+
# TODO: Assertion counts isn't TAP-Y/J spec, is it a good idea to add ?
|
211
|
+
if entry['counts'] && entry['counts']['assertions']
|
212
|
+
assertions = entry['counts']['assertions']['pass']
|
213
|
+
failures = entry['counts']['assertions']['fail']
|
180
214
|
else
|
181
|
-
|
215
|
+
assertions = nil
|
216
|
+
failures = nil
|
182
217
|
end
|
183
218
|
|
184
|
-
|
185
|
-
|
186
|
-
|
219
|
+
text = []
|
220
|
+
text << "%s pass".ansi(*config.pass)
|
221
|
+
text << "%s fail".ansi(*config.fail)
|
222
|
+
text << "%s errs".ansi(*config.error)
|
223
|
+
text << "%s todo".ansi(*config.todo)
|
224
|
+
text << "%s omit".ansi(*config.omit)
|
225
|
+
text = "%s tests: " + text.join(", ")
|
187
226
|
|
188
227
|
if assertions
|
189
|
-
text
|
190
|
-
text = text %
|
228
|
+
text << " (%s/%s assertions)"
|
229
|
+
text = text % (sums + [assertions - failures, assertions])
|
191
230
|
else
|
192
|
-
text =
|
193
|
-
text = text % [total, *sums]
|
231
|
+
text = text % sums
|
194
232
|
end
|
195
233
|
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
234
|
+
text
|
235
|
+
end
|
236
|
+
|
237
|
+
# Give a test entry, returns a clean and filtered backtrace.
|
238
|
+
#
|
239
|
+
def backtrace(test)
|
240
|
+
exception = test['exception']
|
241
|
+
|
242
|
+
trace = exception['backtrace']
|
243
|
+
file = exception['file']
|
244
|
+
line = exception['line']
|
245
|
+
|
246
|
+
if trace
|
247
|
+
trace = clean_backtrace(trace)
|
200
248
|
else
|
201
|
-
|
249
|
+
trace = []
|
250
|
+
trace << "#{file}:#{line}" if file && line
|
202
251
|
end
|
252
|
+
|
253
|
+
trace
|
203
254
|
end
|
204
255
|
|
205
256
|
# Used to clean-up backtrace.
|
@@ -207,9 +258,13 @@ module TapOut
|
|
207
258
|
# TODO: Use Rubinius global system instead.
|
208
259
|
INTERNALS = /(lib|bin)#{Regexp.escape(File::SEPARATOR)}tapout/
|
209
260
|
|
210
|
-
# Clean the backtrace of any reference
|
261
|
+
# Clean the backtrace of any "boring" reference.
|
211
262
|
def clean_backtrace(backtrace)
|
212
|
-
|
263
|
+
if ENV['debug']
|
264
|
+
trace = backtrace
|
265
|
+
else
|
266
|
+
trace = backtrace.reject{ |bt| bt =~ INTERNALS }
|
267
|
+
end
|
213
268
|
trace = trace.map do |bt|
|
214
269
|
if i = bt.index(':in')
|
215
270
|
bt[0...i]
|
@@ -222,6 +277,59 @@ module TapOut
|
|
222
277
|
trace
|
223
278
|
end
|
224
279
|
|
280
|
+
# Get s nicely formatted string of backtrace and source code, ready
|
281
|
+
# for output.
|
282
|
+
#
|
283
|
+
# @return [String] Formatted backtrace with source code.
|
284
|
+
def backtrace_snippets(test)
|
285
|
+
string = []
|
286
|
+
backtrace_snippets_chain(test).each do |(stamp, snip)|
|
287
|
+
string << stamp.ansi(*config.highlight)
|
288
|
+
if snip
|
289
|
+
if snip.index('=>')
|
290
|
+
string << snip.sub(/(\=\>.*?)$/, '\1'.ansi(*config.highlight))
|
291
|
+
else
|
292
|
+
string << snip
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|
296
|
+
string.join("\n")
|
297
|
+
end
|
298
|
+
|
299
|
+
# Returns an associative array of backtraces along with corresponding
|
300
|
+
# source code, if available.
|
301
|
+
#
|
302
|
+
# @return [Array<String,String>]
|
303
|
+
# Array of backtrace line and source code.
|
304
|
+
def backtrace_snippets_chain(test)
|
305
|
+
code = test['exception']['snippet']
|
306
|
+
line = test['exception']['line']
|
307
|
+
|
308
|
+
chain = []
|
309
|
+
backtrace(test).each do |bt|
|
310
|
+
if md = /(.+?):(\d+)/.match(bt)
|
311
|
+
chain << [bt, code_snippet('file'=>md[1], 'line'=>md[2].to_i)]
|
312
|
+
else
|
313
|
+
chain << [bt, nil]
|
314
|
+
end
|
315
|
+
end
|
316
|
+
# use the tap-y/j snippet if the first file was not found
|
317
|
+
chain[0][1] = code_snippet('snippet'=>snippet, 'line'=>line) unless chain[0][1]
|
318
|
+
chain
|
319
|
+
end
|
320
|
+
|
321
|
+
# Parse a bactrace line into file and line number. Returns nil for both
|
322
|
+
# if parsing fails.
|
323
|
+
#
|
324
|
+
# @return [Array<String,Integer>] File and line number.
|
325
|
+
def parse_backtrace(bt)
|
326
|
+
if md = /(.+?):(\d+)/.match(bt)
|
327
|
+
return md[1], md[2].to_i
|
328
|
+
else
|
329
|
+
return nil, nil
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
225
333
|
# Returns a String of source code.
|
226
334
|
def code_snippet(entry)
|
227
335
|
file = entry['file']
|
@@ -264,6 +372,26 @@ module TapOut
|
|
264
372
|
end
|
265
373
|
end
|
266
374
|
|
375
|
+
format_snippet_array(s, line)
|
376
|
+
|
377
|
+
# len = s.map{ |(n,t)| n }.max.to_s.length
|
378
|
+
#
|
379
|
+
# # ensure proper alignment by zero-padding line numbers
|
380
|
+
# format = " %5s %0#{len}d %s"
|
381
|
+
#
|
382
|
+
# #s = s.map{|n,t|[n,t]}.sort{|a,b|a[0]<=>b[0]}
|
383
|
+
#
|
384
|
+
# pretty = s.map do |(n,t)|
|
385
|
+
# format % [('=>' if n == line), n, t.rstrip]
|
386
|
+
# end #.unshift "[#{region.inspect}] in #{source_file}"
|
387
|
+
#
|
388
|
+
# return pretty
|
389
|
+
end
|
390
|
+
|
391
|
+
#
|
392
|
+
def format_snippet_array(array, line)
|
393
|
+
s = array
|
394
|
+
|
267
395
|
len = s.map{ |(n,t)| n }.max.to_s.length
|
268
396
|
|
269
397
|
# ensure proper alignment by zero-padding line numbers
|
@@ -275,11 +403,13 @@ module TapOut
|
|
275
403
|
format % [('=>' if n == line), n, t.rstrip]
|
276
404
|
end #.unshift "[#{region.inspect}] in #{source_file}"
|
277
405
|
|
278
|
-
|
406
|
+
pretty.join("\n")
|
279
407
|
end
|
280
408
|
|
281
409
|
# Cache source file text. This is only used if the TAP-Y stream
|
282
410
|
# doesn not provide a snippet and the test file is locatable.
|
411
|
+
#
|
412
|
+
# @return [String] File contents.
|
283
413
|
def source(file)
|
284
414
|
@source[file] ||= (
|
285
415
|
File.readlines(file)
|
@@ -313,6 +443,68 @@ module TapOut
|
|
313
443
|
end
|
314
444
|
end
|
315
445
|
|
446
|
+
#
|
447
|
+
def captured_stdout(test)
|
448
|
+
stdout = test['stdout'].to_s.strip
|
449
|
+
return if stdout.empty?
|
450
|
+
if block_given?
|
451
|
+
yield(stdout)
|
452
|
+
else
|
453
|
+
stdout
|
454
|
+
end
|
455
|
+
end
|
456
|
+
|
457
|
+
#
|
458
|
+
def captured_stderr(test)
|
459
|
+
stderr = test['stderr'].to_s.strip
|
460
|
+
return if stderr.empty?
|
461
|
+
if block_given?
|
462
|
+
yield(stderr)
|
463
|
+
else
|
464
|
+
stderr
|
465
|
+
end
|
466
|
+
end
|
467
|
+
|
468
|
+
#
|
469
|
+
def captured_output(test)
|
470
|
+
str = ""
|
471
|
+
str += captured_stdout(test){ |c| "\nSTDOUT\n#{c.tabto(4)}\n" }.to_s
|
472
|
+
str += captured_stderr(test){ |c| "\nSTDERR\n#{c.tabto(4)}\n" }.to_s
|
473
|
+
str
|
474
|
+
end
|
475
|
+
|
476
|
+
#
|
477
|
+
def captured_output?(test)
|
478
|
+
captured_stdout?(test) || captured_stderr?(test)
|
479
|
+
end
|
480
|
+
|
481
|
+
#
|
482
|
+
def captured_stdout?(test)
|
483
|
+
stderr = test['stdout'].to_s.strip
|
484
|
+
!stderr.empty?
|
485
|
+
end
|
486
|
+
|
487
|
+
#
|
488
|
+
def captured_stderr?(test)
|
489
|
+
stderr = test['stderr'].to_s.strip
|
490
|
+
!stderr.empty?
|
491
|
+
end
|
492
|
+
|
493
|
+
#
|
494
|
+
def duration(seconds, precision=2)
|
495
|
+
p = precision.to_i
|
496
|
+
s = seconds.to_i
|
497
|
+
f = seconds - s
|
498
|
+
h, s = s.divmod(60)
|
499
|
+
m, s = s.divmod(60)
|
500
|
+
"%02d:%02d:%02d.%0#{p}d" % [h, m, s, f * 10**p]
|
501
|
+
end
|
502
|
+
|
503
|
+
# Access to configurtion.
|
504
|
+
def config
|
505
|
+
Tapout.config
|
506
|
+
end
|
507
|
+
|
316
508
|
end#class Abstract
|
317
509
|
|
318
510
|
end#module Reporters
|