diff-lcs 1.6.2 → 2.0.0.beta.2

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 (140) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +65 -4
  3. data/CONTRIBUTING.md +91 -35
  4. data/CONTRIBUTORS.md +19 -9
  5. data/LICENCE.md +39 -11
  6. data/Manifest.txt +91 -83
  7. data/README.md +13 -9
  8. data/Rakefile +99 -73
  9. data/SECURITY.md +22 -27
  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/block.rb +29 -24
  17. data/lib/diff/lcs/callbacks.rb +240 -242
  18. data/lib/diff/lcs/change.rb +102 -104
  19. data/lib/diff/lcs/hunk.rb +92 -155
  20. data/lib/diff/lcs/internals.rb +92 -96
  21. data/lib/diff/lcs/ldiff.rb +30 -38
  22. data/lib/diff/lcs/version.rb +1 -1
  23. data/lib/diff/lcs.rb +439 -466
  24. data/licenses/dco.txt +34 -0
  25. data/spec/hunk_spec.rb +32 -45
  26. data/spec/lcs_spec.rb +6 -6
  27. data/spec/ldiff_spec.rb +8 -8
  28. data/spec/spec_helper.rb +17 -27
  29. data/test/fixtures/ldiff/output.diff-c +7 -0
  30. data/test/fixtures/ldiff/output.diff-u +5 -0
  31. data/test/fixtures/ldiff/output.diff.bin2 +1 -0
  32. data/test/fixtures/ldiff/output.diff.bin2-c +1 -0
  33. data/test/fixtures/ldiff/output.diff.bin2-e +1 -0
  34. data/test/fixtures/ldiff/output.diff.bin2-f +1 -0
  35. data/test/fixtures/ldiff/output.diff.bin2-u +1 -0
  36. data/{spec → test}/fixtures/ldiff/output.diff.chef-c +2 -2
  37. data/test/fixtures/ldiff/output.diff.chef-u +9 -0
  38. data/{spec → test}/fixtures/ldiff/output.diff.chef2-c +2 -2
  39. data/{spec → test}/fixtures/ldiff/output.diff.chef2-u +2 -2
  40. data/test/fixtures/ldiff/output.diff.empty.vs.four_lines-c +9 -0
  41. data/test/fixtures/ldiff/output.diff.empty.vs.four_lines-u +7 -0
  42. data/test/fixtures/ldiff/output.diff.four_lines.vs.empty-c +9 -0
  43. data/test/fixtures/ldiff/output.diff.four_lines.vs.empty-u +7 -0
  44. data/test/fixtures/ldiff/output.diff.issue95_trailing_context-c +9 -0
  45. data/test/fixtures/ldiff/output.diff.issue95_trailing_context-u +6 -0
  46. data/{spec → test}/fixtures/ldiff/output.diff.missing_new_line1-c +2 -2
  47. data/test/fixtures/ldiff/output.diff.missing_new_line1-u +9 -0
  48. data/{spec → test}/fixtures/ldiff/output.diff.missing_new_line2-c +2 -2
  49. data/test/fixtures/ldiff/output.diff.missing_new_line2-u +9 -0
  50. data/test/test_block.rb +34 -0
  51. data/test/test_change.rb +234 -0
  52. data/test/test_diff.rb +53 -0
  53. data/test/test_helper.rb +225 -0
  54. data/test/test_hunk.rb +72 -0
  55. data/test/test_issues.rb +168 -0
  56. data/test/test_lcs.rb +47 -0
  57. data/test/test_ldiff.rb +89 -0
  58. data/test/test_patch.rb +362 -0
  59. data/test/test_sdiff.rb +167 -0
  60. data/test/test_traverse_balanced.rb +322 -0
  61. data/test/test_traverse_sequences.rb +187 -0
  62. metadata +199 -110
  63. data/.rspec +0 -1
  64. data/bin/htmldiff +0 -35
  65. data/lib/diff/lcs/backports.rb +0 -13
  66. data/lib/diff/lcs/htmldiff.rb +0 -160
  67. data/mise.toml +0 -5
  68. data/spec/fixtures/ldiff/output.diff-c +0 -7
  69. data/spec/fixtures/ldiff/output.diff-e +0 -3
  70. data/spec/fixtures/ldiff/output.diff-f +0 -3
  71. data/spec/fixtures/ldiff/output.diff-u +0 -5
  72. data/spec/fixtures/ldiff/output.diff.bin2 +0 -1
  73. data/spec/fixtures/ldiff/output.diff.bin2-c +0 -1
  74. data/spec/fixtures/ldiff/output.diff.bin2-e +0 -1
  75. data/spec/fixtures/ldiff/output.diff.bin2-f +0 -1
  76. data/spec/fixtures/ldiff/output.diff.bin2-u +0 -1
  77. data/spec/fixtures/ldiff/output.diff.chef-e +0 -3
  78. data/spec/fixtures/ldiff/output.diff.chef-f +0 -3
  79. data/spec/fixtures/ldiff/output.diff.chef-u +0 -9
  80. data/spec/fixtures/ldiff/output.diff.chef2-e +0 -7
  81. data/spec/fixtures/ldiff/output.diff.chef2-f +0 -7
  82. data/spec/fixtures/ldiff/output.diff.empty.vs.four_lines-c +0 -9
  83. data/spec/fixtures/ldiff/output.diff.empty.vs.four_lines-u +0 -7
  84. data/spec/fixtures/ldiff/output.diff.four_lines.vs.empty-c +0 -9
  85. data/spec/fixtures/ldiff/output.diff.four_lines.vs.empty-u +0 -7
  86. data/spec/fixtures/ldiff/output.diff.issue95_trailing_context-c +0 -9
  87. data/spec/fixtures/ldiff/output.diff.issue95_trailing_context-u +0 -6
  88. data/spec/fixtures/ldiff/output.diff.missing_new_line1-u +0 -9
  89. data/spec/fixtures/ldiff/output.diff.missing_new_line2-u +0 -9
  90. /data/{docs → licenses}/COPYING.txt +0 -0
  91. /data/{docs → licenses}/artistic.txt +0 -0
  92. /data/{spec → test}/fixtures/123_x +0 -0
  93. /data/{spec → test}/fixtures/456_x +0 -0
  94. /data/{spec → test}/fixtures/aX +0 -0
  95. /data/{spec → test}/fixtures/bXaX +0 -0
  96. /data/{spec → test}/fixtures/ds1.csv +0 -0
  97. /data/{spec → test}/fixtures/ds2.csv +0 -0
  98. /data/{spec → test}/fixtures/empty +0 -0
  99. /data/{spec → test}/fixtures/file1.bin +0 -0
  100. /data/{spec → test}/fixtures/file2.bin +0 -0
  101. /data/{spec → test}/fixtures/four_lines +0 -0
  102. /data/{spec → test}/fixtures/four_lines_with_missing_new_line +0 -0
  103. /data/{spec → test}/fixtures/ldiff/diff.missing_new_line1-e +0 -0
  104. /data/{spec → test}/fixtures/ldiff/diff.missing_new_line1-f +0 -0
  105. /data/{spec → test}/fixtures/ldiff/diff.missing_new_line2-e +0 -0
  106. /data/{spec → test}/fixtures/ldiff/diff.missing_new_line2-f +0 -0
  107. /data/{spec → test}/fixtures/ldiff/error.diff.chef-e +0 -0
  108. /data/{spec → test}/fixtures/ldiff/error.diff.chef-f +0 -0
  109. /data/{spec → test}/fixtures/ldiff/error.diff.missing_new_line1-e +0 -0
  110. /data/{spec → test}/fixtures/ldiff/error.diff.missing_new_line1-f +0 -0
  111. /data/{spec → test}/fixtures/ldiff/error.diff.missing_new_line2-e +0 -0
  112. /data/{spec → test}/fixtures/ldiff/error.diff.missing_new_line2-f +0 -0
  113. /data/{spec → test}/fixtures/ldiff/output.diff +0 -0
  114. /data/{spec → test}/fixtures/ldiff/output.diff.bin1 +0 -0
  115. /data/{spec → test}/fixtures/ldiff/output.diff.bin1-c +0 -0
  116. /data/{spec → test}/fixtures/ldiff/output.diff.bin1-e +0 -0
  117. /data/{spec → test}/fixtures/ldiff/output.diff.bin1-f +0 -0
  118. /data/{spec → test}/fixtures/ldiff/output.diff.bin1-u +0 -0
  119. /data/{spec → test}/fixtures/ldiff/output.diff.chef +0 -0
  120. /data/{spec → test}/fixtures/ldiff/output.diff.chef2 +0 -0
  121. /data/{spec → test}/fixtures/ldiff/output.diff.chef2-d +0 -0
  122. /data/{spec → test}/fixtures/ldiff/output.diff.empty.vs.four_lines +0 -0
  123. /data/{spec → test}/fixtures/ldiff/output.diff.empty.vs.four_lines-e +0 -0
  124. /data/{spec → test}/fixtures/ldiff/output.diff.empty.vs.four_lines-f +0 -0
  125. /data/{spec → test}/fixtures/ldiff/output.diff.four_lines.vs.empty +0 -0
  126. /data/{spec → test}/fixtures/ldiff/output.diff.four_lines.vs.empty-e +0 -0
  127. /data/{spec → test}/fixtures/ldiff/output.diff.four_lines.vs.empty-f +0 -0
  128. /data/{spec → test}/fixtures/ldiff/output.diff.issue95_trailing_context +0 -0
  129. /data/{spec → test}/fixtures/ldiff/output.diff.issue95_trailing_context-e +0 -0
  130. /data/{spec → test}/fixtures/ldiff/output.diff.issue95_trailing_context-f +0 -0
  131. /data/{spec → test}/fixtures/ldiff/output.diff.missing_new_line1 +0 -0
  132. /data/{spec → test}/fixtures/ldiff/output.diff.missing_new_line1-e +0 -0
  133. /data/{spec → test}/fixtures/ldiff/output.diff.missing_new_line1-f +0 -0
  134. /data/{spec → test}/fixtures/ldiff/output.diff.missing_new_line2 +0 -0
  135. /data/{spec → test}/fixtures/ldiff/output.diff.missing_new_line2-e +0 -0
  136. /data/{spec → test}/fixtures/ldiff/output.diff.missing_new_line2-f +0 -0
  137. /data/{spec → test}/fixtures/new-chef +0 -0
  138. /data/{spec → test}/fixtures/new-chef2 +0 -0
  139. /data/{spec → test}/fixtures/old-chef +0 -0
  140. /data/{spec → test}/fixtures/old-chef2 +0 -0
