test-unit-ext 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/NEWS.en +7 -0
- data/NEWS.ja +7 -0
- data/README.en +51 -0
- data/README.ja +51 -0
- data/Rakefile +106 -0
- data/html/index.html +21 -0
- data/html/news.html +18 -0
- data/html/news.html.en +18 -0
- data/html/news.html.ja +18 -0
- data/html/readme.html +42 -0
- data/html/readme.html.en +42 -0
- data/html/readme.html.ja +42 -0
- data/lib/test-unit-ext.rb +11 -0
- data/lib/test-unit-ext/always-show-result.rb +28 -0
- data/lib/test-unit-ext/backtrace-filter.rb +17 -0
- data/lib/test-unit-ext/color.rb +59 -0
- data/lib/test-unit-ext/colorized-runner.rb +101 -0
- data/lib/test-unit-ext/diff.rb +187 -0
- data/lib/test-unit-ext/long-display-for-emacs.rb +25 -0
- data/lib/test-unit-ext/metadata.rb +111 -0
- data/lib/test-unit-ext/priority.rb +186 -0
- data/lib/test-unit-ext/version.rb +3 -0
- data/misc/rd2html.rb +42 -0
- data/test/run-test.rb +14 -0
- data/test/test_color.rb +39 -0
- data/test/test_diff.rb +109 -0
- data/test/test_metadata.rb +127 -0
- metadata +83 -0
@@ -0,0 +1,28 @@
|
|
1
|
+
require "test/unit/ui/testrunnermediator"
|
2
|
+
|
3
|
+
module Test
|
4
|
+
module Unit
|
5
|
+
module UI
|
6
|
+
class TestRunnerMediator
|
7
|
+
alias_method :original_run_suite, :run_suite
|
8
|
+
def run_suite
|
9
|
+
@notified_finished = false
|
10
|
+
begin_time = Time.now
|
11
|
+
original_run_suite
|
12
|
+
rescue Interrupt
|
13
|
+
unless @notified_finished
|
14
|
+
end_time = Time.now
|
15
|
+
elapsed_time = end_time - begin_time
|
16
|
+
notify_listeners(FINISHED, elapsed_time)
|
17
|
+
end
|
18
|
+
raise
|
19
|
+
end
|
20
|
+
|
21
|
+
def notify_listeners(channel_name, *arguments)
|
22
|
+
@notified_finished = true if channel_name == FINISHED
|
23
|
+
super
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'test/unit/util/backtracefilter'
|
2
|
+
|
3
|
+
module Test
|
4
|
+
module Unit
|
5
|
+
module Util
|
6
|
+
module BacktraceFilter
|
7
|
+
TEST_UNIT_EXT_PREFIX = File.dirname(__FILE__)
|
8
|
+
|
9
|
+
alias_method :original_filter_backtrace, :filter_backtrace
|
10
|
+
def filter_backtrace(backtrace, prefix=nil)
|
11
|
+
original_result = original_filter_backtrace(backtrace, prefix)
|
12
|
+
original_filter_backtrace(original_result, TEST_UNIT_EXT_PREFIX)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Test
|
2
|
+
class Color
|
3
|
+
NAMES = ["black", "red", "green", "yellow",
|
4
|
+
"blue", "magenta", "cyan", "white"]
|
5
|
+
def initialize(name, options={})
|
6
|
+
@name = name
|
7
|
+
@foreground = options[:foreground]
|
8
|
+
@foreground = true if @foreground.nil?
|
9
|
+
@intensity = options[:intensity]
|
10
|
+
@bold = options[:bold]
|
11
|
+
@italic = options[:italic]
|
12
|
+
@underline = options[:underline]
|
13
|
+
end
|
14
|
+
|
15
|
+
def sequence
|
16
|
+
sequence = []
|
17
|
+
if @name == "none"
|
18
|
+
elsif @name == "reset"
|
19
|
+
sequence << "0"
|
20
|
+
else
|
21
|
+
foreground_parameter = @foreground ? 3 : 4
|
22
|
+
foreground_parameter += 6 if @intensity
|
23
|
+
sequence << "#{foreground_parameter}#{NAMES.index(@name)}"
|
24
|
+
end
|
25
|
+
sequence << "1" if @bold
|
26
|
+
sequence << "3" if @italic
|
27
|
+
sequence << "4" if @underline
|
28
|
+
sequence
|
29
|
+
end
|
30
|
+
|
31
|
+
def escape_sequence
|
32
|
+
"\e[#{sequence.join(';')}m"
|
33
|
+
end
|
34
|
+
|
35
|
+
def +(other)
|
36
|
+
MixColor.new([self, other])
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class MixColor
|
41
|
+
def initialize(colors)
|
42
|
+
@colors = colors
|
43
|
+
end
|
44
|
+
|
45
|
+
def sequence
|
46
|
+
@colors.inject([]) do |result, color|
|
47
|
+
result + color.sequence
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def escape_sequence
|
52
|
+
"\e[#{sequence.join(';')}m"
|
53
|
+
end
|
54
|
+
|
55
|
+
def +(other)
|
56
|
+
self.class.new([self, other])
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require "test/unit/ui/console/testrunner"
|
2
|
+
|
3
|
+
module Test
|
4
|
+
module Unit
|
5
|
+
module UI
|
6
|
+
module Console
|
7
|
+
class ColorizedTestRunner < TestRunner
|
8
|
+
extend TestRunnerUtilities
|
9
|
+
|
10
|
+
SCHEMES = {
|
11
|
+
:default => {
|
12
|
+
"success" => Color.new("green", :bold => true),
|
13
|
+
"failure" => Color.new("red", :bold => true),
|
14
|
+
"error" => Color.new("yellow", :bold => true),
|
15
|
+
},
|
16
|
+
}
|
17
|
+
|
18
|
+
def initialize(suite, output_level=NORMAL, io=STDOUT)
|
19
|
+
super
|
20
|
+
@use_color = guess_color_availability
|
21
|
+
@color_scheme = SCHEMES[:default]
|
22
|
+
@reset_color = Color.new("reset")
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
def add_fault(fault)
|
27
|
+
@faults << fault
|
28
|
+
output_single_with_color(fault.single_character_display,
|
29
|
+
fault_color(fault),
|
30
|
+
PROGRESS_ONLY)
|
31
|
+
@already_outputted = true
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_finished(name)
|
35
|
+
unless @already_outputted
|
36
|
+
output_single_with_color(".",
|
37
|
+
@color_scheme["success"],
|
38
|
+
PROGRESS_ONLY)
|
39
|
+
end
|
40
|
+
nl(VERBOSE)
|
41
|
+
@already_outputted = false
|
42
|
+
end
|
43
|
+
|
44
|
+
def finished(elapsed_time)
|
45
|
+
nl
|
46
|
+
output("Finished in #{elapsed_time} seconds.")
|
47
|
+
@faults.each_with_index do |fault, index|
|
48
|
+
nl
|
49
|
+
output_single("%3d) " % (index + 1))
|
50
|
+
output_with_color(fault.long_display, fault_color(fault))
|
51
|
+
end
|
52
|
+
nl
|
53
|
+
output_with_color(@result.to_s, result_color)
|
54
|
+
end
|
55
|
+
|
56
|
+
def fault_color(fault)
|
57
|
+
@color_scheme[fault.class.name.split(/::/).last.downcase]
|
58
|
+
end
|
59
|
+
|
60
|
+
def result_color
|
61
|
+
if @result.passed?
|
62
|
+
@color_scheme["success"]
|
63
|
+
elsif @result.error_count > 0
|
64
|
+
@color_scheme["error"]
|
65
|
+
elsif @result.failure_count > 0
|
66
|
+
@color_scheme["failure"]
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def output_with_color(message, color=nil, level=NORMAL)
|
71
|
+
return unless output?(level)
|
72
|
+
output_single_with_color(message, color, level)
|
73
|
+
@io.puts
|
74
|
+
end
|
75
|
+
|
76
|
+
def output_single_with_color(message, color=nil, level=NORMAL)
|
77
|
+
return unless output?(level)
|
78
|
+
if @use_color and color
|
79
|
+
message = "%s%s%s" % [color.escape_sequence,
|
80
|
+
message,
|
81
|
+
@reset_color.escape_sequence]
|
82
|
+
end
|
83
|
+
@io.write(message)
|
84
|
+
@io.flush
|
85
|
+
end
|
86
|
+
|
87
|
+
def guess_color_availability
|
88
|
+
term = ENV["TERM"]
|
89
|
+
return true if term and (/term\z/ =~ term or term == "screen")
|
90
|
+
return true if ENV["EMACS"] == "t"
|
91
|
+
false
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
AutoRunner::RUNNERS[:console] = Proc.new do
|
98
|
+
Test::Unit::UI::Console::ColorizedTestRunner
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,187 @@
|
|
1
|
+
# port of ndiff in Python's difflib.
|
2
|
+
|
3
|
+
module Test
|
4
|
+
module Diff
|
5
|
+
class SequenceMatcher
|
6
|
+
def initialize(from, to)
|
7
|
+
@from = from
|
8
|
+
@to = to
|
9
|
+
update_to_indexes
|
10
|
+
end
|
11
|
+
|
12
|
+
def longest_match(from_start, from_end, to_start, to_end)
|
13
|
+
best_from, best_to, best_size = from_start, to_start, 0
|
14
|
+
lengths = {}
|
15
|
+
from_start.upto(from_end) do |i|
|
16
|
+
new_lengths = {}
|
17
|
+
(@to_indexes[@from[i]] || []).each do |j|
|
18
|
+
next if j < to_start
|
19
|
+
break if j >= to_end
|
20
|
+
k = new_lengths[j] = (lengths[j - 1] || 0) + 1
|
21
|
+
if k > best_size
|
22
|
+
best_from, best_to, best_size = i - k + 1, j - k + 1, k
|
23
|
+
end
|
24
|
+
end
|
25
|
+
lengths = new_lengths
|
26
|
+
end
|
27
|
+
|
28
|
+
while best_from > from_start and best_to > to_start and
|
29
|
+
@from[best_from - 1] == @to[best_to - 1]
|
30
|
+
best_from -= 1
|
31
|
+
best_to -= 1
|
32
|
+
best_size += 1
|
33
|
+
end
|
34
|
+
|
35
|
+
while best_from + best_size <= from_end and
|
36
|
+
best_to + best_size <= to_end and
|
37
|
+
@from[best_from + best_size] == @to[best_to + best_size]
|
38
|
+
best_size += 1
|
39
|
+
end
|
40
|
+
|
41
|
+
[best_from, best_to, best_size]
|
42
|
+
end
|
43
|
+
|
44
|
+
def matching_blocks
|
45
|
+
queue = [[0, @from.size - 1, 0, @to.size - 1]]
|
46
|
+
blocks = []
|
47
|
+
until queue.empty?
|
48
|
+
from_start, from_end, to_start, to_end = queue.pop
|
49
|
+
match_info = longest_match(from_start, from_end, to_start, to_end)
|
50
|
+
match_from_index, match_to_index, size = match_info
|
51
|
+
unless size.zero?
|
52
|
+
blocks << match_info
|
53
|
+
if from_start < match_from_index and to_start < match_to_index
|
54
|
+
queue.push([from_start, match_from_index,
|
55
|
+
to_start, match_to_index])
|
56
|
+
end
|
57
|
+
if match_from_index + size < from_end and
|
58
|
+
match_to_index + size < to_end
|
59
|
+
queue.push([match_from_index + size, from_end,
|
60
|
+
match_to_index + size, to_end])
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
non_adjacent = []
|
66
|
+
prev_from_index = prev_to_index = prev_size = 0
|
67
|
+
blocks.sort.each do |from_index, to_index, size|
|
68
|
+
if prev_from_index + prev_size == from_index and
|
69
|
+
prev_to_index + prev_size == to_index
|
70
|
+
prev_size += size
|
71
|
+
else
|
72
|
+
unless prev_size.zero?
|
73
|
+
non_adjacent << [prev_from_index, prev_to_index, prev_size]
|
74
|
+
end
|
75
|
+
prev_from_index, prev_to_index, prev_size =
|
76
|
+
from_index, to_index, size
|
77
|
+
end
|
78
|
+
end
|
79
|
+
unless prev_size.zero?
|
80
|
+
non_adjacent << [prev_from_index, prev_to_index, prev_size]
|
81
|
+
end
|
82
|
+
|
83
|
+
non_adjacent << [@from.size, @to.size, 0]
|
84
|
+
non_adjacent
|
85
|
+
end
|
86
|
+
|
87
|
+
def operations
|
88
|
+
from_index = to_index = 0
|
89
|
+
operations = []
|
90
|
+
matching_blocks.each do |match_from_index, match_to_index, size|
|
91
|
+
tag = nil
|
92
|
+
if from_index < match_from_index and to_index < match_to_index
|
93
|
+
tag = :replace
|
94
|
+
elsif from_index < match_from_index
|
95
|
+
tag = :delete
|
96
|
+
elsif to_index < match_to_index
|
97
|
+
tag = :insert
|
98
|
+
end
|
99
|
+
|
100
|
+
if tag
|
101
|
+
operations << [tag,
|
102
|
+
from_index, match_from_index,
|
103
|
+
to_index, match_to_index]
|
104
|
+
end
|
105
|
+
|
106
|
+
from_index, to_index = match_from_index + size, match_to_index + size
|
107
|
+
if size > 0
|
108
|
+
operations << [:equal,
|
109
|
+
match_from_index, from_index,
|
110
|
+
match_to_index, to_index]
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
operations
|
115
|
+
end
|
116
|
+
|
117
|
+
private
|
118
|
+
def update_to_indexes
|
119
|
+
@to_indexes = {}
|
120
|
+
@to.each_with_index do |line, i|
|
121
|
+
@to_indexes[line] ||= []
|
122
|
+
@to_indexes[line] << i
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
class Differ
|
128
|
+
def initialize(from, to)
|
129
|
+
@from = from
|
130
|
+
@to = to
|
131
|
+
end
|
132
|
+
|
133
|
+
def compare
|
134
|
+
result = []
|
135
|
+
matcher = SequenceMatcher.new(@from, @to)
|
136
|
+
matcher.operations.each do |args|
|
137
|
+
tag, from_start, from_end, to_start, to_end = args
|
138
|
+
case tag
|
139
|
+
when :replace
|
140
|
+
result.concat(fancy_replace(from_start, from_end, to_start, to_end))
|
141
|
+
when :delete
|
142
|
+
result.concat(tagging('-', @from[from_start..from_end]))
|
143
|
+
when :insert
|
144
|
+
result.concat(tagging('+', @to[to_start..to_end]))
|
145
|
+
when :equal
|
146
|
+
result.concat(tagging(' ', @from[from_start..from_end]))
|
147
|
+
else
|
148
|
+
raise "unknown tag: #{tag}"
|
149
|
+
end
|
150
|
+
end
|
151
|
+
result
|
152
|
+
end
|
153
|
+
|
154
|
+
private
|
155
|
+
def tagging(tag, contents)
|
156
|
+
contents.collect {|content| "#{tag} #{content}"}
|
157
|
+
end
|
158
|
+
|
159
|
+
def format_diff_point(from_line, to_line, from_tags, to_tags)
|
160
|
+
common = [n_leading_characters(from_line, ?\t),
|
161
|
+
n_leading_characters(to_line, ?\t)].min
|
162
|
+
common = [common, n_leading_characters(from_tags[0, common], " "[0])].min
|
163
|
+
from_tags = from_tags[common..-1].rstrip
|
164
|
+
to_tags = to_tags[common..-1].rstrip
|
165
|
+
|
166
|
+
result = ["- #{from_line}"]
|
167
|
+
result << "? #{"\t" * common}#{from_tags}" unless from_tags.empty?
|
168
|
+
result << "+ #{to_line}"
|
169
|
+
result << "? #{"\t" * common}#{to_tags}" unless to_tags.empty?
|
170
|
+
result
|
171
|
+
end
|
172
|
+
|
173
|
+
def n_leading_characters(string, character)
|
174
|
+
n = 0
|
175
|
+
while string[n] == character
|
176
|
+
n += 1
|
177
|
+
end
|
178
|
+
n
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
module_function
|
183
|
+
def ndiff(from, to)
|
184
|
+
Differ.new(from, to).compare.join("\n")
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'test/unit/failure'
|
2
|
+
require 'test/unit/error'
|
3
|
+
|
4
|
+
module Test
|
5
|
+
module Unit
|
6
|
+
BACKTRACE_INFO_RE = /.+:\d+:in `.+?'/
|
7
|
+
class Failure
|
8
|
+
alias_method :original_long_display, :long_display
|
9
|
+
def long_display
|
10
|
+
extract_backtraces_re =
|
11
|
+
/^ \[(#{BACKTRACE_INFO_RE}(?:\n #{BACKTRACE_INFO_RE})+)\]:$/
|
12
|
+
original_long_display.gsub(extract_backtraces_re) do |backtraces|
|
13
|
+
$1.gsub(/^ (#{BACKTRACE_INFO_RE})/, '\1') + ':'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class Error
|
19
|
+
alias_method :original_long_display, :long_display
|
20
|
+
def long_display
|
21
|
+
original_long_display.gsub(/^ (#{BACKTRACE_INFO_RE})/, '\1')
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require "test/unit"
|
2
|
+
|
3
|
+
module Test
|
4
|
+
module Unit
|
5
|
+
class TestCase
|
6
|
+
class << self
|
7
|
+
alias_method :method_added_without_metadata, :method_added
|
8
|
+
def method_added(name)
|
9
|
+
method_added_without_metadata(name)
|
10
|
+
if defined?(@current_metadata)
|
11
|
+
set_metadata(name, @current_metadata)
|
12
|
+
@current_metadata = {}
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def metadata(name, value, *tests)
|
17
|
+
@current_metadata ||= {}
|
18
|
+
if tests.empty?
|
19
|
+
@current_metadata[name] = value
|
20
|
+
else
|
21
|
+
tests.each do |test|
|
22
|
+
set_metadata(test, {name => value})
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def bug(value, *tests)
|
28
|
+
metadata(:bug, value, *tests)
|
29
|
+
end
|
30
|
+
|
31
|
+
def set_metadata(test_name, metadata)
|
32
|
+
return if metadata.empty?
|
33
|
+
test_name = normalize_test_name(test_name)
|
34
|
+
@metadata ||= {}
|
35
|
+
@metadata[test_name] ||= {}
|
36
|
+
@metadata[test_name] = @metadata[test_name].merge(metadata)
|
37
|
+
end
|
38
|
+
|
39
|
+
def get_metadata(test_name)
|
40
|
+
test_name = normalize_test_name(test_name)
|
41
|
+
@metadata ||= {}
|
42
|
+
@metadata[test_name]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
alias_method :run_without_metadata, :run
|
47
|
+
def run(result, &block)
|
48
|
+
run_without_metadata(TestResultMetadataSupport.new(result, self), &block)
|
49
|
+
end
|
50
|
+
|
51
|
+
def metadata
|
52
|
+
self.class.get_metadata(@method_name) || {}
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class TestResultMetadataSupport
|
57
|
+
def initialize(result, test)
|
58
|
+
@result = result
|
59
|
+
@test = test
|
60
|
+
end
|
61
|
+
|
62
|
+
def add_failure(failure)
|
63
|
+
failure.metadata = @test.metadata
|
64
|
+
method_missing(:add_failure, failure)
|
65
|
+
end
|
66
|
+
|
67
|
+
def add_error(error)
|
68
|
+
error.metadata = @test.metadata
|
69
|
+
method_missing(:add_error, error)
|
70
|
+
end
|
71
|
+
|
72
|
+
def method_missing(name, *args, &block)
|
73
|
+
@result.send(name, *args, &block)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
module MetadataFormatter
|
78
|
+
private
|
79
|
+
def format_metadata
|
80
|
+
return '' if metadata.empty?
|
81
|
+
metadata.collect do |key, value|
|
82
|
+
" #{key}: #{value}"
|
83
|
+
end.join("\n") + "\n"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
class Failure
|
88
|
+
include MetadataFormatter
|
89
|
+
|
90
|
+
attr_accessor :metadata
|
91
|
+
|
92
|
+
alias_method :long_display_without_metadata, :long_display
|
93
|
+
def long_display
|
94
|
+
long_display_without_metadata.sub(/(^#{Regexp.escape(@test_name)}.*\n)/,
|
95
|
+
"\\1#{format_metadata}")
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
class Error
|
100
|
+
include MetadataFormatter
|
101
|
+
|
102
|
+
attr_accessor :metadata
|
103
|
+
|
104
|
+
alias_method :long_display_without_metadata, :long_display
|
105
|
+
def long_display
|
106
|
+
long_display_without_metadata.sub(/(^#{Regexp.escape(@test_name)}:\n)/,
|
107
|
+
"\\1#{format_metadata}")
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|