diff-lcs 2.0.0.beta.1 → 2.0.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.
Files changed (120) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +32 -5
  3. data/CODE_OF_CONDUCT.md +152 -114
  4. data/CONTRIBUTING.md +8 -3
  5. data/CONTRIBUTORS.md +19 -9
  6. data/Manifest.txt +88 -70
  7. data/README.md +30 -17
  8. data/Rakefile +90 -22
  9. data/SECURITY.md +3 -12
  10. data/integration/compare/array_diff_spec.rb +10 -0
  11. data/integration/compare/hash_diff_spec.rb +25 -0
  12. data/integration/compare/string_diff_spec.rb +10 -0
  13. data/integration/rspec_differ_spec.rb +26 -0
  14. data/integration/rspec_expectations_spec.rb +32 -0
  15. data/integration/runner +20 -0
  16. data/lib/diff/lcs/change.rb +21 -16
  17. data/lib/diff/lcs/ldiff.rb +9 -4
  18. data/lib/diff/lcs/version.rb +1 -1
  19. data/spec/hunk_spec.rb +32 -34
  20. data/spec/ldiff_spec.rb +7 -7
  21. data/spec/spec_helper.rb +4 -12
  22. data/test/fixtures/ldiff/output.diff-c +7 -0
  23. data/test/fixtures/ldiff/output.diff-u +5 -0
  24. data/test/fixtures/ldiff/output.diff.bin2 +1 -0
  25. data/test/fixtures/ldiff/output.diff.bin2-c +1 -0
  26. data/test/fixtures/ldiff/output.diff.bin2-e +1 -0
  27. data/test/fixtures/ldiff/output.diff.bin2-f +1 -0
  28. data/test/fixtures/ldiff/output.diff.bin2-u +1 -0
  29. data/{spec → test}/fixtures/ldiff/output.diff.chef-c +2 -2
  30. data/test/fixtures/ldiff/output.diff.chef-u +9 -0
  31. data/{spec → test}/fixtures/ldiff/output.diff.chef2-c +2 -2
  32. data/{spec → test}/fixtures/ldiff/output.diff.chef2-u +2 -2
  33. data/test/fixtures/ldiff/output.diff.empty.vs.four_lines-c +9 -0
  34. data/test/fixtures/ldiff/output.diff.empty.vs.four_lines-u +7 -0
  35. data/test/fixtures/ldiff/output.diff.four_lines.vs.empty-c +9 -0
  36. data/test/fixtures/ldiff/output.diff.four_lines.vs.empty-u +7 -0
  37. data/test/fixtures/ldiff/output.diff.issue95_trailing_context-c +9 -0
  38. data/test/fixtures/ldiff/output.diff.issue95_trailing_context-u +6 -0
  39. data/{spec → test}/fixtures/ldiff/output.diff.missing_new_line1-c +2 -2
  40. data/test/fixtures/ldiff/output.diff.missing_new_line1-u +9 -0
  41. data/{spec → test}/fixtures/ldiff/output.diff.missing_new_line2-c +2 -2
  42. data/test/fixtures/ldiff/output.diff.missing_new_line2-u +9 -0
  43. data/test/test_block.rb +34 -0
  44. data/test/test_change.rb +234 -0
  45. data/test/test_diff.rb +53 -0
  46. data/test/test_helper.rb +225 -0
  47. data/test/test_hunk.rb +72 -0
  48. data/test/test_issues.rb +168 -0
  49. data/test/test_lcs.rb +47 -0
  50. data/test/test_ldiff.rb +89 -0
  51. data/test/test_patch.rb +362 -0
  52. data/test/test_sdiff.rb +167 -0
  53. data/test/test_traverse_balanced.rb +322 -0
  54. data/test/test_traverse_sequences.rb +187 -0
  55. metadata +136 -105
  56. data/spec/fixtures/ldiff/output.diff-c +0 -7
  57. data/spec/fixtures/ldiff/output.diff-u +0 -5
  58. data/spec/fixtures/ldiff/output.diff.bin2 +0 -1
  59. data/spec/fixtures/ldiff/output.diff.bin2-c +0 -1
  60. data/spec/fixtures/ldiff/output.diff.bin2-e +0 -1
  61. data/spec/fixtures/ldiff/output.diff.bin2-f +0 -1
  62. data/spec/fixtures/ldiff/output.diff.bin2-u +0 -1
  63. data/spec/fixtures/ldiff/output.diff.chef-u +0 -9
  64. data/spec/fixtures/ldiff/output.diff.empty.vs.four_lines-c +0 -9
  65. data/spec/fixtures/ldiff/output.diff.empty.vs.four_lines-u +0 -7
  66. data/spec/fixtures/ldiff/output.diff.four_lines.vs.empty-c +0 -9
  67. data/spec/fixtures/ldiff/output.diff.four_lines.vs.empty-u +0 -7
  68. data/spec/fixtures/ldiff/output.diff.issue95_trailing_context-c +0 -9
  69. data/spec/fixtures/ldiff/output.diff.issue95_trailing_context-u +0 -6
  70. data/spec/fixtures/ldiff/output.diff.missing_new_line1-u +0 -9
  71. data/spec/fixtures/ldiff/output.diff.missing_new_line2-u +0 -9
  72. /data/{spec → test}/fixtures/123_x +0 -0
  73. /data/{spec → test}/fixtures/456_x +0 -0
  74. /data/{spec → test}/fixtures/aX +0 -0
  75. /data/{spec → test}/fixtures/bXaX +0 -0
  76. /data/{spec → test}/fixtures/ds1.csv +0 -0
  77. /data/{spec → test}/fixtures/ds2.csv +0 -0
  78. /data/{spec → test}/fixtures/empty +0 -0
  79. /data/{spec → test}/fixtures/file1.bin +0 -0
  80. /data/{spec → test}/fixtures/file2.bin +0 -0
  81. /data/{spec → test}/fixtures/four_lines +0 -0
  82. /data/{spec → test}/fixtures/four_lines_with_missing_new_line +0 -0
  83. /data/{spec → test}/fixtures/ldiff/diff.missing_new_line1-e +0 -0
  84. /data/{spec → test}/fixtures/ldiff/diff.missing_new_line1-f +0 -0
  85. /data/{spec → test}/fixtures/ldiff/diff.missing_new_line2-e +0 -0
  86. /data/{spec → test}/fixtures/ldiff/diff.missing_new_line2-f +0 -0
  87. /data/{spec → test}/fixtures/ldiff/error.diff.chef-e +0 -0
  88. /data/{spec → test}/fixtures/ldiff/error.diff.chef-f +0 -0
  89. /data/{spec → test}/fixtures/ldiff/error.diff.missing_new_line1-e +0 -0
  90. /data/{spec → test}/fixtures/ldiff/error.diff.missing_new_line1-f +0 -0
  91. /data/{spec → test}/fixtures/ldiff/error.diff.missing_new_line2-e +0 -0
  92. /data/{spec → test}/fixtures/ldiff/error.diff.missing_new_line2-f +0 -0
  93. /data/{spec → test}/fixtures/ldiff/output.diff +0 -0
  94. /data/{spec → test}/fixtures/ldiff/output.diff.bin1 +0 -0
  95. /data/{spec → test}/fixtures/ldiff/output.diff.bin1-c +0 -0
  96. /data/{spec → test}/fixtures/ldiff/output.diff.bin1-e +0 -0
  97. /data/{spec → test}/fixtures/ldiff/output.diff.bin1-f +0 -0
  98. /data/{spec → test}/fixtures/ldiff/output.diff.bin1-u +0 -0
  99. /data/{spec → test}/fixtures/ldiff/output.diff.chef +0 -0
  100. /data/{spec → test}/fixtures/ldiff/output.diff.chef2 +0 -0
  101. /data/{spec → test}/fixtures/ldiff/output.diff.chef2-d +0 -0
  102. /data/{spec → test}/fixtures/ldiff/output.diff.empty.vs.four_lines +0 -0
  103. /data/{spec → test}/fixtures/ldiff/output.diff.empty.vs.four_lines-e +0 -0
  104. /data/{spec → test}/fixtures/ldiff/output.diff.empty.vs.four_lines-f +0 -0
  105. /data/{spec → test}/fixtures/ldiff/output.diff.four_lines.vs.empty +0 -0
  106. /data/{spec → test}/fixtures/ldiff/output.diff.four_lines.vs.empty-e +0 -0
  107. /data/{spec → test}/fixtures/ldiff/output.diff.four_lines.vs.empty-f +0 -0
  108. /data/{spec → test}/fixtures/ldiff/output.diff.issue95_trailing_context +0 -0
  109. /data/{spec → test}/fixtures/ldiff/output.diff.issue95_trailing_context-e +0 -0
  110. /data/{spec → test}/fixtures/ldiff/output.diff.issue95_trailing_context-f +0 -0
  111. /data/{spec → test}/fixtures/ldiff/output.diff.missing_new_line1 +0 -0
  112. /data/{spec → test}/fixtures/ldiff/output.diff.missing_new_line1-e +0 -0
  113. /data/{spec → test}/fixtures/ldiff/output.diff.missing_new_line1-f +0 -0
  114. /data/{spec → test}/fixtures/ldiff/output.diff.missing_new_line2 +0 -0
  115. /data/{spec → test}/fixtures/ldiff/output.diff.missing_new_line2-e +0 -0
  116. /data/{spec → test}/fixtures/ldiff/output.diff.missing_new_line2-f +0 -0
  117. /data/{spec → test}/fixtures/new-chef +0 -0
  118. /data/{spec → test}/fixtures/new-chef2 +0 -0
  119. /data/{spec → test}/fixtures/old-chef +0 -0
  120. /data/{spec → test}/fixtures/old-chef2 +0 -0
