test-unit-ext 0.1.0

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