data/Rakefile CHANGED
@@ -1,52 +1,24 @@
1
1
  require "rubygems"
2
- require "rspec"
3
- require "rspec/core/rake_task"
4
2
  require "hoe"
5
3
  require "rake/clean"
6
-
7
- MAINTENANCE = ENV["MAINTENANCE"] == "true"
8
- BUILD_DOCS = MAINTENANCE || ENV["DOCS"] == "true"
9
- TRUSTED_RELEASE = ENV["rubygems_release_gem"] == "true"
4
+ require "rdoc/task"
5
+ require "minitest/test_task"
10
6
 
11
7
  Hoe.plugin :halostatue
12
- Hoe.plugin :rubygems
13
8
 
14
9
  Hoe.plugins.delete :debug
15
10
  Hoe.plugins.delete :newb
11
+ Hoe.plugins.delete :publish
16
12
  Hoe.plugins.delete :signing
17
- Hoe.plugins.delete :publish unless BUILD_DOCS
18
-
19
- if RUBY_VERSION < "1.9"
20
- class Array # :nodoc:
21
- def to_h
22
- Hash[*flatten(1)]
23
- end
24
- end
13
+ Hoe.plugins.delete :test
25
14
 
26
- class Gem::Specification # :nodoc:
27
- def metadata=(*)
28
- end
29
-
30
- def default_value(*)
31
- end
32
- end
33
-
34
- class Object # :nodoc:
35
- def caller_locations(*)
36
- []
37
- end
38
- end
39
- end
40
-
41
- _spec = Hoe.spec "diff-lcs" do
15
+ hoe = Hoe.spec "diff-lcs" do
42
16
  developer("Austin Ziegler", "halostatue@gmail.com")