data/README.md CHANGED
@@ -1,14 +1,12 @@
1
1
  # Diff::LCS
2
2
 
3
- - home :: https://github.com/halostatue/diff-lcs
4
- - changelog :: https://github.com/halostatue/diff-lcs/blob/main/CHANGELOG.md
5
- - code :: https://github.com/halostatue/diff-lcs
6
- - bugs :: https://github.com/halostatue/diff-lcs/issues
7
- - rdoc :: http://rubydoc.info/github/halostatue/diff-lcs
3
+ [![RubyGems Version][shield-gems]][rubygems] ![Coveralls][shield-coveralls]
4
+ [![Build Status][shield-ci]][ci-workflow]
8
5
 
9
- <a href="https://github.com/halostatue/diff-lcs/actions">
10
- <img src="https://github.com/halostatue/diff-lcs/workflows/CI/badge.svg" />
11
- </a>
6
+ - code :: <https://github.com/halostatue/diff-lcs>
7
+ - issues :: <https://github.com/halostatue/diff-lcs/issues>
8
+ - docs :: <https://halostatue.github.io/diff-lcs/>
9
+ - changelog :: <https://github.com/halostatue/diff-lcs/blob/main/CHANGELOG.md>
12
10
 
13
11
  ## Description
14
12
 
@@ -16,15 +14,23 @@ Diff::LCS computes the difference between two Enumerable sequences using the
16
14
  McIlroy-Hunt longest common subsequence (LCS) algorithm. It includes utilities
