test-unit 2.0.3 → 2.0.4
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/History.txt +22 -0
- data/Manifest.txt +5 -3
- data/README.txt +2 -1
- data/images/color-diff.png +0 -0
- data/lib/test/unit.rb +20 -40
- data/lib/test/unit/assertionfailederror.rb +11 -0
- data/lib/test/unit/assertions.rb +30 -9
- data/lib/test/unit/autorunner.rb +32 -6
- data/lib/test/unit/collector/load.rb +3 -1
- data/lib/test/unit/color-scheme.rb +17 -1
- data/lib/test/unit/diff.rb +221 -37
- data/lib/test/unit/error.rb +7 -5
- data/lib/test/unit/failure.rb +27 -5
- data/lib/test/unit/runner/tap.rb +8 -0
- data/lib/test/unit/ui/console/testrunner.rb +251 -10
- data/lib/test/unit/ui/emacs/testrunner.rb +14 -0
- data/lib/test/unit/ui/tap/testrunner.rb +92 -0
- data/lib/test/unit/ui/testrunner.rb +8 -0
- data/lib/test/unit/version.rb +1 -1
- data/sample/{tc_adder.rb → test_adder.rb} +3 -1
- data/sample/{tc_subtracter.rb → test_subtracter.rb} +3 -1
- data/sample/test_user.rb +1 -0
- data/test/collector/test-load.rb +1 -5
- data/test/run-test.rb +2 -0
- data/test/test-color-scheme.rb +11 -0
- data/test/test-diff.rb +33 -7
- data/test/test_assertions.rb +8 -10
- metadata +15 -13
- data/sample/ts_examples.rb +0 -7
data/History.txt
CHANGED
@@ -1,3 +1,25 @@
|
|
1
|
+
=== 2.0.4 / 2009-10-17
|
2
|
+
|
3
|
+
* 4 major enhancements
|
4
|
+
* use ~/.test-unit.yml as global configuration file.
|
5
|
+
* add TAP runner. (--runner tap)
|
6
|
+
* support colorized diff:
|
7
|
+
http://test-unit.rubyforge.org/svn/trunk/images/color-diff.png
|
8
|
+
* add Test::Unit::AutoRunner.default_runner= to specify default test runner.
|
9
|
+
|
10
|
+
* 4 minor enchancements
|
11
|
+
* improve verbose mode output format. (use indent)
|
12
|
+
* support NOT_PASS_THROUGH_EXCEPTIONS.
|
13
|
+
* support arguments option in #{runner}_options.
|
14
|
+
* TC_ -> Test in sample test case name.
|
15
|
+
|
16
|
+
* 1 bug fixes
|
17
|
+
* [#27195] test-unit-2.0.3 + ruby-1.9.1 cannot properly test
|
18
|
+
DelegateClass subclasses [Mike Pomraning]
|
19
|
+
|
20
|
+
* Thanks
|
21
|
+
* Mike Pomraning
|
22
|
+
|
1
23
|
=== 2.0.3 / 2009-07-19
|
2
24
|
|
3
25
|
* 6 major enhancements
|
data/Manifest.txt
CHANGED
@@ -8,6 +8,7 @@ html/classic.html
|
|
8
8
|
html/index.html
|
9
9
|
html/index.html.ja
|
10
10
|
html/test-unit-classic.png
|
11
|
+
images/color-diff.png
|
11
12
|
lib/test/unit.rb
|
12
13
|
lib/test/unit/assertionfailederror.rb
|
13
14
|
lib/test/unit/assertions.rb
|
@@ -31,12 +32,14 @@ lib/test/unit/pending.rb
|
|
31
32
|
lib/test/unit/priority.rb
|
32
33
|
lib/test/unit/runner/console.rb
|
33
34
|
lib/test/unit/runner/emacs.rb
|
35
|
+
lib/test/unit/runner/tap.rb
|
34
36
|
lib/test/unit/testcase.rb
|
35
37
|
lib/test/unit/testresult.rb
|
36
38
|
lib/test/unit/testsuite.rb
|
37
39
|
lib/test/unit/ui/console/outputlevel.rb
|
38
40
|
lib/test/unit/ui/console/testrunner.rb
|
39
41
|
lib/test/unit/ui/emacs/testrunner.rb
|
42
|
+
lib/test/unit/ui/tap/testrunner.rb
|
40
43
|
lib/test/unit/ui/testrunner.rb
|
41
44
|
lib/test/unit/ui/testrunnermediator.rb
|
42
45
|
lib/test/unit/ui/testrunnerutilities.rb
|
@@ -47,10 +50,9 @@ lib/test/unit/util/procwrapper.rb
|
|
47
50
|
lib/test/unit/version.rb
|
48
51
|
sample/adder.rb
|
49
52
|
sample/subtracter.rb
|
50
|
-
sample/
|
51
|
-
sample/
|
53
|
+
sample/test_adder.rb
|
54
|
+
sample/test_subtracter.rb
|
52
55
|
sample/test_user.rb
|
53
|
-
sample/ts_examples.rb
|
54
56
|
test/collector/test-descendant.rb
|
55
57
|
test/collector/test-load.rb
|
56
58
|
test/collector/test_dir.rb
|
data/README.txt
CHANGED
@@ -7,7 +7,7 @@
|
|
7
7
|
Test::Unit 2.x - Improved version of Test::Unit bundled in
|
8
8
|
Ruby 1.8.x.
|
9
9
|
|
10
|
-
Ruby 1.9.x bundles
|
10
|
+
Ruby 1.9.x bundles minitest not Test::Unit. Test::Unit
|
11
11
|
bundled in Ruby 1.8.x had not been improved but unbundled
|
12
12
|
Test::Unit (Test::Unit 2.x) will be improved actively.
|
13
13
|
|
@@ -50,3 +50,4 @@ This software is distributed under the same terms as ruby.
|
|
50
50
|
* Bill Lear: A suggestion.
|
51
51
|
* Diego Pettenò: A bug report.
|
52
52
|
* Angelo Lakra: A bug report.
|
53
|
+
* Mike Pomraning: A bug report.
|
Binary file
|
data/lib/test/unit.rb
CHANGED
@@ -179,7 +179,7 @@ module Test # :nodoc:
|
|
179
179
|
#
|
180
180
|
# require 'test/unit'
|
181
181
|
#
|
182
|
-
# class
|
182
|
+
# class MyTest < Test::Unit::TestCase
|
183
183
|
# # def setup
|
184
184
|
# # end
|
185
185
|
#
|
@@ -194,21 +194,17 @@ module Test # :nodoc:
|
|
194
194
|
#
|
195
195
|
# == Test Runners
|
196
196
|
#
|
197
|
-
# So, now you have this great test class, but you still
|
198
|
-
# run it and view any failures that occur
|
199
|
-
#
|
200
|
-
#
|
201
|
-
# runner is automatically invoked for you if you require
|
202
|
-
# and simply run the file. To use another
|
203
|
-
#
|
204
|
-
#
|
205
|
-
# Test::Unit::TestSuite. This can be as simple as passing in your
|
206
|
-
# TestCase class (which has a class suite method). It might look
|
207
|
-
# something like this:
|
208
|
-
#
|
209
|
-
# require 'test/unit/ui/console/testrunner'
|
210
|
-
# Test::Unit::UI::Console::TestRunner.run(TC_MyTest)
|
197
|
+
# So, now you have this great test class, but you still
|
198
|
+
# need a way to run it and view any failures that occur
|
199
|
+
# during the run. There are some test runner; console test
|
200
|
+
# runner, GTK+ test runner and so on. The console test
|
201
|
+
# runner is automatically invoked for you if you require
|
202
|
+
# 'test/unit' and simply run the file. To use another
|
203
|
+
# runner simply set default test runner ID to
|
204
|
+
# Test::Unit::AutoRunner:
|
211
205
|
#
|
206
|
+
# require 'test/unit'
|
207
|
+
# Test::Unit::AutoRunner.default_runner = "gtk2"
|
212
208
|
#
|
213
209
|
# == Test Suite
|
214
210
|
#
|
@@ -220,33 +216,17 @@ module Test # :nodoc:
|
|
220
216
|
# in response to a suite method. The TestSuite can, in turn, contain
|
221
217
|
# other TestSuites or individual tests (typically created by a
|
222
218
|
# TestCase). In other words, you can easily wrap up a group of
|
223
|
-
# TestCases and TestSuites
|
224
|
-
#
|
225
|
-
#
|
226
|
-
#
|
227
|
-
#
|
228
|
-
#
|
229
|
-
#
|
230
|
-
# class TS_MyTests
|
231
|
-
# def self.suite
|
232
|
-
# suite = Test::Unit::TestSuite.new
|
233
|
-
# suite << TC_MyFirstTests.suite
|
234
|
-
# suite << TC_MoreTestsByMe.suite
|
235
|
-
# suite << TS_AnotherSetOfTests.suite
|
236
|
-
# return suite
|
237
|
-
# end
|
238
|
-
# end
|
239
|
-
# Test::Unit::UI::Console::TestRunner.run(TS_MyTests)
|
240
|
-
#
|
241
|
-
# Now, this is a bit cumbersome, so Test::Unit does a little bit more
|
242
|
-
# for you, by wrapping these up automatically when you require
|
243
|
-
# 'test/unit'. What does this mean? It means you could write the above
|
244
|
-
# test case like this instead:
|
219
|
+
# TestCases and TestSuites.
|
220
|
+
#
|
221
|
+
# Test::Unit does a little bit more for you, by wrapping
|
222
|
+
# these up automatically when you require
|
223
|
+
# 'test/unit'. What does this mean? It means you could
|
224
|
+
# write the above test case like this instead:
|
245
225
|
#
|
246
226
|
# require 'test/unit'
|
247
|
-
# require '
|
248
|
-
# require '
|
249
|
-
# require '
|
227
|
+
# require 'test_myfirsttests'
|
228
|
+
# require 'test_moretestsbyme'
|
229
|
+
# require 'test_anothersetoftests'
|
250
230
|
#
|
251
231
|
# Test::Unit is smart enough to find all the test cases existing in
|
252
232
|
# the ObjectSpace and wrap them up into a suite for you. It then runs
|
@@ -9,6 +9,17 @@ module Test
|
|
9
9
|
|
10
10
|
# Thrown by Test::Unit::Assertions when an assertion fails.
|
11
11
|
class AssertionFailedError < StandardError
|
12
|
+
attr_accessor :expected, :actual, :user_message
|
13
|
+
attr_accessor :inspected_expected, :inspected_actual
|
14
|
+
def initialize(message=nil, options=nil)
|
15
|
+
options ||= {}
|
16
|
+
@expected = options[:expected]
|
17
|
+
@actual = options[:actual]
|
18
|
+
@inspected_expected = options[:inspected_expected]
|
19
|
+
@inspected_actual = options[:inspected_actual]
|
20
|
+
@user_message = options[:user_message]
|
21
|
+
super(message)
|
22
|
+
end
|
12
23
|
end
|
13
24
|
end
|
14
25
|
end
|
data/lib/test/unit/assertions.rb
CHANGED
@@ -84,7 +84,16 @@ module Test
|
|
84
84
|
<?> expected but was
|
85
85
|
<?>.?
|
86
86
|
EOT
|
87
|
-
|
87
|
+
begin
|
88
|
+
assert_block(full_message) { expected == actual }
|
89
|
+
rescue AssertionFailedError => failure
|
90
|
+
failure.expected = expected
|
91
|
+
failure.actual = actual
|
92
|
+
failure.inspected_expected = AssertionMessage.convert(expected)
|
93
|
+
failure.inspected_actual = AssertionMessage.convert(actual)
|
94
|
+
failure.user_message = message
|
95
|
+
raise
|
96
|
+
end
|
88
97
|
end
|
89
98
|
|
90
99
|
##
|
@@ -880,7 +889,7 @@ EOT
|
|
880
889
|
MaybeContainer.new(value, &formatter)
|
881
890
|
end
|
882
891
|
|
883
|
-
MAX_DIFF_TARGET_STRING_SIZE =
|
892
|
+
MAX_DIFF_TARGET_STRING_SIZE = 1000
|
884
893
|
def diff_target_string?(string)
|
885
894
|
if string.respond_to?(:bytesize)
|
886
895
|
string.bytesize < MAX_DIFF_TARGET_STRING_SIZE
|
@@ -889,15 +898,24 @@ EOT
|
|
889
898
|
end
|
890
899
|
end
|
891
900
|
|
901
|
+
def prepare_for_diff(from, to)
|
902
|
+
if !from.is_a?(String) or !to.is_a?(String)
|
903
|
+
from = convert(from)
|
904
|
+
to = convert(to)
|
905
|
+
end
|
906
|
+
|
907
|
+
if diff_target_string?(from) and diff_target_string?(to)
|
908
|
+
[from, to]
|
909
|
+
else
|
910
|
+
[nil, nil]
|
911
|
+
end
|
912
|
+
end
|
913
|
+
|
892
914
|
def delayed_diff(from, to)
|
893
915
|
delayed_literal do
|
894
|
-
|
895
|
-
from = convert(from)
|
896
|
-
to = convert(to)
|
897
|
-
end
|
916
|
+
from, to = prepare_for_diff(from, to)
|
898
917
|
|
899
|
-
diff = nil
|
900
|
-
diff = "" if !diff_target_string?(from) or !diff_target_string?(to)
|
918
|
+
diff = "" if from.nil? or to.nil?
|
901
919
|
diff ||= Diff.readable(from, to)
|
902
920
|
if /^[-+]/ !~ diff
|
903
921
|
diff = ""
|
@@ -930,7 +948,10 @@ EOM
|
|
930
948
|
if use_pp
|
931
949
|
begin
|
932
950
|
require 'pp' unless defined?(PP)
|
933
|
-
|
951
|
+
begin
|
952
|
+
return PP.pp(object, '').chomp
|
953
|
+
rescue NameError
|
954
|
+
end
|
934
955
|
rescue LoadError
|
935
956
|
self.use_pp = false
|
936
957
|
end
|
data/lib/test/unit/autorunner.rb
CHANGED
@@ -18,6 +18,15 @@ module Test
|
|
18
18
|
RUNNERS[id.to_s]
|
19
19
|
end
|
20
20
|
|
21
|
+
@@default_runner = nil
|
22
|
+
def default_runner
|
23
|
+
runner(@@default_runner)
|
24
|
+
end
|
25
|
+
|
26
|
+
def default_runner=(id)
|
27
|
+
@@default_runner = id
|
28
|
+
end
|
29
|
+
|
21
30
|
def register_collector(id, collector_builder=Proc.new)
|
22
31
|
COLLECTORS[id] = collector_builder
|
23
32
|
COLLECTORS[id.to_s] = collector_builder
|
@@ -102,15 +111,23 @@ module Test
|
|
102
111
|
@to_run = []
|
103
112
|
@color_scheme = ColorScheme.default
|
104
113
|
@runner_options = {}
|
114
|
+
@default_arguments = []
|
105
115
|
@workdir = nil
|
106
|
-
|
107
|
-
|
116
|
+
config_file = "test-unit.yml"
|
117
|
+
if File.exist?(config_file)
|
118
|
+
load_config(config_file)
|
119
|
+
else
|
120
|
+
global_config_file = File.expand_path("~/.test-unit.xml")
|
121
|
+
load_config(global_config_file) if File.exist?(global_config_file)
|
122
|
+
end
|
108
123
|
yield(self) if block_given?
|
109
124
|
end
|
110
125
|
|
111
126
|
def process_args(args = ARGV)
|
127
|
+
default_arguments = @default_arguments.dup
|
112
128
|
begin
|
113
|
-
|
129
|
+
@default_arguments.concat(args)
|
130
|
+
options.order!(@default_arguments) {|arg| @to_run << arg}
|
114
131
|
rescue OptionParser::ParseError => e
|
115
132
|
puts e
|
116
133
|
puts options
|
@@ -119,6 +136,8 @@ module Test
|
|
119
136
|
@filters << proc{false} unless(@filters.empty?)
|
120
137
|
end
|
121
138
|
not @to_run.empty?
|
139
|
+
ensure
|
140
|
+
@default_arguments = default_arguments
|
122
141
|
end
|
123
142
|
|
124
143
|
def options
|
@@ -304,18 +323,24 @@ module Test
|
|
304
323
|
(config["#{runner_name}_options"] || {}).each do |key, value|
|
305
324
|
key = key.to_sym
|
306
325
|
value = ColorScheme[value] if key == :color_scheme
|
307
|
-
|
326
|
+
if key == :arguments
|
327
|
+
@default_arguments.concat(value.split)
|
328
|
+
else
|
329
|
+
runner_options[key.to_sym] = value
|
330
|
+
end
|
308
331
|
end
|
309
332
|
@runner_options = @runner_options.merge(runner_options)
|
310
333
|
end
|
311
334
|
|
312
335
|
private
|
313
336
|
def default_runner
|
337
|
+
runner = self.class.default_runner
|
314
338
|
if ENV["EMACS"] == "t"
|
315
|
-
self.class.runner(:emacs)
|
339
|
+
runner ||= self.class.runner(:emacs)
|
316
340
|
else
|
317
|
-
self.class.runner(:console)
|
341
|
+
runner ||= self.class.runner(:console)
|
318
342
|
end
|
343
|
+
runner
|
319
344
|
end
|
320
345
|
|
321
346
|
def default_collector
|
@@ -327,3 +352,4 @@ end
|
|
327
352
|
|
328
353
|
require 'test/unit/runner/console'
|
329
354
|
require 'test/unit/runner/emacs'
|
355
|
+
require 'test/unit/runner/tap'
|
@@ -76,7 +76,9 @@ module Test
|
|
76
76
|
sub_test_suites << sub_test_suite unless sub_test_suite.empty?
|
77
77
|
end
|
78
78
|
else
|
79
|
-
|
79
|
+
unless excluded_file?(path.basename.to_s)
|
80
|
+
collect_file(path, sub_test_suites, already_gathered)
|
81
|
+
end
|
80
82
|
end
|
81
83
|
|
82
84
|
test_suite = TestSuite.new(path.basename.to_s)
|
@@ -14,7 +14,23 @@ module Test
|
|
14
14
|
"omission" => Color.new("blue", :bold => true),
|
15
15
|
"notification" => Color.new("cyan", :bold => true),
|
16
16
|
"error" => Color.new("yellow", :bold => true) +
|
17
|
-
Color.new("black", :foreground => false)
|
17
|
+
Color.new("black", :foreground => false),
|
18
|
+
"case" => Color.new("white", :bold => true) +
|
19
|
+
Color.new("blue", :foreground => false),
|
20
|
+
"suite" => Color.new("white", :bold => true) +
|
21
|
+
Color.new("green", :foreground => false),
|
22
|
+
"diff-inserted-tag" =>
|
23
|
+
Color.new("red", :bold => true),
|
24
|
+
"diff-deleted-tag" =>
|
25
|
+
Color.new("green", :bold => true),
|
26
|
+
"diff-difference-tag" =>
|
27
|
+
Color.new("cyan", :bold => true),
|
28
|
+
"diff-inserted" =>
|
29
|
+
Color.new("red", :foreground => false) +
|
30
|
+
Color.new("white", :bold => true),
|
31
|
+
"diff-deleted" =>
|
32
|
+
Color.new("green", :foreground => false) +
|
33
|
+
Color.new("white", :bold => true))
|
18
34
|
end
|
19
35
|
|
20
36
|
@@schemes = {}
|
data/lib/test/unit/diff.rb
CHANGED
@@ -35,7 +35,7 @@ module Test
|
|
35
35
|
|
36
36
|
def grouped_operations(context_size=nil)
|
37
37
|
context_size ||= 3
|
38
|
-
_operations = operations
|
38
|
+
_operations = operations.dup
|
39
39
|
_operations = [[:equal, 0, 0, 0, 0]] if _operations.empty?
|
40
40
|
expand_edge_equal_operations!(_operations, context_size)
|
41
41
|
|
@@ -266,29 +266,187 @@ module Test
|
|
266
266
|
end
|
267
267
|
end
|
268
268
|
|
269
|
+
class UTF8Line
|
270
|
+
class << self
|
271
|
+
# from http://unicode.org/reports/tr11/
|
272
|
+
WIDE_CHARACTERS =
|
273
|
+
[0x1100..0x1159, 0x115F..0x115F, 0x2329..0x232A,
|
274
|
+
0x2E80..0x2E99, 0x2E9B..0x2EF3, 0x2F00..0x2FD5,
|
275
|
+
0x2FF0..0x2FFB, 0x3000..0x303E, 0x3041..0x3096,
|
276
|
+
0x3099..0x30FF, 0x3105..0x312D, 0x3131..0x318E,
|
277
|
+
0x3190..0x31B7, 0x31C0..0x31E3, 0x31F0..0x321E,
|
278
|
+
0x3220..0x3243, 0x3250..0x32FE, 0x3300..0x4DB5,
|
279
|
+
0x4E00..0x9FC3, 0xA000..0xA48C, 0xA490..0xA4C6,
|
280
|
+
0xAC00..0xD7A3, 0xF900..0xFA2D, 0xFA30..0xFA6A,
|
281
|
+
0xFA70..0xFAD9, 0xFE10..0xFE19, 0xFE30..0xFE52,
|
282
|
+
0xFE54..0xFE66, 0xFE68..0xFE6B, 0xFF01..0xFF60,
|
283
|
+
0xFFE0..0xFFE6, 0x20000..0x2FFFD, 0x30000..0x3FFFD,
|
284
|
+
]
|
285
|
+
|
286
|
+
AMBIGUOUS =
|
287
|
+
[0x00A1..0x00A1, 0x00A4..0x00A4, 0x00A7..0x00A8,
|
288
|
+
0x00AA..0x00AA, 0x00AD..0x00AE, 0x00B0..0x00B4,
|
289
|
+
0x00B6..0x00BA, 0x00BC..0x00BF, 0x00C6..0x00C6,
|
290
|
+
0x00D0..0x00D0, 0x00D7..0x00D8, 0x00DE..0x00E1,
|
291
|
+
0x00E6..0x00E6, 0x00E8..0x00EA, 0x00EC..0x00ED,
|
292
|
+
0x00F0..0x00F0, 0x00F2..0x00F3, 0x00F7..0x00FA,
|
293
|
+
0x00FC..0x00FC, 0x00FE..0x00FE, 0x0101..0x0101,
|
294
|
+
0x0111..0x0111, 0x0113..0x0113, 0x011B..0x011B,
|
295
|
+
0x0126..0x0127, 0x012B..0x012B, 0x0131..0x0133,
|
296
|
+
0x0138..0x0138, 0x013F..0x0142, 0x0144..0x0144,
|
297
|
+
0x0148..0x014B, 0x014D..0x014D, 0x0152..0x0153,
|
298
|
+
0x0166..0x0167, 0x016B..0x016B, 0x01CE..0x01CE,
|
299
|
+
0x01D0..0x01D0, 0x01D2..0x01D2, 0x01D4..0x01D4,
|
300
|
+
0x01D6..0x01D6, 0x01D8..0x01D8, 0x01DA..0x01DA,
|
301
|
+
0x01DC..0x01DC, 0x0251..0x0251, 0x0261..0x0261,
|
302
|
+
0x02C4..0x02C4, 0x02C7..0x02C7, 0x02C9..0x02CB,
|
303
|
+
0x02CD..0x02CD, 0x02D0..0x02D0, 0x02D8..0x02DB,
|
304
|
+
0x02DD..0x02DD, 0x02DF..0x02DF, 0x0300..0x036F,
|
305
|
+
0x0391..0x03A1, 0x03A3..0x03A9, 0x03B1..0x03C1,
|
306
|
+
0x03C3..0x03C9, 0x0401..0x0401, 0x0410..0x044F,
|
307
|
+
0x0451..0x0451, 0x2010..0x2010, 0x2013..0x2016,
|
308
|
+
0x2018..0x2019, 0x201C..0x201D, 0x2020..0x2022,
|
309
|
+
0x2024..0x2027, 0x2030..0x2030, 0x2032..0x2033,
|
310
|
+
0x2035..0x2035, 0x203B..0x203B, 0x203E..0x203E,
|
311
|
+
0x2074..0x2074, 0x207F..0x207F, 0x2081..0x2084,
|
312
|
+
0x20AC..0x20AC, 0x2103..0x2103, 0x2105..0x2105,
|
313
|
+
0x2109..0x2109, 0x2113..0x2113, 0x2116..0x2116,
|
314
|
+
0x2121..0x2122, 0x2126..0x2126, 0x212B..0x212B,
|
315
|
+
0x2153..0x2154, 0x215B..0x215E, 0x2160..0x216B,
|
316
|
+
0x2170..0x2179, 0x2190..0x2199, 0x21B8..0x21B9,
|
317
|
+
0x21D2..0x21D2, 0x21D4..0x21D4, 0x21E7..0x21E7,
|
318
|
+
0x2200..0x2200, 0x2202..0x2203, 0x2207..0x2208,
|
319
|
+
0x220B..0x220B, 0x220F..0x220F, 0x2211..0x2211,
|
320
|
+
0x2215..0x2215, 0x221A..0x221A, 0x221D..0x2220,
|
321
|
+
0x2223..0x2223, 0x2225..0x2225, 0x2227..0x222C,
|
322
|
+
0x222E..0x222E, 0x2234..0x2237, 0x223C..0x223D,
|
323
|
+
0x2248..0x2248, 0x224C..0x224C, 0x2252..0x2252,
|
324
|
+
0x2260..0x2261, 0x2264..0x2267, 0x226A..0x226B,
|
325
|
+
0x226E..0x226F, 0x2282..0x2283, 0x2286..0x2287,
|
326
|
+
0x2295..0x2295, 0x2299..0x2299, 0x22A5..0x22A5,
|
327
|
+
0x22BF..0x22BF, 0x2312..0x2312, 0x2460..0x24E9,
|
328
|
+
0x24EB..0x254B, 0x2550..0x2573, 0x2580..0x258F,
|
329
|
+
0x2592..0x2595, 0x25A0..0x25A1, 0x25A3..0x25A9,
|
330
|
+
0x25B2..0x25B3, 0x25B6..0x25B7, 0x25BC..0x25BD,
|
331
|
+
0x25C0..0x25C1, 0x25C6..0x25C8, 0x25CB..0x25CB,
|
332
|
+
0x25CE..0x25D1, 0x25E2..0x25E5, 0x25EF..0x25EF,
|
333
|
+
0x2605..0x2606, 0x2609..0x2609, 0x260E..0x260F,
|
334
|
+
0x2614..0x2615, 0x261C..0x261C, 0x261E..0x261E,
|
335
|
+
0x2640..0x2640, 0x2642..0x2642, 0x2660..0x2661,
|
336
|
+
0x2663..0x2665, 0x2667..0x266A, 0x266C..0x266D,
|
337
|
+
0x266F..0x266F, 0x273D..0x273D, 0x2776..0x277F,
|
338
|
+
0xE000..0xF8FF, 0xFE00..0xFE0F, 0xFFFD..0xFFFD,
|
339
|
+
0xE0100..0xE01EF, 0xF0000..0xFFFFD, 0x100000..0x10FFFD,
|
340
|
+
]
|
341
|
+
|
342
|
+
def wide_character?(character)
|
343
|
+
binary_search_ranges(character, WIDE_CHARACTERS) or
|
344
|
+
binary_search_ranges(character, AMBIGUOUS)
|
345
|
+
end
|
346
|
+
|
347
|
+
private
|
348
|
+
def binary_search_ranges(character, ranges)
|
349
|
+
if ranges.size.zero?
|
350
|
+
false
|
351
|
+
elsif ranges.size == 1
|
352
|
+
ranges[0].include?(character)
|
353
|
+
else
|
354
|
+
half = ranges.size / 2
|
355
|
+
range = ranges[half]
|
356
|
+
if range.include?(character)
|
357
|
+
true
|
358
|
+
elsif character < range.begin
|
359
|
+
binary_search_ranges(character, ranges[0...half])
|
360
|
+
else
|
361
|
+
binary_search_ranges(character, ranges[(half + 1)..-1])
|
362
|
+
end
|
363
|
+
end
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
def initialize(line)
|
368
|
+
@line = line
|
369
|
+
@characters = @line.unpack("U*")
|
370
|
+
end
|
371
|
+
|
372
|
+
def [](*args)
|
373
|
+
result = @characters[*args]
|
374
|
+
if result.respond_to?(:pack)
|
375
|
+
result.pack("U*")
|
376
|
+
else
|
377
|
+
result
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
def each(&block)
|
382
|
+
@characters.each(&block)
|
383
|
+
end
|
384
|
+
|
385
|
+
def size
|
386
|
+
@characters.size
|
387
|
+
end
|
388
|
+
|
389
|
+
def to_s
|
390
|
+
@line
|
391
|
+
end
|
392
|
+
|
393
|
+
def compute_width(start, _end)
|
394
|
+
width = 0
|
395
|
+
start.upto(_end - 1) do |i|
|
396
|
+
if self.class.wide_character?(@characters[i])
|
397
|
+
width += 2
|
398
|
+
else
|
399
|
+
width += 1
|
400
|
+
end
|
401
|
+
end
|
402
|
+
width
|
403
|
+
end
|
404
|
+
end
|
405
|
+
|
269
406
|
class ReadableDiffer < Differ
|
270
407
|
def diff(options={})
|
271
|
-
result = []
|
272
|
-
|
273
|
-
matcher.operations.each do |args|
|
274
|
-
tag, from_start, from_end, to_start, to_end = args
|
408
|
+
@result = []
|
409
|
+
operations.each do |tag, from_start, from_end, to_start, to_end|
|
275
410
|
case tag
|
276
411
|
when :replace
|
277
|
-
|
412
|
+
diff_lines(from_start, from_end, to_start, to_end)
|
278
413
|
when :delete
|
279
|
-
|
414
|
+
tag_deleted(@from[from_start...from_end])
|
280
415
|
when :insert
|
281
|
-
|
416
|
+
tag_inserted(@to[to_start...to_end])
|
282
417
|
when :equal
|
283
|
-
|
418
|
+
tag_equal(@from[from_start...from_end])
|
284
419
|
else
|
285
420
|
raise "unknown tag: #{tag}"
|
286
421
|
end
|
287
422
|
end
|
288
|
-
result
|
423
|
+
@result
|
289
424
|
end
|
290
425
|
|
291
426
|
private
|
427
|
+
def operations
|
428
|
+
@operations ||= nil
|
429
|
+
if @operations.nil?
|
430
|
+
matcher = SequenceMatcher.new(@from, @to)
|
431
|
+
@operations = matcher.operations
|
432
|
+
end
|
433
|
+
@operations
|
434
|
+
end
|
435
|
+
|
436
|
+
def default_ratio
|
437
|
+
0.74
|
438
|
+
end
|
439
|
+
|
440
|
+
def cut_off_ratio
|
441
|
+
0.75
|
442
|
+
end
|
443
|
+
|
444
|
+
def tag(mark, contents)
|
445
|
+
contents.each do |content|
|
446
|
+
@result << "#{mark}#{content}"
|
447
|
+
end
|
448
|
+
end
|
449
|
+
|
292
450
|
def tag_deleted(contents)
|
293
451
|
tag("- ", contents)
|
294
452
|
end
|
@@ -306,7 +464,7 @@ module Test
|
|
306
464
|
end
|
307
465
|
|
308
466
|
def find_diff_line_info(from_start, from_end, to_start, to_end)
|
309
|
-
best_ratio =
|
467
|
+
best_ratio = default_ratio
|
310
468
|
from_equal_index = to_equal_index = nil
|
311
469
|
from_best_index = to_best_index = nil
|
312
470
|
|
@@ -334,30 +492,29 @@ module Test
|
|
334
492
|
end
|
335
493
|
|
336
494
|
def diff_lines(from_start, from_end, to_start, to_end)
|
337
|
-
cut_off = 0.75
|
338
|
-
|
339
495
|
info = find_diff_line_info(from_start, from_end, to_start, to_end)
|
340
496
|
best_ratio, from_equal_index, to_equal_index, *info = info
|
341
497
|
from_best_index, to_best_index = info
|
342
498
|
|
343
|
-
if best_ratio <
|
499
|
+
if best_ratio < cut_off_ratio
|
344
500
|
if from_equal_index.nil?
|
345
|
-
tagged_from = tag_deleted(@from[from_start...from_end])
|
346
|
-
tagged_to = tag_inserted(@to[to_start...to_end])
|
347
501
|
if to_end - to_start < from_end - from_start
|
348
|
-
|
502
|
+
tag_inserted(@to[to_start...to_end])
|
503
|
+
tag_deleted(@from[from_start...from_end])
|
349
504
|
else
|
350
|
-
|
505
|
+
tag_deleted(@from[from_start...from_end])
|
506
|
+
tag_inserted(@to[to_start...to_end])
|
351
507
|
end
|
508
|
+
return
|
352
509
|
end
|
353
510
|
from_best_index = from_equal_index
|
354
511
|
to_best_index = to_equal_index
|
355
512
|
best_ratio = 1.0
|
356
513
|
end
|
357
514
|
|
358
|
-
_diff_lines(from_start, from_best_index, to_start, to_best_index)
|
359
|
-
|
360
|
-
|
515
|
+
_diff_lines(from_start, from_best_index, to_start, to_best_index)
|
516
|
+
diff_line(@from[from_best_index], @to[to_best_index])
|
517
|
+
_diff_lines(from_best_index + 1, from_end, to_best_index + 1, to_end)
|
361
518
|
end
|
362
519
|
|
363
520
|
def _diff_lines(from_start, from_end, to_start, to_end)
|
@@ -372,26 +529,54 @@ module Test
|
|
372
529
|
end
|
373
530
|
end
|
374
531
|
|
532
|
+
def line_operations(from_line, to_line)
|
533
|
+
if !from_line.respond_to?(:force_encoding) and $KCODE == "UTF8"
|
534
|
+
from_line = UTF8Line.new(from_line)
|
535
|
+
to_line = UTF8Line.new(to_line)
|
536
|
+
end
|
537
|
+
matcher = SequenceMatcher.new(from_line, to_line,
|
538
|
+
&method(:space_character?))
|
539
|
+
[from_line, to_line, matcher.operations]
|
540
|
+
end
|
541
|
+
|
542
|
+
def compute_width(line, start, _end)
|
543
|
+
if line.respond_to?(:encoding) and
|
544
|
+
Encoding.compatible?(Encoding::UTF_8, line.encoding)
|
545
|
+
utf8_line = line[start..._end].encode(Encoding::UTF_8)
|
546
|
+
width = 0
|
547
|
+
utf8_line.each_codepoint do |unicode_codepoint|
|
548
|
+
if UTF8Line.wide_character?(unicode_codepoint)
|
549
|
+
width += 2
|
550
|
+
else
|
551
|
+
width += 1
|
552
|
+
end
|
553
|
+
end
|
554
|
+
width
|
555
|
+
elsif line.is_a?(UTF8Line)
|
556
|
+
line.compute_width(start, _end)
|
557
|
+
else
|
558
|
+
_end - start
|
559
|
+
end
|
560
|
+
end
|
561
|
+
|
375
562
|
def diff_line(from_line, to_line)
|
376
563
|
from_tags = ""
|
377
564
|
to_tags = ""
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
from_length = from_end - from_start
|
383
|
-
to_length = to_end - to_start
|
565
|
+
from_line, to_line, _operations = line_operations(from_line, to_line)
|
566
|
+
_operations.each do |tag, from_start, from_end, to_start, to_end|
|
567
|
+
from_width = compute_width(from_line, from_start, from_end)
|
568
|
+
to_width = compute_width(to_line, to_start, to_end)
|
384
569
|
case tag
|
385
570
|
when :replace
|
386
|
-
from_tags << "^" *
|
387
|
-
to_tags << "^" *
|
571
|
+
from_tags << "^" * from_width
|
572
|
+
to_tags << "^" * to_width
|
388
573
|
when :delete
|
389
|
-
from_tags << "-" *
|
574
|
+
from_tags << "-" * from_width
|
390
575
|
when :insert
|
391
|
-
to_tags << "+" *
|
576
|
+
to_tags << "+" * to_width
|
392
577
|
when :equal
|
393
|
-
from_tags << " " *
|
394
|
-
to_tags << " " *
|
578
|
+
from_tags << " " * from_width
|
579
|
+
to_tags << " " * to_width
|
395
580
|
else
|
396
581
|
raise "unknown tag: #{tag}"
|
397
582
|
end
|
@@ -409,13 +594,12 @@ module Test
|
|
409
594
|
|
410
595
|
result = tag_deleted([from_line])
|
411
596
|
unless from_tags.empty?
|
412
|
-
|
597
|
+
tag_difference(["#{"\t" * common}#{from_tags}"])
|
413
598
|
end
|
414
|
-
|
599
|
+
tag_inserted([to_line])
|
415
600
|
unless to_tags.empty?
|
416
|
-
|
601
|
+
tag_difference(["#{"\t" * common}#{to_tags}"])
|
417
602
|
end
|
418
|
-
result
|
419
603
|
end
|
420
604
|
|
421
605
|
def n_leading_characters(string, character)
|