diff-lcs 1.5.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 (132) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +581 -0
  3. data/CODE_OF_CONDUCT.md +166 -0
  4. data/CONTRIBUTING.md +127 -0
  5. data/CONTRIBUTORS.md +59 -0
  6. data/LICENCE.md +68 -0
  7. data/Manifest.txt +99 -35
  8. data/README.md +105 -0
  9. data/Rakefile +107 -96
  10. data/SECURITY.md +36 -0
  11. data/integration/compare/array_diff_spec.rb +10 -0
  12. data/integration/compare/hash_diff_spec.rb +25 -0
  13. data/integration/compare/string_diff_spec.rb +10 -0
  14. data/integration/rspec_differ_spec.rb +26 -0
  15. data/integration/rspec_expectations_spec.rb +32 -0
  16. data/integration/runner +20 -0
  17. data/lib/diff/lcs/block.rb +29 -24
  18. data/lib/diff/lcs/callbacks.rb +240 -242
  19. data/lib/diff/lcs/change.rb +102 -104
  20. data/lib/diff/lcs/hunk.rb +110 -157
  21. data/lib/diff/lcs/internals.rb +92 -96
  22. data/lib/diff/lcs/ldiff.rb +81 -73
  23. data/lib/diff/lcs/version.rb +7 -0
  24. data/lib/diff/lcs.rb +440 -466
  25. data/{docs → licenses}/artistic.txt +1 -1
  26. data/licenses/dco.txt +34 -0
  27. data/spec/hunk_spec.rb +33 -46
  28. data/spec/issues_spec.rb +32 -32
  29. data/spec/lcs_spec.rb +6 -6
  30. data/spec/ldiff_spec.rb +27 -16
  31. data/spec/patch_spec.rb +1 -1
  32. data/spec/spec_helper.rb +98 -108
  33. data/test/fixtures/123_x +2 -0
  34. data/test/fixtures/456_x +2 -0
  35. data/test/fixtures/empty +0 -0
  36. data/test/fixtures/file1.bin +0 -0
  37. data/test/fixtures/file2.bin +0 -0
  38. data/test/fixtures/four_lines +4 -0
  39. data/test/fixtures/four_lines_with_missing_new_line +4 -0
  40. data/test/fixtures/ldiff/diff.missing_new_line1-e +1 -0
  41. data/test/fixtures/ldiff/diff.missing_new_line1-f +1 -0
  42. data/test/fixtures/ldiff/diff.missing_new_line2-e +1 -0
  43. data/test/fixtures/ldiff/diff.missing_new_line2-f +1 -0
  44. data/test/fixtures/ldiff/error.diff.chef-e +2 -0
  45. data/test/fixtures/ldiff/error.diff.chef-f +2 -0
  46. data/test/fixtures/ldiff/error.diff.missing_new_line1-e +1 -0
  47. data/test/fixtures/ldiff/error.diff.missing_new_line1-f +1 -0
  48. data/test/fixtures/ldiff/error.diff.missing_new_line2-e +1 -0
  49. data/test/fixtures/ldiff/error.diff.missing_new_line2-f +1 -0
  50. data/test/fixtures/ldiff/output.diff-c +7 -0
  51. data/test/fixtures/ldiff/output.diff-u +5 -0
  52. data/test/fixtures/ldiff/output.diff.bin1 +0 -0
  53. data/test/fixtures/ldiff/output.diff.bin1-c +0 -0
  54. data/test/fixtures/ldiff/output.diff.bin1-e +0 -0
  55. data/test/fixtures/ldiff/output.diff.bin1-f +0 -0
  56. data/test/fixtures/ldiff/output.diff.bin1-u +0 -0
  57. data/test/fixtures/ldiff/output.diff.bin2 +1 -0
  58. data/test/fixtures/ldiff/output.diff.bin2-c +1 -0
  59. data/test/fixtures/ldiff/output.diff.bin2-e +1 -0
  60. data/test/fixtures/ldiff/output.diff.bin2-f +1 -0
  61. data/test/fixtures/ldiff/output.diff.bin2-u +1 -0
  62. data/{spec → test}/fixtures/ldiff/output.diff.chef-c +2 -2
  63. data/test/fixtures/ldiff/output.diff.chef-u +9 -0
  64. data/{spec → test}/fixtures/ldiff/output.diff.chef2-c +2 -2
  65. data/{spec → test}/fixtures/ldiff/output.diff.chef2-u +2 -2
  66. data/test/fixtures/ldiff/output.diff.empty.vs.four_lines +5 -0
  67. data/test/fixtures/ldiff/output.diff.empty.vs.four_lines-c +9 -0
  68. data/test/fixtures/ldiff/output.diff.empty.vs.four_lines-e +6 -0
  69. data/test/fixtures/ldiff/output.diff.empty.vs.four_lines-f +6 -0
  70. data/test/fixtures/ldiff/output.diff.empty.vs.four_lines-u +7 -0
  71. data/test/fixtures/ldiff/output.diff.four_lines.vs.empty +5 -0
  72. data/test/fixtures/ldiff/output.diff.four_lines.vs.empty-c +9 -0
  73. data/test/fixtures/ldiff/output.diff.four_lines.vs.empty-e +1 -0
  74. data/test/fixtures/ldiff/output.diff.four_lines.vs.empty-f +1 -0
  75. data/test/fixtures/ldiff/output.diff.four_lines.vs.empty-u +7 -0
  76. data/test/fixtures/ldiff/output.diff.issue95_trailing_context +4 -0
  77. data/test/fixtures/ldiff/output.diff.issue95_trailing_context-c +9 -0
  78. data/{spec/fixtures/ldiff/output.diff-e → test/fixtures/ldiff/output.diff.issue95_trailing_context-e} +1 -1
  79. data/{spec/fixtures/ldiff/output.diff-f → test/fixtures/ldiff/output.diff.issue95_trailing_context-f} +1 -1
  80. data/test/fixtures/ldiff/output.diff.issue95_trailing_context-u +6 -0
  81. data/test/fixtures/ldiff/output.diff.missing_new_line1 +5 -0
  82. data/test/fixtures/ldiff/output.diff.missing_new_line1-c +14 -0
  83. data/test/fixtures/ldiff/output.diff.missing_new_line1-e +0 -0
  84. data/test/fixtures/ldiff/output.diff.missing_new_line1-f +0 -0
  85. data/test/fixtures/ldiff/output.diff.missing_new_line1-u +9 -0
  86. data/test/fixtures/ldiff/output.diff.missing_new_line2 +5 -0
  87. data/test/fixtures/ldiff/output.diff.missing_new_line2-c +14 -0
  88. data/test/fixtures/ldiff/output.diff.missing_new_line2-e +0 -0
  89. data/test/fixtures/ldiff/output.diff.missing_new_line2-f +0 -0
  90. data/test/fixtures/ldiff/output.diff.missing_new_line2-u +9 -0
  91. data/test/test_block.rb +34 -0
  92. data/test/test_change.rb +234 -0
  93. data/test/test_diff.rb +53 -0
  94. data/test/test_helper.rb +225 -0
  95. data/test/test_hunk.rb +72 -0
  96. data/test/test_issues.rb +168 -0
  97. data/test/test_lcs.rb +47 -0
  98. data/test/test_ldiff.rb +89 -0
  99. data/test/test_patch.rb +362 -0
  100. data/test/test_sdiff.rb +167 -0
  101. data/test/test_traverse_balanced.rb +322 -0
  102. data/test/test_traverse_sequences.rb +187 -0
  103. metadata +211 -103
  104. data/.rspec +0 -1
  105. data/Code-of-Conduct.md +0 -74
  106. data/Contributing.md +0 -121
  107. data/History.md +0 -431
  108. data/License.md +0 -41
  109. data/README.rdoc +0 -84
  110. data/bin/htmldiff +0 -35
  111. data/lib/diff/lcs/backports.rb +0 -9
  112. data/lib/diff/lcs/htmldiff.rb +0 -158
  113. data/spec/fixtures/ldiff/output.diff-c +0 -7
  114. data/spec/fixtures/ldiff/output.diff-u +0 -5
  115. data/spec/fixtures/ldiff/output.diff.chef-e +0 -3
  116. data/spec/fixtures/ldiff/output.diff.chef-f +0 -3
  117. data/spec/fixtures/ldiff/output.diff.chef-u +0 -9
  118. data/spec/fixtures/ldiff/output.diff.chef2-e +0 -7
  119. data/spec/fixtures/ldiff/output.diff.chef2-f +0 -7
  120. /data/{docs → licenses}/COPYING.txt +0 -0
  121. /data/{spec → test}/fixtures/aX +0 -0
  122. /data/{spec → test}/fixtures/bXaX +0 -0
  123. /data/{spec → test}/fixtures/ds1.csv +0 -0
  124. /data/{spec → test}/fixtures/ds2.csv +0 -0
  125. /data/{spec → test}/fixtures/ldiff/output.diff +0 -0
  126. /data/{spec → test}/fixtures/ldiff/output.diff.chef +0 -0
  127. /data/{spec → test}/fixtures/ldiff/output.diff.chef2 +0 -0
  128. /data/{spec → test}/fixtures/ldiff/output.diff.chef2-d +0 -0
  129. /data/{spec → test}/fixtures/new-chef +0 -0
  130. /data/{spec → test}/fixtures/new-chef2 +0 -0
  131. /data/{spec → test}/fixtures/old-chef +0 -0
  132. /data/{spec → test}/fixtures/old-chef2 +0 -0