17
15
  to create a simple HTML diff output format and a standard diff-like tool.
18
16
 
19
- This is release 1.6.1, providing a simple extension that allows for
20
- Diff::LCS::Change objects to be treated implicitly as arrays and fixes a number
21
- of formatting issues.
17
+ This is release 2.0, which has significant breaking changes (removal of
18
+ deprecations and workarounds) and requires at least Ruby 3.2 or higher. See full
19
+ details in the `CHANGELOG`, but users of diff-lcs should not notice any
20
+ significant changes to the APIs.
22
21
 
23
- Ruby versions below 2.5 are soft-deprecated, which means that older versions are
24
- no longer part of the CI test suite. If any changes have been introduced that
25
- break those versions, bug reports and patches will be accepted, but it will be
26
- up to the reporter to verify any fixes prior to release. The next major release
27
- will completely break compatibility.
22
+ ### Performance and Compatibility
23
+
24
+ I have not run any benchmarks, but the use of immutable Data classes and the
25
+ removal of a number of inner loop conditionals related to encoding and String
26
+ handling (which have been unnecessary since at least Ruby 2.1 but kept for
27
+ strict compatibility) should allow better optimization by modern Ruby
28
+ implementations.
29
+
30
+ If you are using RSpec for your test suite, you are unlikely to be able to use
31
+ Diff::LCS 2.0 because of the minimum Ruby version unless the developers of RSpec
32
+ loosen their version constraints. I cannot control this and have raised
33
+ [rspec/rspec#290][rspec-issue-290].
28
34
 
29
35
  ## Synopsis
30
36
 
@@ -88,5 +94,12 @@ Subsequences</em>, CACM, vol.20, no.5, pp.350-353, May 1977, with a few minor
88
94
  improvements to improve the speed. A simplified description of the algorithm,
89
95
  originally written for the Perl version, was written by Mark-Jason Dominus.
90
96
 
97
+ [ci-workflow]: https://github.com/halostatue/diff-lcs/actions/workflows/ci.yml
98
+ [coveralls]: https://coveralls.io/github/halostatue/diff-lcs?branch=main
99
+ [perl]: https://search.cpan.org/~nedkonz/Algorithm-Diff-1.15/
100
+ [rspec-issue-290]: https://github.com/rspec/rspec/issues/290
101
+ [rubygems]: https://rubygems.org/gems/diff-lcs
102
+ [shield-ci]: https://img.shields.io/github/actions/workflow/status/halostatue/diff-lcs/ci.yml?style=for-the-badge "Build Status"
103
+ [shield-coveralls]: https://img.shields.io/coverallsCoverage/github/halostatue/diff-lcs?style=for-the-badge
104
+ [shield-gems]: https://img.shields.io/gem/v/diff-lcs?style=for-the-badge "Version"
91
105
  [smalltalk]: ftp://st.cs.uiuc.edu/pub/Smalltalk/MANCHESTER/manchester/4.0/diff.st
92
- [perl]: http://search.cpan.org/~nedkonz/Algorithm-Diff-1.15/
data/Rakefile CHANGED
@@ -1,17 +1,16 @@
1
1
  require "rubygems"
2
- require "rspec"
3
- require "rspec/core/rake_task"
4
2
  require "hoe"
5
3
  require "rake/clean"
6
4
  require "rdoc/task"
5
+ require "minitest/test_task"
7
6
 
8
7
  Hoe.plugin :halostatue
9
- Hoe.plugin :rubygems
10
8
 
11
9
  Hoe.plugins.delete :debug
12
10
  Hoe.plugins.delete :newb
13
11
  Hoe.plugins.delete :publish
14
12
  Hoe.plugins.delete :signing
13
+ Hoe.plugins.delete :test
15
14
 
16
15
  hoe = Hoe.spec "diff-lcs" do
17
16
  developer("Austin Ziegler", "halostatue@gmail.com")
@@ -27,10 +26,12 @@ hoe = Hoe.spec "diff-lcs" do
27
26
  }
