test-unit-runner-tap 1.0.0 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile.lock ADDED
@@ -0,0 +1,16 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ test-unit-runner-tap (0.0.1)
5
+ test-unit
6
+
7
+ GEM
8
+ remote: http://rubygems.org/
9
+ specs:
10
+ test-unit (2.4.7)
11
+
12
+ PLATFORMS
13
+ ruby
14
+
15
+ DEPENDENCIES
16
+ test-unit-runner-tap!
data/README.rdoc CHANGED
@@ -18,8 +18,25 @@ TAP-Y/J format.
18
18
 
19
19
  == USAGE:
20
20
 
21
+ It you test helper script use:
22
+
21
23
  require 'test/unit/runner/tap'
22
24
 
25
+ Then you can select the runner via the `--runner` command line option.
26
+
27
+ $ ruby test/run_tests.rb --runner yaml
28
+
29
+ Available runners are `tap`, `tapy`/`yaml` or `tapj`/`json`.
30
+
31
+ The runner can also be specified in code, if need be. See API documentation
32
+ for more information on how to do this.
33
+
34
+ To use TAP-Y/J formats with TAPOUT, just pipe results to tapout utility.
35
+
36
+ $ ruby test/run_tests.rb --runner yaml | tapout
37
+
38
+ See TAPOUT poject for more information on that.
39
+
23
40
  == LICENSE:
24
41
 
25
42
  (LGPL v3.0 License)
@@ -2,7 +2,7 @@ module Test
2
2
  module Unit
3
3
  module Runner
4
4
  module Tap
5
- VERSION = "1.0.0"
5
+ VERSION = "1.1.1"
6
6
  end
7
7
  end
8
8
  end
@@ -1,11 +1,40 @@
1
- require 'test/unit'
1
+ require 'test-unit'
2
2
  require 'test/unit/runner/tap-version'
3
3
 
4
4
  module Test
5
5
  module Unit
6
6
  AutoRunner.register_runner(:tap) do |auto_runner|
7
- require 'test/unit/ui/tap/testrunner'
8
- Test::Unit::UI::Tap::TestRunner
7
+ require 'test/unit/ui/tap/perl_testrunner'
8
+ Test::Unit::UI::Tap::PerlTestRunner
9
+ end
10
+
11
+ AutoRunner.register_runner(:tapj) do |auto_runner|
12
+ require 'test/unit/ui/tap/json_testrunner'
13
+ Test::Unit::UI::Tap::JSONTestRunner
14
+ end
15
+
16
+ # alias for tap-j
17
+ AutoRunner.register_runner(:json) do |auto_runner|
18
+ require 'test/unit/ui/tap/json_testrunner'
19
+ Test::Unit::UI::Tap::JSONTestRunner
20
+ end
21
+
22
+ AutoRunner.register_runner(:tapy) do |auto_runner|
23
+ require 'test/unit/ui/tap/yaml_testrunner'
24
+ Test::Unit::UI::Tap::YAMLTestRunner
25
+ end
26
+
27
+ # alias for tap-y
28
+ AutoRunner.register_runner(:yaml) do |auto_runner|
29
+ require 'test/unit/ui/tap/yaml_testrunner'
30
+ Test::Unit::UI::Tap::YAMLTestRunner
31
+ end
32
+
33
+ # temporaryily available as fallback to orignal tap runner
34
+ # just in case the new runner exhibits any issues
35
+ AutoRunner.register_runner(:tap0) do |auto_runner|
36
+ require 'test/unit/ui/tap/perl_testrunner'
37
+ Test::Unit::UI::Tap::OldTestRunner
9
38
  end
10
39
  end
11
40
  end