43
17
 
44
- self.trusted_release = TRUSTED_RELEASE
18
+ self.trusted_release = ENV["rubygems_release_gem"] == "true"
45
19
 
46
- require_ruby_version ">= 1.8"
20
+ require_ruby_version ">= 3.2.0", "< 5"
47
21
 
48
- self.history_file = "CHANGELOG.md"
49
- self.readme_file = "README.md"
50
22
  self.licenses = ["MIT", "Artistic-1.0-Perl", "GPL-2.0-or-later"]
51
23
 
52
24
  spec_extras[:metadata] = ->(val) {
@@ -54,62 +26,116 @@ _spec = Hoe.spec "diff-lcs" do
54
26
  }
55
27
 
56
28
  extra_dev_deps << ["hoe", "~> 4.0"]
57
- extra_dev_deps << ["hoe-halostatue", "~> 2.0"]
58
- extra_dev_deps << ["hoe-rubygems", "~> 1.0"]
59
- 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"]
60
33
  extra_dev_deps << ["rake", ">= 10.0", "< 14"]
61
- extra_dev_deps << ["rdoc", ">= 6.3.1", "< 7"]
34
+ extra_dev_deps << ["rdoc", ">= 6.0", "< 8"]
35
+ extra_dev_deps << ["simplecov", "~> 0.9"]
36
+ extra_dev_deps << ["simplecov-lcov", "~> 0.9"]
37
+ extra_dev_deps << ["standard", "~> 1.50"]
38
+ extra_dev_deps << ["standard-thread_safety", "~> 1.0"]
39
+ extra_dev_deps << ["fasterer", "~> 0.11"]
62
40
  end