28
27
 
29
28
  extra_dev_deps << ["hoe", "~> 4.0"]
30
- extra_dev_deps << ["hoe-halostatue", "~> 2.1", ">= 2.1.1"]
31
- extra_dev_deps << ["rspec", ">= 2.0", "< 4"]
29
+ extra_dev_deps << ["hoe-halostatue", "~> 3.0"]
30
+ extra_dev_deps << ["minitest", "~> 6.0"]
31
+ extra_dev_deps << ["minitest-autotest", "~> 1.0"]
32
+ extra_dev_deps << ["minitest-focus", "~> 1.1"]
32
33
  extra_dev_deps << ["rake", ">= 10.0", "< 14"]
33
- extra_dev_deps << ["rdoc", ">= 6.3.1", "< 7"]
34
+ extra_dev_deps << ["rdoc", ">= 6.0", "< 8"]
34
35
  extra_dev_deps << ["simplecov", "~> 0.9"]
35
36
  extra_dev_deps << ["simplecov-lcov", "~> 0.9"]
36
37
  extra_dev_deps << ["standard", "~> 1.50"]
@@ -38,24 +39,33 @@ hoe = Hoe.spec "diff-lcs" do
38
39
  extra_dev_deps << ["fasterer", "~> 0.11"]
39
40
  end
40
41
 
41
- desc "Run all specifications"
42
- RSpec::Core::RakeTask.new(:spec) do |t|
43
- rspec_dirs = %w[spec lib].join(":")
44
- t.rspec_opts = ["-I#{rspec_dirs}"]
45
- end
42
+ Minitest::TestTask.create :test
43
+ Minitest::TestTask.create :coverage do |t|
44
+ formatters = <<-RUBY.split($/).join(" ")
45
+ SimpleCov::Formatter::MultiFormatter.new([
46
+ SimpleCov::Formatter::HTMLFormatter,
47
+ SimpleCov::Formatter::LcovFormatter,
48
+ SimpleCov::Formatter::SimpleFormatter
49
+ ])
50
+ RUBY
51
+ t.test_prelude = <<-RUBY.split($/).join("; ")
52
+ require "simplecov"
53
+ require "simplecov-lcov"
46
54
 
47
- namespace :spec do
48
- desc "Runs test coverage. Only works Ruby 2.0+ and assumes 'simplecov' is installed."
49
- task :coverage do
50
- Rake::Task["spec"].execute
55
+ SimpleCov::Formatter::LcovFormatter.config do |config|
56
+ config.report_with_single_file = true
57
+ config.lcov_file_name = "lcov.info"
51
58
  end
52
- end
53
59
 
54
- task coverage: "spec:coverage"
55
- Rake::Task["spec"].actions.uniq! { |a| a.source_location }
60
+ SimpleCov.start "test_frameworks" do
61
+ enable_coverage :branch
62
+ primary_coverage :branch
63
+ formatter #{formatters}
64
+ end
65
+ RUBY
66
+ end
56
67
 
57
- task default: :spec unless Rake::Task["default"].prereqs.include?("spec")
58
- task test: :spec unless Rake::Task["test"].prereqs.include?("spec")
68
+ task default: :test
59
69
 
60
70
  task :version do
61
71
  require "diff/lcs/version"
@@ -64,10 +74,68 @@ end
64
74
 
65
75
  RDoc::Task.new do |config|
66
76
  config.title = "diff-lcs"
67
- # config.main = "lib/diff/lcs.rb"
68
77
  config.main = "README.md"
69
78
  config.rdoc_dir = "doc"
70
- config.rdoc_files = hoe.spec.require_paths - ["Manifest.txt"] + hoe.spec.extra_rdoc_files
79
+ config.rdoc_files = hoe.spec.require_paths + hoe.spec.extra_rdoc_files -
80
+ FileList["integration/golden/*.txt", "Manifest.txt"].to_a
71
81
  config.markup = "markdown"
72
82
  end
73
83
  task docs: :rerdoc