@@ -0,0 +1,550 @@
1
+ require 'test/unit/ui/testrunner'
2
+ require 'test/unit/ui/testrunnermediator'
3
+ require 'stringio'
4
+
5
+ module Test
6
+ module Unit
7
+ module UI
8
+ module Tap
9
+
10
+ # Base class for all TAP runners.
11
+ #
12
+ class BaseTestRunner < Test::Unit::UI::TestRunner
13
+
14
+ # TAP-Y/J Revision
15
+ REVISION = 4
16
+
17
+ #
18
+ def initialize(suite, options={})
19
+ super
20
+
21
+ @output = @options[:output] || STDOUT
22
+
23
+ @level = 0
24
+
25
+ @_source_cache = {}
26
+ @already_outputted = false
27
+ @top_level = true
28
+
29
+ @counts = Hash.new{ |h,k| h[k] = 0 }
30
+ end
31
+
32
+ private
33
+
34
+ #
35
+ def setup_mediator
36
+ super
37
+
38
+ #suite_name = @suite.to_s # file name
39
+ #suite_name = @suite.name if @suite.kind_of?(Module)
40
+
41
+ #reset_output # TODO: Should we do this up front?
42
+ end
43
+
44
+ #
45
+ def attach_to_mediator
46
+ @mediator.add_listener(Test::Unit::TestResult::FAULT, &method(:tapout_fault))
47
+ @mediator.add_listener(Test::Unit::UI::TestRunnerMediator::STARTED, &method(:tapout_before_suite))
48
+ @mediator.add_listener(Test::Unit::UI::TestRunnerMediator::FINISHED, &method(:tapout_after_suite))
49
+ @mediator.add_listener(Test::Unit::TestCase::STARTED_OBJECT, &method(:tapout_before_test))
50
+ @mediator.add_listener(Test::Unit::TestCase::FINISHED_OBJECT, &method(:tapout_pass))
51
+ @mediator.add_listener(Test::Unit::TestSuite::STARTED_OBJECT, &method(:tapout_before_case))
52
+ @mediator.add_listener(Test::Unit::TestSuite::FINISHED_OBJECT, &method(:tapout_after_case))
53
+ end
54
+
55
+ #
56
+ # Before everything else.
57
+ #
58
+ def tapout_before_suite(result)
59
+ @result = result
60
+ @suite_start = Time.now
61
+
62
+ doc = {
63
+ 'type' => 'suite',
64
+ 'start' => @suite_start.strftime('%Y-%m-%d %H:%M:%S'),
65
+ 'count' => @suite.size,
66
+ #'seed' => #@suite.seed, # no seed?
67
+ 'rev' => REVISION
68
+ }
69
+ return doc
70
+ end
71
+
72
+ #
73
+ # After everything else.
74
+ #
75
+ def tapout_after_suite(elapsed_time)
76
+ doc = {
77
+ 'type' => 'final',
78
+ 'time' => elapsed_time, #Time.now - @suite_start,
79
+ 'counts' => {
80
+ 'total' => @counts[:total],
81
+ 'pass' => @counts[:pass], #self.test_count - self.failures - self.errors - self.skips,
82
+ 'fail' => @counts[:fail],
83
+ 'error' => @counts[:error],
84
+ 'omit' => @counts[:omit],
85
+ 'todo' => @counts[:todo],
86
+ } #,
87
+ #'assertions' => {
88
+ # 'total' => @result.assertion_count + @counts[:fail],
89
+ # 'pass' => @result.assertion_count,
90
+ # 'fail' => @counts[:fail]
91
+ #}
92
+ }
93
+ return doc
94
+ end
95
+
96
+ #
97
+ #
98
+ #
99
+ def tapout_before_case(testcase)
100
+ return nil if testcase.test_case.nil?
101
+
102
+ @test_case = testcase
103
+
104
+ doc = {
105
+ 'type' => 'case',
106
+ #'subtype' => '',
107
+ 'label' => testcase.name,
108
+ 'level' => @level
109
+ }
110
+
111
+ @level += 1
112
+
113
+ return doc
114
+ end
115
+
116
+ #
117
+ # After each case, decrement the case level.
118
+ #
119
+ def tapout_after_case(testcase)
120
+ @level -= 1
121
+ end
122
+
123
+ #
124
+ def tapout_before_test(test)
125
+ @test_start = Time.now
126
+ # set up stdout and stderr to be captured
127
+ reset_output
128
+ end
129
+
130
+ #
131
+ def tapout_fault(fault)
132
+ case fault
133
+ when Test::Unit::Pending
134
+ tapout_todo(fault)
135
+ when Test::Unit::Omission
136
+ tapout_omit(fault)
137
+ when Test::Unit::Notification
138
+ tapout_note(fault)
139
+ when Test::Unit::Failure
140
+ tapout_fail(fault)
141
+ else
142
+ tapout_error(fault)
143
+ end
144
+
145
+ @already_outputted = true #if fault.critical?
146
+ end
147
+
148
+ #
149
+ def tapout_note(note)
150
+ doc = {
151
+ 'type' => 'note',
152
+ 'text' => note.message
153
+ }
154
+ return doc
155
+ end
156
+
157
+ #
158
+ def tapout_pass(test)
159
+ if @already_outputted
160
+ @already_outputted = false
161
+ return nil
162
+ end
163
+
164
+ @counts[:total] += 1
165
+ @counts[:pass] += 1
166
+
167
+ doc = {
168
+ 'type' => 'test',
169
+ #'subtype' => '',
170
+ 'status' => 'pass',
171
+ #'setup': foo instance
172
+ 'label' => clean_label(test.name),
173
+ #'expected' => 2
174
+ #'returned' => 2
175
+ #'file' => test_file
176
+ #'line' => test_line
177
+ #'source' => source(test_file)[test_line-1].strip,
178
+ #'snippet' => code_snippet(test_file, test_line),
179
+ 'time' => Time.now - @suite_start
180
+ }
181
+
182
+ doc.update(captured_output)
183
+
184
+ return doc
185
+ end
186
+
187
+ #
188
+ def tapout_todo(fault)
189
+ @counts[:total] += 1
190
+ @counts[:todo] += 1
191
+
192
+ file, line = location(fault.location)
193
+ rel_file = file.sub(Dir.pwd+'/', '')
194
+
195
+ doc = {
196
+ 'type' => 'test',
197
+ #'subtype' => '',
198
+ 'status' => 'todo',
199
+ 'label' => clean_label(fault.test_name),
200
+ #'setup' => "foo instance",
201
+ #'expected' => 2,
202
+ #'returned' => 1,
203
+ #'file' => test_file
204
+ #'line' => test_line
205
+ #'source' => source(test_file)[test_line-1].strip,
206
+ #'snippet' => code_snippet(test_file, test_line),
207
+ 'exception' => {
208
+ 'message' => clean_message(fault.message),
209
+ 'class' => fault.class.name,
210
+ 'file' => rel_file,
211
+ 'line' => line,
212
+ 'source' => source(file)[line-1].strip,
213
+ 'snippet' => code_snippet(file, line),
214
+ 'backtrace' => filter_backtrace(fault.location)
215
+ },
216
+ 'time' => Time.now - @suite_start
217
+ }
218
+
219
+ doc.update(captured_output)
220
+
221
+ return doc
222
+ end
223
+
224
+ #
225
+ def tapout_omit(fault)
226
+ @counts[:total] += 1
227
+ @counts[:omit] += 1
228
+
229
+ file, line = location(fault.location)
230
+ rel_file = file.sub(Dir.pwd+'/', '')
231
+
232
+ doc = {
233
+ 'type' => 'test',
234
+ #'subtype' => '',
235
+ 'status' => 'skip',
236
+ 'label' => clean_label(fault.test_name),
237
+ #'setup' => "foo instance",
238
+ #'expected' => 2,
239
+ #'returned' => 1,
240
+ #'file' => test_file
241
+ #'line' => test_line
242
+ #'source' => source(test_file)[test_line-1].strip,
243
+ #'snippet' => code_snippet(test_file, test_line),
244
+ 'exception' => {
245
+ 'message' => clean_message(fault.message),
246
+ 'class' => fault.class.name,
247
+ 'file' => rel_file,
248
+ 'line' => line,
249
+ 'source' => source(file)[line-1].strip,
250
+ 'snippet' => code_snippet(file, line),
251
+ 'backtrace' => filter_backtrace(fault.location)
252
+ },
253
+ 'time' => Time.now - @suite_start
254
+ }
255
+
256
+ doc.update(captured_output)
257
+
258
+ return doc
259
+ end
260
+
261
+ #
262
+ def tapout_fail(fault)
263
+ @counts[:total] += 1
264
+ @counts[:fail] += 1
265
+
266
+ file, line = location(fault.location)
267
+ rel_file = file.sub(Dir.pwd+'/', '')
268
+
269
+ doc = {
270
+ 'type' => 'test',
271
+ #'subtype' => '',
272
+ 'status' => 'fail',
273
+ 'label' => clean_label(fault.test_name),
274
+ #'setup' => "foo instance",
275
+ 'expected' => fault.inspected_expected,
276
+ 'returned' => fault.inspected_actual,
277
+ #'file' => test_file
278
+ #'line' => test_line
279
+ #'source' => ok 1, 2
280
+ #'snippet' =>
281
+ # - 44: ok 0,0
282
+ # - 45: ok 1,2
283
+ # - 46: ok 2,4
284
+ 'exception' => {
285
+ 'message' => clean_message(fault.user_message || fault.message),
286
+ 'class' => fault.class.name,
287
+ 'file' => rel_file,
288
+ 'line' => line,
289
+ 'source' => source(file)[line-1].strip,
290
+ 'snippet' => code_snippet(file, line),
291
+ 'backtrace' => filter_backtrace(fault.location)
292
+ },
293
+ 'time' => Time.now - @suite_start
294
+ }
295
+
296
+ doc.update(captured_output)
297
+
298
+ return doc
299
+ end
300
+
301
+ #
302
+ def tapout_error(fault)
303
+ @counts[:total] += 1
304
+ @counts[:error] += 1
305
+
306
+ file, line = location(fault.location)
307
+ rel_file = file.sub(Dir.pwd+'/', '')
308
+
309
+ doc = {
310
+ 'type' => 'test',
311
+ #'subtype' => '',
312
+ 'status' => 'error',
313
+ 'label' => clean_label(fault.test_name),
314
+ #'setup' => "foo instance",
315
+ #'expected' => fault.inspected_expected,
316
+ #'returned' => fault.inspected_actual,
317
+ #'file' => test_file
318
+ #'line' => test_line
319
+ #'source' => ok 1, 2
320
+ #'snippet' =>
321
+ # - 44: ok 0,0
322
+ # - 45: ok 1,2
323
+ # - 46: ok 2,4
324
+ 'exception' => {
325
+ 'message' => clean_message(fault.message),
326
+ 'class' => fault.class.name,
327
+ 'file' => rel_file,
328
+ 'line' => line,
329
+ 'source' => source(file)[line-1].strip,
330
+ 'snippet' => code_snippet(file, line),
331
+ 'backtrace' => filter_backtrace(fault.location)
332
+ },
333
+ 'time' => Time.now - @suite_start
334
+ }
335
+
336
+ doc.update(captured_output)
337
+
338
+ return doc
339
+ end
340
+
341
+ #
342
+ def clean_label(name)
343
+ name.sub(/\(.+?\)\z/, '').chomp('()')
344
+ end
345
+
346
+ # Clean the backtrace of any reference to test framework itself.
347
+ def filter_backtrace(backtrace)
348
+ trace = backtrace
349
+
350
+ ## remove backtraces that match any pattern in $RUBY_IGNORE_CALLERS
351
+ #trace = race.reject{|b| $RUBY_IGNORE_CALLERS.any?{|i| i=~b}}
352
+
353
+ ## remove `:in ...` portion of backtraces
354
+ trace = trace.map do |bt|
355
+ i = bt.index(':in')
356
+ i ? bt[0...i] : bt
357
+ end
358
+
359
+ # TODO: does TestUnit have a filter ?
360
+ ## now apply MiniTest's own filter (note: doesn't work if done first, why?)
361
+ #trace = MiniTest::filter_backtrace(trace)
362
+
363
+ ## if the backtrace is empty now then revert to the original
364
+ trace = backtrace if trace.empty?
365
+
366
+ ## simplify paths to be relative to current workding diectory
367
+ trace = trace.map{ |bt| bt.sub(Dir.pwd+File::SEPARATOR,'') }
368
+
369
+ return trace
370
+ end
371
+
372
+ # Returns a Hash of source code.
373
+ def code_snippet(file, line)
374
+ s = []
375
+ if File.file?(file)
376
+ source = source(file)
377
+ radius = 2 # TODO: make customizable (number of surrounding lines to show)
378
+ region = [line - radius, 1].max ..
379
+ [line + radius, source.length].min
380
+
381
+ s = region.map do |n|
382
+ {n => source[n-1].chomp}
383
+ end
384
+ end
385
+ return s
386
+ end
387
+
388
+ # Return nicely formated String of code lines.
389
+ def code_snippet_string(file, line)
390
+ str = []
391
+ snp = code_snippet_array(file, line)
392
+ max = snp.map{ |n, c| n.to_s.size }.max
393
+ snp.each do |n, c|
394
+ if n == line
395
+ str << "=> %#{max}d %s" % [n, c]
396
+ else
397
+ str << " %#{max}d %s" % [n, c]
398
+ end
399
+ end
400
+ str.join("\n")
401
+ end
402
+
403
+ # Return Array of source code line numbers and text.
404
+ def code_snippet_array(file, line)
405
+ snp = []
406
+ if File.file?(file)
407
+ source = source(file)
408
+ radius = 2 # TODO: make customizable (number of surrounding lines to show)
409
+ region = [line - radius, 1].max ..
410
+ [line + radius, source.length].min
411
+ snp = region.map do |n|
412
+ [n, source[n-1].chomp]
413
+ end
414
+ end
415
+ return snp
416
+ end
417
+
418
+ # Cache source file text. This is only used if the TAP-Y stream
419
+ # doesn not provide a snippet and the test file is locatable.
420
+ def source(file)
421
+ @_source_cache[file] ||= (
422
+ File.readlines(file)
423
+ )
424
+ end
425
+
426
+ # Parse source location from caller, caller[0] or an Exception object.
427
+ def parse_source_location(caller)
428
+ case caller
429
+ when Exception
430
+ trace = caller.backtrace.reject{ |bt| bt =~ INTERNALS }
431
+ caller = trace.first
432
+ when Array
433
+ caller = caller.first
434
+ end
435
+ caller =~ /(.+?):(\d+(?=:|\z))/ or return ""
436
+ source_file, source_line = $1, $2.to_i
437
+ return source_file, source_line
438
+ end
439
+
440
+ # Get location of exception.
441
+ def location(backtrace)
442
+ last_before_assertion = ""
443
+ backtrace.reverse_each do |s|
444
+ break if s =~ /in .(assert|refute|flunk|pass|fail|raise|must|wont)/
445
+ last_before_assertion = s
446
+ end
447
+ file, line = last_before_assertion.sub(/:in .*$/, '').split(':')
448
+ line = line.to_i if line
449
+ return file, line
450
+ end
451
+
452
+ #
453
+ def clean_message(message)
454
+ message.strip.gsub(/\n+/, "\n")
455
+ end
456
+
457
+ #
458
+ def puts(string='')
459
+ @output.write(string.chomp+"\n")
460
+ @output.flush
461
+ end
462
+
463
+ #
464
+ def reset_output
465
+ @_oldout = $stdout
466
+ @_olderr = $stderr
467
+
468
+ @_newout = StringIO.new
469
+ @_newerr = StringIO.new
470
+
471
+ $stdout = @_newout
472
+ $stderr = @_newerr
473
+ end
474
+
475
+ #
476
+ def captured_output
477
+ stdout = @_newout.string.chomp("\n")
478
+ stderr = @_newerr.string.chomp("\n")
479
+
480
+ doc = {}
481
+ doc['stdout'] = stdout unless stdout.empty?
482
+ doc['stderr'] = stderr unless stderr.empty?
483
+
484
+ $stdout = @_oldout
485
+ $stderr = @_olderr
486
+
487
+ return doc
488
+ end
489
+
490
+ end
491
+
492
+ end #module Tap
493
+ end #module UI
494
+ end #module Unit
495
+ end #module Test
496
+
497
+
498
+
499
+
500
+ =begin
501
+ # TEMPORARILY LEAVING THIS FOR REFERENCE. What's this about encoding?
502
+
503
+ def output_fault_message(fault)
504
+ if fault.expected.respond_to?(:encoding) and
505
+ fault.actual.respond_to?(:encoding) and
506
+ fault.expected.encoding != fault.actual.encoding
507
+ need_encoding = true
508
+ else
509
+ need_encoding = false
510
+ end
511
+ output(fault.user_message) if fault.user_message
512
+ output_single("<")
513
+ output_single(fault.inspected_expected, color("pass"))
514
+ output_single(">")
515
+ if need_encoding
516
+ output_single("(")
517
+ output_single(fault.expected.encoding.name, color("pass"))
518
+ output_single(")")
519
+ end
520
+ output(" expected but was")
521
+ output_single("<")
522
+ output_single(fault.inspected_actual, color("failure"))
523
+ output_single(">")
524
+ if need_encoding
525
+ output_single("(")
526
+ output_single(fault.actual.encoding.name, color("failure"))
527
+ output_single(")")
528
+ end
529
+ output("")
530
+
531
+ from, to = prepare_for_diff(fault.expected, fault.actual)
532
+ if from and to
533
+ from_lines = from.split(/\r?\n/)
534
+ to_lines = to.split(/\r?\n/)
535
+ if need_encoding
536
+ from_lines << ""
537
+ to_lines << ""
538
+ from_lines << "Encoding: #{fault.expected.encoding.name}"
539
+ to_lines << "Encoding: #{fault.actual.encoding.name}"
540
+ end
541
+ differ = ColorizedReadableDiffer.new(from_lines, to_lines, self)
542
+ if differ.need_diff?
543
+ output("")
544
+ output("diff:")
545
+ differ.diff
546
+ end
547
+ end
548
+ end
549
+ =end
550
+