tapout 0.3.2 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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