84
+
85
+ def rspec_to_golden(file)
86
+ File.join("integration/golden", File.basename(file, "_spec.rb")) + ".txt"
87
+ end
88
+
89
+ def normalize_rspec_output(data)
90
+ data
91
+ .gsub(/Randomized with seed \d+/, "Randomized with seed XXXXX")
92
+ .gsub(/Finished in [\d.]+ seconds/, "Finished in X.XXXXX seconds")
93
+ .gsub(/files took [\d.]+ seconds to load/, "files took X.XXXXX seconds to load")
94
+ end
95
+
96
+ def unbundled(&block)
97
+ if defined?(Bundler)
98
+ Bundler.with_unbundled_env(&block)
99
+ else
100
+ block.call
101
+ end
102
+ end
103
+
104
+ rspecs = FileList["integration/compare/*_spec.rb"]
105
+
106
+ namespace :integration do
107
+ desc "Compare RSpec output with and without diff-lcs 2"
108
+ task :compare do
109
+ require "tempfile"
110
+ base = Tempfile.create("baseline") { _1.path }
111
+ work = Tempfile.create("working") { _1.path }
112
+
113
+ unbundled { sh "gem install rspec" }
114
+
115
+ rspecs.to_a.each do |rspec_file|
116
+ basename = File.basename(rspec_file, "_spec.rb")
117
+
118
+ base_contents = unbundled { `integration/runner rspec #{rspec_file} 2>&1` }
119
+ base_contents = normalize_rspec_output(base_contents)
120
+
121
+ work_contents = unbundled { `integration/runner rspec -Ilib -rdiff/lcs #{rspec_file} 2>&1` }
122
+ work_contents = normalize_rspec_output(work_contents)
123
+
124
+ if base_contents == work_contents
125
+ puts "#{basename}: OK"
126
+ else
127
+ puts "#{basename}: FAIL"
128
+
129
+ File.write(base, base_contents)
130
+ File.write(work, work_contents)
131
+
132
+ unbundled { sh "integration/runner -Ilib bin/ldiff -U #{base} #{work}" }
133
+ end
134
+ end
135
+ end
136
+ end
137
+
138
+ desc "Run RSpec integration tests with diff-lcs 2.0"
139
+ task integration: ["integration:compare"] do
140
+ sh "rspec -Ilib -r diff/lcs integration/*_spec.rb"
141
+ end
data/SECURITY.md CHANGED
@@ -22,24 +22,15 @@ All issues raised must be demonstrated on the minimum supported Ruby version.
22
22
  >
23
23
  > | Version | Release Date | Support Ends | Security Support Ends |
24
24
  > | ------- | ------------ | ------------ | --------------------- |
25
- > | 1.x | 2010 | 2026-04-30 | 2027-01-31 |
26
- > | 2.x | 2026-01-31 | - | - |
25
+ > | 1.x | 2010 | 2026-05-01 | 2027-02-01 |
26
+ > | 2.x | 2026-02-01 | - | - |
27
27
 
28
28
  ## Reporting a Vulnerability
29
29
 
30
30
  Report vulnerabilities via the [Tidelift security contact][tidelift]. Tidelift
31
31
  will coordinate the fix and disclosure.
32
32
 
33
- Alternatively, create a [private vulnerability report][advisory] with GitHub or
34
- send an email to [security@ruby.halostatue.ca][email] with the text `diff-lcs`
35
- in the subject. Emails sent to this address should be encrypted using [age][age]
36
- with the following public key:
37
-
38
- ```
39
- age1fc6ngxmn02m62fej5cl30lrvwmxn4k3q2atqu53aatekmnqfwumqj4g93w
40
- ```
33
+ Alternatively, create a [private vulnerability report][advisory] with GitHub.
41
34
 
42
35
  [advisory]: https://github.com/halostatue/diff-lcs/security/advisories/new
43
- [age]: https://github.com/FiloSottile/age
44
- [email]: mailto:security@ruby.halostatue.ca
45
36
  [tidelift]: https://tidelift.com/security
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe "Array diff failure" do
4
+ it "shows diff for array with different elements" do
5
+ expected = ["apple", "banana", "cherry", "date", "elderberry"]
6
+ actual = ["apple", "blueberry", "cherry", "date", "elderberry"]
7
+
8
+ expect(actual).to eq(expected)
9
+ end
10
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe "Hash diff failure" do
4
+ it "shows diff for nested hash mismatch" do
5
+ expected = {
6
+ name: "John",
7
+ age: 30,
8
+ address: {
9
+ city: "New York",
10
+ zip: "10001"
11
+ }
12
+ }
13
+
14
+ actual = {
15
+ name: "John",
16
+ age: 35,
17
+ address: {
18
+ city: "Boston",
19
+ zip: "10001"
20
+ }
21
+ }
22
+
23
+ expect(actual).to eq(expected)
24
+ end
25
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe "String diff failure" do
4
+ it "shows diff for multiline string mismatch" do
5
+ expected = "line1\nline2\nline3\nline4\nline5\n"
6
+ actual = "line1\nchanged\nline3\nline4\nline5\n"
7
+
8
+ expect(actual).to eq(expected)
9
+ end
10
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Integration test to verify diff-lcs 2.0 works with RSpec's differ
4
+ # This runs RSpec with diff-lcs 1.x installed but loads the repo version
5
+
6
+ RSpec.describe "Diff::LCS 2.0 with RSpec::Support::Differ" do
7
+ let(:differ) { RSpec::Support::Differ.new }
8
+
9
+ it "produces diff output for multiline strings" do
10
+ expected = "foo\nzap\nbar\n"
11
+ actual = "foo\nbar\nzap\n"
12
+
13
+ diff = differ.diff(actual, expected)
14
+
15
+ expect(diff).to be_a(String)
16
+ expect(diff).not_to be_empty
17
+ end
18
+
19
+ it "handles identical strings" do
20
+ str = "same\n"
21
+ diff = differ.diff(str, str)
22
+
23
+ # May return empty or just newline depending on implementation
24
+ expect(diff.strip).to be_empty
25
+ end
26
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Integration test for RSpec expectation failures that use diff-lcs
4
+ # Verifies that diff output is generated correctly with diff-lcs 2.0
5
+
6
+ RSpec.describe "Diff::LCS 2.0 with RSpec expectations" do
7
+ it "produces diff for failed multiline string equality" do
8
+ expect {
9
+ expect("foo\nbar\nbaz").to eq("foo\nqux\nbaz")
10
+ }.to raise_error(RSpec::Expectations::ExpectationNotMetError) do |error|
11
+ expect(error.message).to include("Diff:")
12
+ expect(error.message).to match(/[-+](qux|bar)/)
13
+ end
14
+ end
15
+
16
+ it "produces diff for failed hash equality" do
17
+ expect {
18
+ expect({a: 1, b: 2}).to eq({a: 1, b: 3})
19
+ }.to raise_error(RSpec::Expectations::ExpectationNotMetError) do |error|
20
+ expect(error.message).to include("Diff:")
21
+ end
22
+ end
23
+
24
+ it "does not crash when comparing complex objects" do
25
+ obj1 = {a: [1, 2, 3], b: "test"}
26
+ obj2 = {a: [1, 4, 3], b: "test"}
27
+
28
+ expect {
29
+ expect(obj1).to eq(obj2)
30
+ }.to raise_error(RSpec::Expectations::ExpectationNotMetError)
31
+ end
32
+ end
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "rbconfig"
4
+ require "rubygems"
5
+
6
+ if ENV["CI"]
7
+ vendor_dir = Dir.glob(File.join(Dir.pwd, "vendor", "bundle", "{ruby,jruby,truffleruby}", "*")).max
8
+ unless vendor_dir && Dir.exist?(vendor_dir)
9
+ fail "vendor bundle not found; expected vendor/bundle/ruby/*"
10
+ end
11
+
12
+ # Prefer vendored gems, fall back to system gem dir
13
+ ENV["GEM_HOME"] = vendor_dir
14
+ ENV["GEM_PATH"] = "#{vendor_dir}:#{Gem.default_dir}"
15
+
16
+ # Ensure Gem.path is updated for the running process (affects Gem.lookup)
17
+ Gem.use_paths(ENV["GEM_HOME"], ENV["GEM_PATH"].split(":"))
18
+ end
19
+
20
+ exec RbConfig.ruby, "-S", *ARGV
@@ -10,7 +10,7 @@ class Diff::LCS::Change
10
10
  # (changed), '<' (tail changes from first sequence), or '>' (tail changes from second