data/Rakefile CHANGED
@@ -1,130 +1,141 @@
1
- # frozen_string_literal: true
2
-
3
1
  require "rubygems"
4
- require "rspec"
5
- require "rspec/core/rake_task"
6
2
  require "hoe"
3
+ require "rake/clean"
4
+ require "rdoc/task"
5
+ require "minitest/test_task"
7
6
 
8
- # This is required until https://github.com/seattlerb/hoe/issues/112 is fixed
9
- class Hoe
10
- def with_config
11
- config = Hoe::DEFAULT_CONFIG
7
+ Hoe.plugin :halostatue
12
8
 
13
- rc = File.expand_path("~/.hoerc")
14
- homeconfig = load_config(rc)
15
- config = config.merge(homeconfig)
9
+ Hoe.plugins.delete :debug
10
+ Hoe.plugins.delete :newb
11
+ Hoe.plugins.delete :publish
12
+ Hoe.plugins.delete :signing
13
+ Hoe.plugins.delete :test
16
14
 
17
- localconfig = load_config(File.expand_path(File.join(Dir.pwd, ".hoerc")))
18
- config = config.merge(localconfig)
15
+ hoe = Hoe.spec "diff-lcs" do
16
+ developer("Austin Ziegler", "halostatue@gmail.com")
19
17
 
