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.
@@ -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
@@ -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/tc_adder.rb
51
- sample/tc_subtracter.rb
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 miniunit not Test::Unit. Test::Unit
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
@@ -179,7 +179,7 @@ module Test # :nodoc:
179
179
  #
180
180
  # require 'test/unit'
181
181
  #
182
- # class TC_MyTest < Test::Unit::TestCase
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 need a way to
198
- # run it and view any failures that occur during the run. This is
199
- # where Test::Unit::UI::Console::TestRunner (and others, such as
200
- # Test::Unit::UI::GTK::TestRunner) comes into play. The console test
201
- # runner is automatically invoked for you if you require 'test/unit'
202
- # and simply run the file. To use another runner, or to manually
203
- # invoke a runner, simply call its run class method and pass in an
204
- # object that responds to the suite message with a
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 like this:
224
- #
225
- # require 'test/unit/testsuite'
226
- # require 'tc_myfirsttests'
227
- # require 'tc_moretestsbyme'
228
- # require 'ts_anothersetoftests'
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 'tc_myfirsttests'
248
- # require 'tc_moretestsbyme'
249
- # require 'ts_anothersetoftests'
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
@@ -84,7 +84,16 @@ module Test
84
84
  <?> expected but was
85
85
  <?>.?
86
86
  EOT
87
- assert_block(full_message) { expected == actual }
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 = 300
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
- if !from.is_a?(String) or !to.is_a?(String)
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
- return PP.pp(object, '').chomp
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
@@ -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
- default_config_file = "test-unit.yml"
107
- load_config(default_config_file) if File.exist?(default_config_file)
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
- options.order!(args) {|arg| @to_run << arg}
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
- runner_options[key.to_sym] = value
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
- collect_file(path, sub_test_suites, already_gathered)
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 = {}
@@ -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
- matcher = SequenceMatcher.new(@from, @to)
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
- result.concat(diff_lines(from_start, from_end, to_start, to_end))
412
+ diff_lines(from_start, from_end, to_start, to_end)
278
413
  when :delete
279
- result.concat(tag_deleted(@from[from_start...from_end]))
414
+ tag_deleted(@from[from_start...from_end])
280
415
  when :insert
281
- result.concat(tag_inserted(@to[to_start...to_end]))
416
+ tag_inserted(@to[to_start...to_end])
282
417
  when :equal
283
- result.concat(tag_equal(@from[from_start...from_end]))
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 = 0.74
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 < cut_off
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
- return tagged_to + tagged_from
502
+ tag_inserted(@to[to_start...to_end])
503
+ tag_deleted(@from[from_start...from_end])
349
504
  else
350
- return tagged_from + tagged_to
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
- diff_line(@from[from_best_index], @to[to_best_index]) +
360
- _diff_lines(from_best_index + 1, from_end, to_best_index + 1, to_end)
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
- matcher = SequenceMatcher.new(from_line, to_line,
379
- &method(:space_character?))
380
- operations = matcher.operations
381
- operations.each do |tag, from_start, from_end, to_start, to_end|
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 << "^" * from_length
387
- to_tags << "^" * to_length
571
+ from_tags << "^" * from_width
572
+ to_tags << "^" * to_width
388
573
  when :delete
389
- from_tags << "-" * from_length
574
+ from_tags << "-" * from_width
390
575
  when :insert
391
- to_tags << "+" * to_length
576
+ to_tags << "+" * to_width
392
577
  when :equal
393
- from_tags << " " * from_length
394
- to_tags << " " * to_length
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
- result.concat(tag_difference(["#{"\t" * common}#{from_tags}"]))
597
+ tag_difference(["#{"\t" * common}#{from_tags}"])
413
598
  end
414
- result.concat(tag_inserted([to_line]))
599
+ tag_inserted([to_line])
415
600
  unless to_tags.empty?
416
- result.concat(tag_difference(["#{"\t" * common}#{to_tags}"]))
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)