11
11
  # sequence). The last two ('<>') are only found with Diff::LCS::diff and
12
12
  # Diff::LCS::sdiff.
13
- VALID_ACTIONS = %w[+ - = ! > <].freeze
13
+ VALID_ACTIONS = %w[= - + ! > <].freeze
14
14
 
15
15
  def self.valid_action?(action) = VALID_ACTIONS.include?(action)
16
16
 
@@ -37,6 +37,9 @@ class Diff::LCS::Change
37
37
 
38
38
  def to_a = [action, position, element]
39
39
  alias_method :to_ary, :to_a
40
+ alias_method :deconstruct, :to_a
41
+
42
+ def deconstruct_keys(_) = {action:, position:, element:}
40
43
 
41
44
  def self.from_a(arr)
42
45
  case arr
@@ -59,8 +62,8 @@ class Diff::LCS::Change
59
62
  end
60
63
 
61
64
  def <=>(other)
62
- r = action <=> other.action
63
- r = position <=> other.position if r.zero?
65
+ r = position <=> other.position
66
+ r = VALID_ACTIONS.index(action) <=> VALID_ACTIONS.index(other.action) if r.zero?
64
67
  r = element <=> other.element if r.zero?
65
68
  r
66
69
  end
@@ -111,28 +114,27 @@ class Diff::LCS::ContextChange
111
114
 
112
115
  def to_a = [action, [old_position, old_element], [new_position, new_element]]
113
116
  alias_method :to_ary, :to_a
117
+ alias_method :deconstruct, :to_a
118
+
119
+ def deconstruct_keys(_) = {action:, old_position:, old_element:, new_position:, new_element:}
114
120
 
115
121
  def self.from_a(arr) = Diff::LCS::Change.from_a(arr)
116
122
 
117
123
  # Simplifies a context change for use in some diff callbacks. '<' actions are converted
118
124
  # to '-' and '>' actions are converted to '+'.
119
125
  def self.simplify(event)
120
- ea = event.to_a
121
-
122
- case ea[0]
126
+ case event.action
123
127
  when "-"
124
- ea[2][1] = nil
128
+ event.with(new_element: nil)
125
129
  when "<"
126
- ea[0] = "-"
127
- ea[2][1] = nil
130
+ event.with(action: "-", new_element: nil)
128
131
  when "+"
129
- ea[1][1] = nil
132
+ event.with(old_element: nil)
130
133
  when ">"
131
- ea[0] = "+"
132
- ea[1][1] = nil
134
+ event.with(action: "+", old_element: nil)
135
+ else
136
+ event
133
137
  end
134
-
135
- from_a(ea)
136
138
  end
137
139
 
138
140
  def ==(other)
@@ -145,9 +147,12 @@ class Diff::LCS::ContextChange
145
147
  end