20
- yield config, rc
21
- end
18
+ self.trusted_release = ENV["rubygems_release_gem"] == "true"
22
19
 
23
- def load_config(name)
24
- File.exist?(name) ? safe_load_yaml(name) : {}
25
- end
20
+ require_ruby_version ">= 3.2.0", "< 5"
26
21
 
27
- def safe_load_yaml(name)
28
- return safe_load_yaml_file(name) if YAML.respond_to?(:safe_load_file)
22
+ self.licenses = ["MIT", "Artistic-1.0-Perl", "GPL-2.0-or-later"]
29
23
 
30
- data = IO.binread(name)
31
- YAML.safe_load(data, permitted_classes: [Regexp])
32
- rescue
33
- YAML.safe_load(data, [Regexp])
24
+ spec_extras[:metadata] = ->(val) {
25
+ val["rubygems_mfa_required"] = "true"
26
+ }
27
+
28
+ extra_dev_deps << ["hoe", "~> 4.0"]
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"]
33
+ extra_dev_deps << ["rake", ">= 10.0", "< 14"]
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"]
40
+ end
41
+
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"
34
58
  end
35
59
 
36
- def safe_load_yaml_file(name)
37
- YAML.safe_load_file(name, permitted_classes: [Regexp])
38
- rescue
39
- YAML.safe_load_file(name, [Regexp])
60
+ SimpleCov.start "test_frameworks" do
61
+ enable_coverage :branch
62
+ primary_coverage :branch
63
+ formatter #{formatters}
40
64
  end
65
+ RUBY
41
66
  end
42
67
 
43
- Hoe.plugin :bundler
44
- Hoe.plugin :doofus
45
- Hoe.plugin :gemspec2
46
- Hoe.plugin :git
68
+ task default: :test
47
69
 
48
- if RUBY_VERSION < "1.9"
49
- class Array # :nodoc:
50
- def to_h
51
- Hash[*flatten(1)]
52
- end
53
- end
70
+ task :version do
71
+ require "diff/lcs/version"
72
+ puts Diff::LCS::VERSION
73
+ end
54
74
 
55
- class Gem::Specification # :nodoc:
56
- def metadata=(*)
57
- end
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
58
84
 
59
- def default_value(*)
60
- end
61
- end
85
+ def rspec_to_golden(file)
86
+ File.join("integration/golden", File.basename(file, "_spec.rb")) + ".txt"
87
+ end
62
88
 
63
- class Object # :nodoc:
64
- def caller_locations(*)
65
- []
66
- 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
67
101
  end
68
102
  end
69
103
 
70
- _spec = Hoe.spec "diff-lcs" do
71
- developer("Austin Ziegler", "halostatue@gmail.com")
104
+ rspecs = FileList["integration/compare/*_spec.rb"]
72
105
 
73
- require_ruby_version ">= 1.8"
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 }
74
112
 
75
- self.history_file = "History.md"
76
- self.readme_file = "README.rdoc"
77
- self.licenses = ["MIT", "Artistic-2.0", "GPL-2.0-or-later"]
113
+ unbundled { sh "gem install rspec" }
78
114
 
