test-unit-ext 0.1.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/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
|