146
148
 
147
149
  def <=>(other)
148
- r = action <=> other.action
149
- r = old_position <=> other.old_position if r.zero?
150
+ r = old_position <=> other.old_position
150
151
  r = new_position <=> other.new_position if r.zero?
152
+ if r.zero?
153
+ r = Diff::LCS::Change::VALID_ACTIONS.index(action) <=>
154
+ Diff::LCS::Change::VALID_ACTIONS.index(other.action)
155
+ end
151
156
  r = old_element <=> other.old_element if r.zero?
152
157
  r = new_element <=> other.new_element if r.zero?
153
158
  r
@@ -16,9 +16,14 @@ class Diff::LCS::Ldiff # :nodoc:
16
16
  MIT licence.
17
17
  COPYRIGHT
18
18
 
19
- InputInfo = Struct.new(:filename, :data, :stat) do
19
+ InputInfo = Struct.new(:filename, :data, :mtime) do
20
20
  def initialize(filename)
21
- super(filename, ::File.read(filename), ::File.stat(filename))
21
+ if filename == "-"
22
+ super("<stdin>", $stdin.read, Time.now)
23
+ return
24
+ end
25
+
26
+ super(filename, ::File.read(filename), ::File.stat(filename).mtime)
22
27
  end
23
28
  end
24
29
 
@@ -142,9 +147,9 @@ class Diff::LCS::Ldiff # :nodoc:
142
147
  output << "Files #{info_old.filename} and #{info_new.filename} differ\n"
143
148
  return true
144
149
  when :unified, :context
145
- ft = info_old.stat.mtime.localtime.strftime("%Y-%m-%d %H:%M:%S.000000000 %z")
150
+ ft = info_old.mtime.localtime.strftime("%Y-%m-%d %H:%M:%S.000000000 %z")
146
151
  output << "#{char_old} #{info_old.filename}\t#{ft}\n"
147
- ft = info_new.stat.mtime.localtime.strftime("%Y-%m-%d %H:%M:%S.000000000 %z")
152
+ ft = info_new.mtime.localtime.strftime("%Y-%m-%d %H:%M:%S.000000000 %z")
148
153
  output << "#{char_new} #{info_new.filename}\t#{ft}\n"
149
154
  when :ed
150
155
  output = []
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Diff
4
4
  module LCS
5
- VERSION = "2.0.0.beta.1"
5
+ VERSION = "2.0.0"
6
6
  end
7
7
  end
data/spec/hunk_spec.rb CHANGED
@@ -2,71 +2,69 @@
2
2
 
3
3
  require "spec_helper"
4
4
 
5
- if String.method_defined?(:encoding)
6
- require "diff/lcs/hunk"
5
+ require "diff/lcs/hunk"
7
6
 
8
- describe Diff::LCS::Hunk do
9
- let(:old_data) { ["Tu a un carté avec {count} itéms".encode("UTF-16LE")] }
10
- let(:new_data) { ["Tu a un carte avec {count} items".encode("UTF-16LE")] }
11
- let(:pieces) { Diff::LCS.diff old_data, new_data }
12
- let(:hunk) { Diff::LCS::Hunk.new(old_data, new_data, pieces[0], 3, 0) }
7
+ describe Diff::LCS::Hunk do
8
+ let(:old_data) { ["Tu a un carté avec {count} itéms".encode("UTF-16LE")] }
9
+ let(:new_data) { ["Tu a un carte avec {count} items".encode("UTF-16LE")] }
10
+ let(:pieces) { Diff::LCS.diff old_data, new_data }
11
+ let(:hunk) { Diff::LCS::Hunk.new(old_data, new_data, pieces[0], 3, 0) }
13
12
 
14
- it "produces a unified diff from the two pieces" do
15
- expected = <<-EXPECTED.gsub(/^\s+/, "").encode("UTF-16LE").chomp
13
+ it "produces a unified diff from the two pieces" do
14
+ expected = <<-EXPECTED.gsub(/^\s+/, "").encode("UTF-16LE").chomp
16
15
  @@ -1 +1 @@
17
16
  -Tu a un carté avec {count} itéms
18
17
  +Tu a un carte avec {count} items
19
- EXPECTED
18
+ EXPECTED
20
19
 
21
- expect(hunk.diff(:unified)).to eq(expected)
22
- end
20
+ expect(hunk.diff(:unified)).to eq(expected)
21
+ end
23
22
 
24
- it "produces a unified diff from the two pieces (last entry)" do
25
- expected = <<-EXPECTED.gsub(/^\s+/, "").encode("UTF-16LE").chomp
23
+ it "produces a unified diff from the two pieces (last entry)" do
24
+ expected = <<-EXPECTED.gsub(/^\s+/, "").encode("UTF-16LE").chomp
26
25
  @@ -1 +1 @@
27
26
  -Tu a un carté avec {count} itéms
28
27
  +Tu a un carte avec {count} items
29
28
  \
30
- EXPECTED
29
+ EXPECTED
31
30
 
32
- expect(hunk.diff(:unified, true)).to eq(expected)
33
- end
31
+ expect(hunk.diff(:unified, true)).to eq(expected)
32
+ end
34
33
 