79
- spec_extras[:metadata] = ->(val) { val["rubygems_mfa_required"] = "true" }
115
+ rspecs.to_a.each do |rspec_file|
116
+ basename = File.basename(rspec_file, "_spec.rb")
80
117
 
81
- extra_dev_deps << ["hoe", ">= 3.0", "< 5"]
82
- extra_dev_deps << ["hoe-doofus", "~> 1.0"]
83
- extra_dev_deps << ["hoe-gemspec2", "~> 1.1"]
84
- extra_dev_deps << ["hoe-git2", "~> 1.7"]
85
- extra_dev_deps << ["hoe-rubygems", "~> 1.0"]
86
- extra_dev_deps << ["rspec", ">= 2.0", "< 4"]
87
- extra_dev_deps << ["rake", ">= 10.0", "< 14"]
88
- extra_dev_deps << ["rdoc", ">= 6.3.1", "< 7"]
89
- end
118
+ base_contents = unbundled { `integration/runner rspec #{rspec_file} 2>&1` }
119
+ base_contents = normalize_rspec_output(base_contents)
90
120
 
91
- desc "Run all specifications"
92
- RSpec::Core::RakeTask.new(:spec) do |t|
93
- rspec_dirs = %w[spec lib].join(":")
94
- t.rspec_opts = ["-I#{rspec_dirs}"]
95
- end
121
+ work_contents = unbundled { `integration/runner rspec -Ilib -rdiff/lcs #{rspec_file} 2>&1` }
122
+ work_contents = normalize_rspec_output(work_contents)
96
123
 
97
- Rake::Task["spec"].actions.uniq! { |a| a.source_location }
124
+ if base_contents == work_contents
125
+ puts "#{basename}: OK"
126
+ else
127
+ puts "#{basename}: FAIL"
98
128
 
99
- # standard:disable Style/HashSyntax
100
- task :default => :spec unless Rake::Task["default"].prereqs.include?("spec")
101
- task :test => :spec unless Rake::Task["test"].prereqs.include?("spec")
102
- # standard:enable Style/HashSyntax
129
+ File.write(base, base_contents)
130
+ File.write(work, work_contents)
103
131
 
104
- if RUBY_VERSION >= "2.0" && RUBY_ENGINE == "ruby"
105
- namespace :spec do
106
- desc "Runs test coverage. Only works Ruby 2.0+ and assumes 'simplecov' is installed."
107
- task :coverage do
108
- ENV["COVERAGE"] = "yes"
109
- Rake::Task["spec"].execute
132
+ unbundled { sh "integration/runner -Ilib bin/ldiff -U #{base} #{work}" }
133
+ end
110
134
  end
111
135
  end
112
136
  end
113
137
 
114
- task :ruby18 do
115
- # standard:disable Layout/HeredocIndentation
116
- puts <<-MESSAGE
117
- You are starting a barebones Ruby 1.8 docker environment. You will need to
118
- do the following:
119
-
120
- - mv Gemfile.lock{,.v2}
121
- - gem install bundler --version 1.17.2 --no-ri --no-rdoc
122
- - ruby -S bundle
123
- - rake
124
-
125
- Don't forget to restore your Gemfile.lock after testing.
126
-
127
- MESSAGE
128
- # standard:enable Layout/HeredocIndentation
129
- sh "docker run -it --rm -v #{Dir.pwd}:/root/diff-lcs bellbind/docker-ruby18-rails2 bash -l"
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"
130
141
  end
data/SECURITY.md ADDED
@@ -0,0 +1,36 @@
1
+ # diff-lcs Security
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
+
8
+ ## Supported Versions
9
+
10
+ Security reports are accepted for the most recent major release, with a limited
11
+ window of support after the initial major release.
12
+
13
+ - Bug reports will be accepted up to three months after release.
14
+ - Security reports will be accepted up to six months after release.
15
+
16
+ All issues raised must be demonstrated on the minimum supported Ruby version.
17
+
18
+ > [!important]
19
+ >
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.
22
+ >
23
+ > | Version | Release Date | Support Ends | Security Support Ends |
24
+ > | ------- | ------------ | ------------ | --------------------- |
25
+ > | 1.x | 2010 | 2026-05-01 | 2027-02-01 |
26
+ > | 2.x | 2026-02-01 | - | - |
27
+
28
+ ## Reporting a Vulnerability
29
+
30
+ Report vulnerabilities via the [Tidelift security contact][tidelift]. Tidelift
31
+ will coordinate the fix and disclosure.
32
+
33
+ Alternatively, create a [private vulnerability report][advisory] with GitHub.
34
+
35
+ [advisory]: https://github.com/halostatue/diff-lcs/security/advisories/new
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
@@ -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