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.
Files changed (37) hide show
  1. data/.ruby +12 -3
  2. data/.yardopts +6 -5
  3. data/HISTORY.rdoc +16 -0
  4. data/README.rdoc +18 -16
  5. data/TAP-YJ.md +15 -15
  6. data/bin/tapout +1 -1
  7. data/demo/applique/ae.rb +1 -0
  8. data/{spec → demo}/applique/env.rb +0 -0
  9. data/{spec → demo}/issues/applique/env.rb +0 -0
  10. data/{spec → demo}/issues/default_reporter.rdoc +2 -2
  11. data/{spec → demo}/perl_adapter.rdoc +3 -3
  12. data/{spec → demo}/reporters/applique/cli.rb +1 -1
  13. data/{spec → demo}/reporters/fixtures/tapy.yml +0 -0
  14. data/{spec → demo}/reporters/reporters.rdoc +0 -0
  15. data/lib/tapout.rb +5 -101
  16. data/lib/tapout.yml +12 -3
  17. data/lib/tapout/adapters/perl.rb +1 -1
  18. data/lib/tapout/cli.rb +98 -0
  19. data/lib/tapout/config.rb +103 -0
  20. data/lib/tapout/parsers/json.rb +2 -2
  21. data/lib/tapout/parsers/perl.rb +1 -1
  22. data/lib/tapout/parsers/yaml.rb +3 -2
  23. data/lib/tapout/reporters.rb +2 -1
  24. data/lib/tapout/reporters/abstract.rb +223 -31
  25. data/lib/tapout/reporters/breakdown_reporter.rb +49 -57
  26. data/lib/tapout/reporters/dot_reporter.rb +52 -33
  27. data/lib/tapout/reporters/html_reporter.rb +3 -1
  28. data/lib/tapout/reporters/markdown_reporter.rb +101 -0
  29. data/lib/tapout/reporters/outline_reporter.rb +68 -25
  30. data/lib/tapout/reporters/pretty_reporter.rb +65 -86
  31. data/lib/tapout/reporters/progress_reporter.rb +47 -22
  32. data/lib/tapout/reporters/runtime_reporter.rb +223 -0
  33. data/lib/tapout/reporters/tap_reporter.rb +1 -1
  34. data/lib/tapout/reporters/turn_reporter.rb +26 -65
  35. data/lib/tapout/version.rb +2 -2
  36. metadata +34 -19
  37. 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
@@ -1,10 +1,10 @@
1
1
  require 'tapout/reporters'
2
2
  require 'json'
3
3
 
4
- module TapOut
4
+ module Tapout
5
5
 
6
6
  # The TAP-J Parser takes a TAP-J stream and routes it through
7
- # a TapOut report format.
7
+ # a Tapout report format.
8
8
  #
9
9
  class JsonParser
10
10
 
@@ -2,7 +2,7 @@ require 'tapout/version'
2
2
  require 'tapout/adapters/perl'
3
3
  require 'tapout/reporters'
4
4
 
5
- module TapOut
5
+ module Tapout
6
6
 
7
7
  # The TAPLegacy Parser takes a traditional TAP stream and routes
8
8
  # it through a Tap Out report format.
@@ -1,10 +1,11 @@
1
1
  require 'tapout/reporters'
2
2
  require 'yaml'
3
3
 
4
- module TapOut
4
+ module Tapout
5
5
 
6
6
  # The TAP-Y Parser takes a TAP-Y stream and routes it through
7
- # a TapOut report format.
7
+ # a Tapout report format.
8
+ #
8
9
  class YamlParser
9
10
 
10
11
  #
@@ -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 TapOut
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 skip(entry)
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
- skip(entry)
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
- total = @passed.size + @failed.size + @raised.size #+ @skipped.size + @omitted.size
206
+ sums = count_tally(entry)
168
207
 
169
- if entry['counts']
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
- if tally = entry['counts']
179
- sums = %w{pass fail error todo omit}.map{ |e| tally[e] || 0 }
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
- sums = [@passed, @failed, @raised, @skipped, @omitted].map{ |e| e.size }
215
+ assertions = nil
216
+ failures = nil
182
217
  end
183
218
 
184
- # ???
185
- assertions = entry['assertions']
186
- failures = entry['failures']
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 = "%s tests: %s pass, %s fail, %s err, %s todo, %omit (%s/%s assertions)"
190
- text = text % [total, *sums] + [assertions - failures, assertions]
228
+ text << " (%s/%s assertions)"
229
+ text = text % (sums + [assertions - failures, assertions])
191
230
  else
192
- text = "%s tests: %s pass, %s fail, %s err, %s todo, %s omit"
193
- text = text % [total, *sums]
231
+ text = text % sums
194
232
  end
195
233
 
196
- if count_fail > 0
197
- text.ansi(:red)
198
- elsif count_error > 0
199
- text.ansi(:yellow)
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
- text.ansi(:green)
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 to ko/ paths and code.
261
+ # Clean the backtrace of any "boring" reference.
211
262
  def clean_backtrace(backtrace)
212
- trace = backtrace.reject{ |bt| bt =~ INTERNALS }
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
- return pretty
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