35
- it "produces a context diff from the two pieces" do
36
- expected = <<-EXPECTED.gsub(/^\s+/, "").encode("UTF-16LE").chomp
34
+ it "produces a context diff from the two pieces" do
35
+ expected = <<-EXPECTED.gsub(/^\s+/, "").encode("UTF-16LE").chomp
37
36
  ***************
38
37
  *** 1 ****
39
38
  ! Tu a un carté avec {count} itéms
40
39
  --- 1 ----
41
40
  ! Tu a un carte avec {count} items
42
- EXPECTED
41
+ EXPECTED
43
42
 
44
- expect(hunk.diff(:context)).to eq(expected)
45
- end
43
+ expect(hunk.diff(:context)).to eq(expected)
44
+ end
46
45
 
47
- it "produces an old diff from the two pieces" do
48
- expected = <<-EXPECTED.gsub(/^ +/, "").encode("UTF-16LE").chomp
46
+ it "produces an old diff from the two pieces" do
47
+ expected = <<-EXPECTED.gsub(/^ +/, "").encode("UTF-16LE").chomp
49
48
  1c1
50
49
  < Tu a un carté avec {count} itéms
51
50
  ---
52
51
  > Tu a un carte avec {count} items
53
52
 
54
- EXPECTED
53
+ EXPECTED
55
54
 
56
- expect(hunk.diff(:old)).to eq(expected)
57
- end
55
+ expect(hunk.diff(:old)).to eq(expected)
56
+ end
58
57
 
59
- context "with empty first data set" do
60
- let(:old_data) { [] }
58
+ context "with empty first data set" do
59
+ let(:old_data) { [] }
61
60
 
62
- it "produces a unified diff" do
63
- expected = <<-EXPECTED.gsub(/^\s+/, "").encode("UTF-16LE").chomp
61
+ it "produces a unified diff" do
62
+ expected = <<-EXPECTED.gsub(/^\s+/, "").encode("UTF-16LE").chomp
64
63
  @@ -0,0 +1 @@
65
64
  +Tu a un carte avec {count} items
66
- EXPECTED
65
+ EXPECTED
67
66
 
68
- expect(hunk.diff(:unified)).to eq(expected)
69
- end
67
+ expect(hunk.diff(:unified)).to eq(expected)
70
68
  end
71
69
  end
72
70
  end
data/spec/ldiff_spec.rb CHANGED
@@ -27,11 +27,11 @@ RSpec.describe "bin/ldiff" do
27
27
  def self.test_ldiff(fixture)
28
28
  desc = [
29
29
  fixture[:flag],
30
- "spec/fixtures/#{fixture[:left]}",
31
- "spec/fixtures/#{fixture[:right]}",
30
+ "test/fixtures/#{fixture[:left]}",
31
+ "test/fixtures/#{fixture[:right]}",
32
32
  "#",
33
33
  "=>",
34
- "spec/fixtures/ldiff/output.#{fixture[:name]}#{fixture[:flag]}"
34
+ "test/fixtures/ldiff/output.#{fixture[:name]}#{fixture[:flag]}"
35
35
  ].join(" ")
36
36
 
37
37
  it desc do
@@ -49,7 +49,7 @@ RSpec.describe "bin/ldiff" do
49
49
  def read_fixture(options, mode: "output", allow_missing: false)
50
50
  fixture = options.fetch(:name)
51
51
  flag = options.fetch(:flag)
52
- name = "spec/fixtures/ldiff/#{mode}.#{fixture}#{flag}"
52
+ name = "test/fixtures/ldiff/#{mode}.#{fixture}#{flag}"
53
53
 
54
54
  return "" if !::File.exist?(name) && allow_missing
55
55
 
@@ -74,7 +74,7 @@ RSpec.describe "bin/ldiff" do
74
74
  ^
75
75
  [-+*]{3}
76
76
  \s*
77
- spec/fixtures/(\S+)
77
+ test/fixtures/(\S+)
78
78
  \s*
79
79
  \d{4}-\d\d-\d\d
80
80
  \s*
@@ -82,7 +82,7 @@ RSpec.describe "bin/ldiff" do
82
82
  \s*
83
83
  (?:[-+]\d{4}|Z)
84
84
  }x,
85
- '*** spec/fixtures/\1 0000-00-00 :00 =>:00 =>00.000000000 -0000'
85
+ '*** test/fixtures/\1 0000-00-00 :00 =>:00 =>00.000000000 -0000'
86
86
  )
87
87
  end
88
88
 
@@ -92,7 +92,7 @@ RSpec.describe "bin/ldiff" do
92
92
  right = options.fetch(:right)
93
93
 
94
94
  stdout, stderr = capture_subprocess_io do
95
- system("ruby -Ilib bin/ldiff #{flag} spec/fixtures/#{left} spec/fixtures/#{right}")
95
+ system("ruby -Ilib bin/ldiff #{flag} test/fixtures/#{left} test/fixtures/#{right}")
96
96
  end
97
97
 
98
98
  [clean_data(stdout, flag), stderr, $?.exitstatus]