63
41
 
64
- if BUILD_DOCS
65
- rake_tasks = Rake.application.instance_variable_get(:@tasks)
66
- tasks = ["publish_docs", "publish_on_announce", "debug_email", "post_blog", "announce"]
67
- tasks.each do |task|
68
- rake_tasks.delete(task)
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"
54
+
55
+ SimpleCov::Formatter::LcovFormatter.config do |config|
56
+ config.report_with_single_file = true
57
+ config.lcov_file_name = "lcov.info"
69
58
  end
70
- end
71
59
 
72
- desc "Run all specifications"
73
- RSpec::Core::RakeTask.new(:spec) do |t|
74
- rspec_dirs = %w[spec lib].join(":")
75
- t.rspec_opts = ["-I#{rspec_dirs}"]
60
+ SimpleCov.start "test_frameworks" do
61
+ enable_coverage :branch
62
+ primary_coverage :branch
63
+ formatter #{formatters}
64
+ end
65
+ RUBY
76
66
  end
77
67
 
68
+ task default: :test
69
+
78
70
  task :version do
79
71
  require "diff/lcs/version"
80
72
  puts Diff::LCS::VERSION
81
73
  end
82
74
 
83
- Rake::Task["spec"].actions.uniq! { |a| a.source_location }
75
+ RDoc::Task.new do |config|
76
+ config.title = "diff-lcs"
77
+ config.main = "README.md"
78
+ config.rdoc_dir = "doc"
79
+ config.rdoc_files = hoe.spec.require_paths + hoe.spec.extra_rdoc_files -
80
+ FileList["integration/golden/*.txt", "Manifest.txt"].to_a
81
+ config.markup = "markdown"
82
+ end
83
+ task docs: :rerdoc
84
84
 
85
- # standard:disable Style/HashSyntax
86
- task :default => :spec unless Rake::Task["default"].prereqs.include?("spec")
87
- task :test => :spec unless Rake::Task["test"].prereqs.include?("spec")
88
- # standard:enable Style/HashSyntax
85
+ def rspec_to_golden(file)
86
+ File.join("integration/golden", File.basename(file, "_spec.rb")) + ".txt"
87
+ end
89
88
 
90
- if RUBY_VERSION >= "3.0" && RUBY_ENGINE == "ruby"
91
- namespace :spec do
92
- desc "Runs test coverage. Only works Ruby 2.0+ and assumes 'simplecov' is installed."
93
- task :coverage do
94
- ENV["COVERAGE"] = "true"
95
- Rake::Task["spec"].execute
96
- end
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
97
101
  end
98
102
  end
99
103
 
100
- if MAINTENANCE
101
- task ruby18: :package do
102
- require "diff/lcs/version"
103
- # standard:disable Layout/HeredocIndentation
104
- puts <<-MESSAGE
105
- You are starting a barebones Ruby 1.8 docker environment for testing.
106
- A snapshot package has been built, so install it with:
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" }
107
114
 
108
- cd diff-lcs
109
- gem install pkg/diff-lcs-#{Diff::LCS::VERSION}
115
+ rspecs.to_a.each do |rspec_file|
116
+ basename = File.basename(rspec_file, "_spec.rb")
110
117
 
111
- MESSAGE
112
- # standard:enable Layout/HeredocIndentation
113
- sh "docker run -it --rm -v #{Dir.pwd}:/root/diff-lcs bellbind/docker-ruby18-rails2 bash -l"
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
114
135
  end
115
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
@@ -1,41 +1,36 @@
1
1
  # diff-lcs Security
2
2
 
3
+ ## LLM-Generated Security Report Policy
4
+
5
+ Absolutely no security reports will be accepted that have been generated by LLM
6
+ agents.
7
+
3
8
  ## Supported Versions
4
9
 
5
- Security reports are accepted for the most recent major release and the previous
6
- version for a limited time after the initial major release version. After a
7
- major release, the previous version will receive full support for six months and
8
- security support for an additional six months (for a total of twelve months).
10
+ Security reports are accepted for the most recent major release, with a limited
11
+ window of support after the initial major release.
9
12
 
10
- Because diff-lcs 1.x supports a wide range of Ruby versions, security reports
11
- will only be accepted when they can be demonstrated on Ruby 3.1 or higher.
13
+ - Bug reports will be accepted up to three months after release.
14
+ - Security reports will be accepted up to six months after release.
12
15
 
13
- > [!information]
14
- >
15
- > There will be a diff-lcs 2.0 released in 2025 which narrows support to modern
16
- > versions of Ruby only.
16
+ All issues raised must be demonstrated on the minimum supported Ruby version.
17
+
18
+ > [!important]
17
19
  >
18
- > | Release Date | Support Ends | Security Support Ends |
19
- > | ------------ | ------------ | --------------------- |
20
- > | 2025 | +6 months | +12 months |
20
+ > Because diff-lcs 1 has been the only version for over twenty years, security
21
+ > reports will be accepted for one year after the release of diff-lcs 2.
21
22
  >
22
- > If the 2.0.0 release happens on 2025-07-01, regular support for diff-lcs 1.x
23
- > will end on 2026-12-31 and security support for diff-lcs 1.x will end on
24
- > 2026-06-30.
23
+ > | Version | Release Date | Support Ends | Security Support Ends |
24
+ > | ------- | ------------ | ------------ | --------------------- |
25
+ > | 1.x | 2010 | 2026-04-30 | 2027-01-31 |
26
+ > | 2.x | 2026-01-31 | - | - |
25
27
 
26
28
  ## Reporting a Vulnerability
27
29
 
28
- By preference, use the [Tidelift security contact][tidelift]. Tidelift will
29
- coordinate the fix and disclosure.
30
-
31
- Alternatively, Send an email to [diff-lcs@halostatue.ca][email] with the text
32
- `Diff::LCS` in the subject. Emails sent to this address should be encrypted
33
- using [age][age] with the following public key:
30
+ Report vulnerabilities via the [Tidelift security contact][tidelift]. Tidelift
31
+ will coordinate the fix and disclosure.
34
32
 
35
- ```
36
- age1fc6ngxmn02m62fej5cl30lrvwmxn4k3q2atqu53aatekmnqfwumqj4g93w
37
- ```
33
+ Alternatively, create a [private vulnerability report][advisory] with GitHub.
38
34
 
35
+ [advisory]: https://github.com/halostatue/diff-lcs/security/advisories/new
39
36
  [tidelift]: https://tidelift.com/security
40
- [email]: mailto:diff-lcs@halostatue.ca
41
- [age]: https://github.com/FiloSottile/age
@@ -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
@@ -1,37 +1,42 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # A block is an operation removing, adding, or changing a group of items.
4
- # Basically, this is just a list of changes, where each change adds or
5
- # deletes a single item. Used by bin/ldiff.
6
- class Diff::LCS::Block
7
- attr_reader :changes, :insert, :remove
3
+ Diff::LCS::Block = Data.define(:changes, :insert, :remove) # :nodoc:
8
4
 
9
- def initialize(chunk)
10
- @changes = []
11
- @insert = []
12
- @remove = []
5
+ # A block is an operation removing, adding, or changing a group of items, a list of
6
+ # changes, where each change adds or deletes a single item.
7
+ #
8
+ # Used by bin/ldiff.
9
+ class Diff::LCS::Block
10
+ def self.from_chunk(chunk)
11
+ changes, insert, remove = [], [], []
13
12
 
14
- chunk.each do |item|
15
- @changes << item
16
- @remove << item if item.deleting?
17
- @insert << item if item.adding?
13
+ chunk.each do
14
+ changes << _1
15
+ remove << _1 if _1.deleting?
16
+ insert << _1 if _1.adding?
18
17
  end
18
+
19
+ new(changes: changes.freeze, remove: remove.freeze, insert: insert.freeze)
19
20
  end
20
21
 
21
- def diff_size
22
- @insert.size - @remove.size
22
+ class << self
23
+ private :new, :[]
23
24
  end
24
25
 
26
+ private :with
27
+
28
+ def diff_size = insert.size - remove.size
29
+
25
30
  def op
26
- case [@remove.empty?, @insert.empty?]
27
- when [false, false]
28
- "!"
29
- when [false, true]
30
- "-"
31
- when [true, false]
32
- "+"
33
- else # [true, true]
34
- "^"
31
+ case [remove, insert]
32
+ # Unchanged
33
+ in [[], []] then "^"
34
+ # Delete
35
+ in [_, []] then "-"
36
+ # Insert
37
+ in [[], _] then "+"
38
+ # Conflict
39
+ in [_, _] then "!"
35
40
  end
36
41
  end
37
42
  end