diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1a7fe242..0164d776 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,7 +37,7 @@ jobs: with: persist-credentials: false - - uses: ruby/setup-ruby@8a836efbcebe5de0fe86b48a775b7a31b5c70c93 # v1.277.0 + - uses: ruby/setup-ruby@708024e6c902387ab41de36e1669e43b5ee7085e # v1.283.0 with: ruby-version: '3.4' rubygems: latest @@ -71,7 +71,7 @@ jobs: with: persist-credentials: false - - uses: ruby/setup-ruby@8a836efbcebe5de0fe86b48a775b7a31b5c70c93 # v1.277.0 + - uses: ruby/setup-ruby@708024e6c902387ab41de36e1669e43b5ee7085e # v1.283.0 with: ruby-version: '3.4' rubygems: latest @@ -120,7 +120,7 @@ jobs: with: persist-credentials: false - - uses: ruby/setup-ruby@8a836efbcebe5de0fe86b48a775b7a31b5c70c93 # v1.277.0 + - uses: ruby/setup-ruby@708024e6c902387ab41de36e1669e43b5ee7085e # v1.283.0 with: ruby-version: ${{ matrix.ruby }} rubygems: latest @@ -129,6 +129,11 @@ jobs: - run: bundle exec ruby -S rake test --trace + - name: Verify RSpec compatibility + run: | + gem install rspec + integration/runner rake integration + required-macos: name: Ruby ${{ matrix.ruby }} - ${{ matrix.os }} @@ -167,7 +172,7 @@ jobs: with: persist-credentials: false - - uses: ruby/setup-ruby@8a836efbcebe5de0fe86b48a775b7a31b5c70c93 # v1.277.0 + - uses: ruby/setup-ruby@708024e6c902387ab41de36e1669e43b5ee7085e # v1.283.0 with: ruby-version: ${{ matrix.ruby }} rubygems: latest @@ -176,6 +181,13 @@ jobs: - run: bundle exec ruby -S rake test --trace + - name: Verify RSpec compatibility + env: + GEM_PATH: vendor/bundle + run: | + gem install rspec + integration/runner rake integration + optional-windows: name: Ruby ${{ matrix.ruby }} - ${{ matrix.os }} @@ -216,7 +228,7 @@ jobs: with: persist-credentials: false - - uses: ruby/setup-ruby@8a836efbcebe5de0fe86b48a775b7a31b5c70c93 # v1.277.0 + - uses: ruby/setup-ruby@708024e6c902387ab41de36e1669e43b5ee7085e # v1.283.0 with: ruby-version: ${{ matrix.ruby }} rubygems: latest @@ -225,6 +237,13 @@ jobs: - run: bundle exec ruby -S rake test --trace + - name: Verify RSpec compatibility + env: + GEM_PATH: vendor/bundle + run: | + gem install rspec + integration/runner rake integration + jruby-optional: name: JRuby ${{ matrix.ruby }} - ${{ matrix.os }} @@ -262,7 +281,7 @@ jobs: with: persist-credentials: false - - uses: ruby/setup-ruby@8a836efbcebe5de0fe86b48a775b7a31b5c70c93 # v1.277.0 + - uses: ruby/setup-ruby@708024e6c902387ab41de36e1669e43b5ee7085e # v1.283.0 with: ruby-version: ${{ matrix.ruby }} rubygems: latest @@ -271,6 +290,13 @@ jobs: - run: bundle exec ruby -S rake test --trace + - name: Verify RSpec compatibility + env: + GEM_PATH: vendor/bundle + run: | + gem install rspec + integration/runner rake integration + ruby-head-optional: name: Ruby ${{ matrix.ruby }} - ${{ matrix.os }} (optional) @@ -308,7 +334,7 @@ jobs: with: persist-credentials: false - - uses: ruby/setup-ruby@8a836efbcebe5de0fe86b48a775b7a31b5c70c93 # v1.277.0 + - uses: ruby/setup-ruby@708024e6c902387ab41de36e1669e43b5ee7085e # v1.283.0 with: ruby-version: ${{ matrix.ruby }} rubygems: latest @@ -316,3 +342,10 @@ jobs: bundler-cache: true - run: bundle exec ruby -S rake test --trace + + - name: Verify RSpec compatibility + env: + GEM_PATH: vendor/bundle + run: | + gem install rspec + integration/runner rake integration diff --git a/.github/workflows/dco-check.yml b/.github/workflows/dco-check.yml index 821f07c2..40c2068f 100644 --- a/.github/workflows/dco-check.yml +++ b/.github/workflows/dco-check.yml @@ -27,4 +27,4 @@ jobs: api.github.com:443 github.com:443 - - uses: KineticCafe/actions-dco@76b7fc30ff5988e68d01ea07deeaf7e71256598f # v2.1.0 + - uses: KineticCafe/actions-dco@6e1652ef3027ce128e65e6edd215ae053350bd16 # v2.1.1 diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml index fbd62612..cd32cf22 100644 --- a/.github/workflows/publish-docs.yml +++ b/.github/workflows/publish-docs.yml @@ -38,7 +38,7 @@ jobs: 'print "version=", Gem::Specification.load(ARGV[0]).rubygems_version, "\n"' \ diff-lcs.gemspec >>"${GITHUB_OUTPUT}" - - uses: ruby/setup-ruby@8a836efbcebe5de0fe86b48a775b7a31b5c70c93 # v1.277.0 + - uses: ruby/setup-ruby@708024e6c902387ab41de36e1669e43b5ee7085e # v1.283.0 with: bundler-cache: false ruby-version: ruby diff --git a/.github/workflows/publish-gem.yml b/.github/workflows/publish-gem.yml index 15492445..e5a4a355 100644 --- a/.github/workflows/publish-gem.yml +++ b/.github/workflows/publish-gem.yml @@ -65,7 +65,7 @@ jobs: 'print "version=", Gem::Specification.load(ARGV[0]).rubygems_version, "\n"' \ diff-lcs.gemspec >>"${GITHUB_OUTPUT}" - - uses: ruby/setup-ruby@8a836efbcebe5de0fe86b48a775b7a31b5c70c93 # v1.277.0 + - uses: ruby/setup-ruby@708024e6c902387ab41de36e1669e43b5ee7085e # v1.283.0 with: bundler-cache: false ruby-version: ruby diff --git a/.github/zizmor.yml b/.github/zizmor.yml index ba895203..a6d13ba5 100644 --- a/.github/zizmor.yml +++ b/.github/zizmor.yml @@ -1,4 +1,4 @@ rules: artipacked: ignore: - - publish-gem.yml:53 # publish-gem adds and pushes a tag. + - publish-gem.yml:58 # publish-gem adds and pushes a tag. diff --git a/CHANGELOG.md b/CHANGELOG.md index 26af8ea4..03f5ba20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## 2.0.0.beta.1 / 2025-12-31 +## 2.0.0.beta.2 / 2025-01-22 This release has significant **breaking changes**. @@ -35,6 +35,27 @@ This release has significant **breaking changes**. Most callers are using `Diff::LCS.lcs` and modern Ruby did-you-mean support should assist with this update. +- `Diff::LCS::Change` objects implemented the comparison operator (`<=>`) + incorrectly. Comparisons are now done so that the `position` is compared + first, then the `action` (by index of `VALID_ACTIONS`), and finally the + element. `Diff::LCS::ContextChange` works similarly, comparing the old and new + positions prior to comparing the actions by index. + + The order of `VALID_ACTIONS` was changed for the index order to make sense. + +- `ldiff` now implements `-` as a filename option for standard input. This is + used for integration testing with RSpec like this: + + ```console + rspec -Ilib -rdiff/lcs integration/failure/array_diff_spec.rb 2>&1 | + ruby -Ilib bin/ldiff -U integration/golden/array_diff.txt - + ``` + +- diff-lcs no longer uses RSpec as its test suite, but instead uses Minitest. + The conversion to Minitest and the new RSpec integration tests + (`rake integration`) were written with the assistance of [Kiro][kiro] and + verified manually, with portions changed as required. + ## 1.6.2 / 2025-05-12 - Handle upcoming changes to the `cgi` gem in Ruby 3.5 ([#147][pull-147]) @@ -60,8 +81,9 @@ This release has significant **breaking changes**. ## 1.6.0 / 2025-02-13 -- Baptiste Courtois (@annih) has done significant work on making `bin/ldiff` - work better, contributing a number of issues and pull requests. These include: +- Baptiste Courtois ([@annih][gh-user-annih]) has done significant work on + making `bin/ldiff` work better, contributing a number of issues and pull + requests. These include: - Separation of command parsing from diff-generation in `Diff::LCS::Ldiff` code extraction making it easier to use separately from the `bin/ldiff` @@ -420,8 +442,8 @@ This release has significant **breaking changes**. ## 1.1.1 / 2004-09-25 -- Fixed bug #891 (Set returned from patch command does not contain last equal - part). +- Fixed bug [#891][gh-issue-891] (Set returned from patch command does not + contain last equal part). - Fixed a problem with callback initialisation code (it assumed that all callbacks passed as classes can be initialised; now, it rescues NoMethodError @@ -552,3 +574,6 @@ This release has significant **breaking changes**. [standard ruby]: https://github.com/standardrb/standard [tidelift]: https://tidelift.com/security [tp]: https://guides.rubygems.org/trusted-publishing/ +[kiro]: https://kiro.dev +[gh-user-annih]: https://github.com/annih +[gh-issue-891]: https://github.com/halostatue/diff-lcs/issues/891 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 923c0637..b9763435 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -13,7 +13,11 @@ diff-lcs is governed under the [Contributor Covenant Code of Conduct][cccoc]. I have several guidelines to contributing code through pull requests: - All code changes require tests. In most cases, this will be added or updated - unit tests. I use [RSpec][rspec]. + unit tests. I use [Minitest][minitest]. + + - There are integration tests with RSpec which must not be broken, accessible + through `rake integration`. This requires a non-bundled RSpec, + `gem install rspec`. - I use code formatters, static analysis tools, and linting to ensure consistent styles and formatting. There should be no warning output from test run @@ -115,6 +119,7 @@ required metadata trailers are: [dco]: licences/dco.txt [hoe]: https://github.com/seattlerb/hoe [issues]: https://github.com/halostatue/diff-lcs/issues +[minitest]: https://github.com/seattlerb/minitest [rspec]: https://rspec.info/documentation/ [standardrb]: https://github.com/standardrb/standard [tpope-qcm]: https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 9053019c..c9598df2 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,26 +1,26 @@ # Contributors -- Austin Ziegler (@halostatue) created diff-lcs. +- Austin Ziegler ([@halostatue][gh-user-halostatue]) created diff-lcs. Thanks to everyone else who has contributed to diff-lcs over the years: -- @ginriki -- @joshbronson -- @kevinmook -- @mckaz +- [@ginriki][gh-user-ginriki] +- [@joshbronson][gh-user-joshbronson] +- [@kevinmook][gh-user-kevinmook] +- [@mckaz][gh-user-mckaz] - Akinori Musha - Artem Ignatyev - Brandon Fish -- Baptiste Courtois (@annih) +- Baptiste Courtois ([@annih][gh-user-annih]) - Camille Drapier - Cédric Boutillier -- @earlopain +- [@earlopain][gh-user-earlopain] - Gregg Kellogg - Jagdeep Singh - Jason Gladish - Jon Rowe - Josef Strzibny -- Josep (@apuratepp) +- Josep ([@apuratepp][gh-user-apuratepp]) - Josh Bronson - Jun Aruga - Justin Steele @@ -44,6 +44,16 @@ Thanks to everyone else who has contributed to diff-lcs over the years: - Ryan Lovelett - Scott Steele - Simon Courtois -- Tien (@tiendo1011) +- Tien ([@tiendo1011][gh-user-tiendo1011]) - Tomas Jura - Vít Ondruch + +[gh-user-halostatue]: https://github.com/halostatue +[gh-user-ginriki]: https://github.com/ginriki +[gh-user-joshbronson]: https://github.com/joshbronson +[gh-user-kevinmook]: https://github.com/kevinmook +[gh-user-mckaz]: https://github.com/mckaz +[gh-user-annih]: https://github.com/annih +[gh-user-earlopain]: https://github.com/earlopain +[gh-user-apuratepp]: https://github.com/apuratepp +[gh-user-tiendo1011]: https://github.com/tiendo1011 diff --git a/Manifest.txt b/Manifest.txt index 8fbb5c71..20d5af8b 100644 --- a/Manifest.txt +++ b/Manifest.txt @@ -8,6 +8,12 @@ README.md Rakefile SECURITY.md bin/ldiff +integration/compare/array_diff_spec.rb +integration/compare/hash_diff_spec.rb +integration/compare/string_diff_spec.rb +integration/rspec_differ_spec.rb +integration/rspec_expectations_spec.rb +integration/runner lib/diff-lcs.rb lib/diff/lcs.rb lib/diff/lcs/array.rb @@ -24,76 +30,6 @@ licenses/artistic.txt licenses/dco.txt spec/change_spec.rb spec/diff_spec.rb -spec/fixtures/123_x -spec/fixtures/456_x -spec/fixtures/aX -spec/fixtures/bXaX -spec/fixtures/ds1.csv -spec/fixtures/ds2.csv -spec/fixtures/empty -spec/fixtures/file1.bin -spec/fixtures/file2.bin -spec/fixtures/four_lines -spec/fixtures/four_lines_with_missing_new_line -spec/fixtures/ldiff/diff.missing_new_line1-e -spec/fixtures/ldiff/diff.missing_new_line1-f -spec/fixtures/ldiff/diff.missing_new_line2-e -spec/fixtures/ldiff/diff.missing_new_line2-f -spec/fixtures/ldiff/error.diff.chef-e -spec/fixtures/ldiff/error.diff.chef-f -spec/fixtures/ldiff/error.diff.missing_new_line1-e -spec/fixtures/ldiff/error.diff.missing_new_line1-f -spec/fixtures/ldiff/error.diff.missing_new_line2-e -spec/fixtures/ldiff/error.diff.missing_new_line2-f -spec/fixtures/ldiff/output.diff -spec/fixtures/ldiff/output.diff-c -spec/fixtures/ldiff/output.diff-u -spec/fixtures/ldiff/output.diff.bin1 -spec/fixtures/ldiff/output.diff.bin1-c -spec/fixtures/ldiff/output.diff.bin1-e -spec/fixtures/ldiff/output.diff.bin1-f -spec/fixtures/ldiff/output.diff.bin1-u -spec/fixtures/ldiff/output.diff.bin2 -spec/fixtures/ldiff/output.diff.bin2-c -spec/fixtures/ldiff/output.diff.bin2-e -spec/fixtures/ldiff/output.diff.bin2-f -spec/fixtures/ldiff/output.diff.bin2-u -spec/fixtures/ldiff/output.diff.chef -spec/fixtures/ldiff/output.diff.chef-c -spec/fixtures/ldiff/output.diff.chef-u -spec/fixtures/ldiff/output.diff.chef2 -spec/fixtures/ldiff/output.diff.chef2-c -spec/fixtures/ldiff/output.diff.chef2-d -spec/fixtures/ldiff/output.diff.chef2-u -spec/fixtures/ldiff/output.diff.empty.vs.four_lines -spec/fixtures/ldiff/output.diff.empty.vs.four_lines-c -spec/fixtures/ldiff/output.diff.empty.vs.four_lines-e -spec/fixtures/ldiff/output.diff.empty.vs.four_lines-f -spec/fixtures/ldiff/output.diff.empty.vs.four_lines-u -spec/fixtures/ldiff/output.diff.four_lines.vs.empty -spec/fixtures/ldiff/output.diff.four_lines.vs.empty-c -spec/fixtures/ldiff/output.diff.four_lines.vs.empty-e -spec/fixtures/ldiff/output.diff.four_lines.vs.empty-f -spec/fixtures/ldiff/output.diff.four_lines.vs.empty-u -spec/fixtures/ldiff/output.diff.issue95_trailing_context -spec/fixtures/ldiff/output.diff.issue95_trailing_context-c -spec/fixtures/ldiff/output.diff.issue95_trailing_context-e -spec/fixtures/ldiff/output.diff.issue95_trailing_context-f -spec/fixtures/ldiff/output.diff.issue95_trailing_context-u -spec/fixtures/ldiff/output.diff.missing_new_line1 -spec/fixtures/ldiff/output.diff.missing_new_line1-c -spec/fixtures/ldiff/output.diff.missing_new_line1-e -spec/fixtures/ldiff/output.diff.missing_new_line1-f -spec/fixtures/ldiff/output.diff.missing_new_line1-u -spec/fixtures/ldiff/output.diff.missing_new_line2 -spec/fixtures/ldiff/output.diff.missing_new_line2-c -spec/fixtures/ldiff/output.diff.missing_new_line2-e -spec/fixtures/ldiff/output.diff.missing_new_line2-f -spec/fixtures/ldiff/output.diff.missing_new_line2-u -spec/fixtures/new-chef -spec/fixtures/new-chef2 -spec/fixtures/old-chef -spec/fixtures/old-chef2 spec/hunk_spec.rb spec/issues_spec.rb spec/lcs_spec.rb @@ -103,3 +39,85 @@ spec/sdiff_spec.rb spec/spec_helper.rb spec/traverse_balanced_spec.rb spec/traverse_sequences_spec.rb +test/fixtures/123_x +test/fixtures/456_x +test/fixtures/aX +test/fixtures/bXaX +test/fixtures/ds1.csv +test/fixtures/ds2.csv +test/fixtures/empty +test/fixtures/file1.bin +test/fixtures/file2.bin +test/fixtures/four_lines +test/fixtures/four_lines_with_missing_new_line +test/fixtures/ldiff/diff.missing_new_line1-e +test/fixtures/ldiff/diff.missing_new_line1-f +test/fixtures/ldiff/diff.missing_new_line2-e +test/fixtures/ldiff/diff.missing_new_line2-f +test/fixtures/ldiff/error.diff.chef-e +test/fixtures/ldiff/error.diff.chef-f +test/fixtures/ldiff/error.diff.missing_new_line1-e +test/fixtures/ldiff/error.diff.missing_new_line1-f +test/fixtures/ldiff/error.diff.missing_new_line2-e +test/fixtures/ldiff/error.diff.missing_new_line2-f +test/fixtures/ldiff/output.diff +test/fixtures/ldiff/output.diff-c +test/fixtures/ldiff/output.diff-u +test/fixtures/ldiff/output.diff.bin1 +test/fixtures/ldiff/output.diff.bin1-c +test/fixtures/ldiff/output.diff.bin1-e +test/fixtures/ldiff/output.diff.bin1-f +test/fixtures/ldiff/output.diff.bin1-u +test/fixtures/ldiff/output.diff.bin2 +test/fixtures/ldiff/output.diff.bin2-c +test/fixtures/ldiff/output.diff.bin2-e +test/fixtures/ldiff/output.diff.bin2-f +test/fixtures/ldiff/output.diff.bin2-u +test/fixtures/ldiff/output.diff.chef +test/fixtures/ldiff/output.diff.chef-c +test/fixtures/ldiff/output.diff.chef-u +test/fixtures/ldiff/output.diff.chef2 +test/fixtures/ldiff/output.diff.chef2-c +test/fixtures/ldiff/output.diff.chef2-d +test/fixtures/ldiff/output.diff.chef2-u +test/fixtures/ldiff/output.diff.empty.vs.four_lines +test/fixtures/ldiff/output.diff.empty.vs.four_lines-c +test/fixtures/ldiff/output.diff.empty.vs.four_lines-e +test/fixtures/ldiff/output.diff.empty.vs.four_lines-f +test/fixtures/ldiff/output.diff.empty.vs.four_lines-u +test/fixtures/ldiff/output.diff.four_lines.vs.empty +test/fixtures/ldiff/output.diff.four_lines.vs.empty-c +test/fixtures/ldiff/output.diff.four_lines.vs.empty-e +test/fixtures/ldiff/output.diff.four_lines.vs.empty-f +test/fixtures/ldiff/output.diff.four_lines.vs.empty-u +test/fixtures/ldiff/output.diff.issue95_trailing_context +test/fixtures/ldiff/output.diff.issue95_trailing_context-c +test/fixtures/ldiff/output.diff.issue95_trailing_context-e +test/fixtures/ldiff/output.diff.issue95_trailing_context-f +test/fixtures/ldiff/output.diff.issue95_trailing_context-u +test/fixtures/ldiff/output.diff.missing_new_line1 +test/fixtures/ldiff/output.diff.missing_new_line1-c +test/fixtures/ldiff/output.diff.missing_new_line1-e +test/fixtures/ldiff/output.diff.missing_new_line1-f +test/fixtures/ldiff/output.diff.missing_new_line1-u +test/fixtures/ldiff/output.diff.missing_new_line2 +test/fixtures/ldiff/output.diff.missing_new_line2-c +test/fixtures/ldiff/output.diff.missing_new_line2-e +test/fixtures/ldiff/output.diff.missing_new_line2-f +test/fixtures/ldiff/output.diff.missing_new_line2-u +test/fixtures/new-chef +test/fixtures/new-chef2 +test/fixtures/old-chef +test/fixtures/old-chef2 +test/test_block.rb +test/test_change.rb +test/test_diff.rb +test/test_helper.rb +test/test_hunk.rb +test/test_issues.rb +test/test_lcs.rb +test/test_ldiff.rb +test/test_patch.rb +test/test_sdiff.rb +test/test_traverse_balanced.rb +test/test_traverse_sequences.rb diff --git a/Rakefile b/Rakefile index b9da24b5..95b88c36 100644 --- a/Rakefile +++ b/Rakefile @@ -1,17 +1,16 @@ require "rubygems" -require "rspec" -require "rspec/core/rake_task" require "hoe" require "rake/clean" require "rdoc/task" +require "minitest/test_task" Hoe.plugin :halostatue -Hoe.plugin :rubygems Hoe.plugins.delete :debug Hoe.plugins.delete :newb Hoe.plugins.delete :publish Hoe.plugins.delete :signing +Hoe.plugins.delete :test hoe = Hoe.spec "diff-lcs" do developer("Austin Ziegler", "halostatue@gmail.com") @@ -27,10 +26,12 @@ hoe = Hoe.spec "diff-lcs" do } extra_dev_deps << ["hoe", "~> 4.0"] - extra_dev_deps << ["hoe-halostatue", "~> 2.1", ">= 2.1.1"] - extra_dev_deps << ["rspec", ">= 2.0", "< 4"] + extra_dev_deps << ["hoe-halostatue", "~> 3.0"] + extra_dev_deps << ["minitest", "~> 6.0"] + extra_dev_deps << ["minitest-autotest", "~> 1.0"] + extra_dev_deps << ["minitest-focus", "~> 1.1"] extra_dev_deps << ["rake", ">= 10.0", "< 14"] - extra_dev_deps << ["rdoc", ">= 6.3.1", "< 7"] + extra_dev_deps << ["rdoc", ">= 6.0", "< 8"] extra_dev_deps << ["simplecov", "~> 0.9"] extra_dev_deps << ["simplecov-lcov", "~> 0.9"] extra_dev_deps << ["standard", "~> 1.50"] @@ -38,24 +39,33 @@ hoe = Hoe.spec "diff-lcs" do extra_dev_deps << ["fasterer", "~> 0.11"] end -desc "Run all specifications" -RSpec::Core::RakeTask.new(:spec) do |t| - rspec_dirs = %w[spec lib].join(":") - t.rspec_opts = ["-I#{rspec_dirs}"] -end +Minitest::TestTask.create :test +Minitest::TestTask.create :coverage do |t| + formatters = <<-RUBY.split($/).join(" ") + SimpleCov::Formatter::MultiFormatter.new([ + SimpleCov::Formatter::HTMLFormatter, + SimpleCov::Formatter::LcovFormatter, + SimpleCov::Formatter::SimpleFormatter + ]) + RUBY + t.test_prelude = <<-RUBY.split($/).join("; ") + require "simplecov" + require "simplecov-lcov" -namespace :spec do - desc "Runs test coverage. Only works Ruby 2.0+ and assumes 'simplecov' is installed." - task :coverage do - Rake::Task["spec"].execute + SimpleCov::Formatter::LcovFormatter.config do |config| + config.report_with_single_file = true + config.lcov_file_name = "lcov.info" end -end -task coverage: "spec:coverage" -Rake::Task["spec"].actions.uniq! { |a| a.source_location } + SimpleCov.start "test_frameworks" do + enable_coverage :branch + primary_coverage :branch + formatter #{formatters} + end + RUBY +end -task default: :spec unless Rake::Task["default"].prereqs.include?("spec") -task test: :spec unless Rake::Task["test"].prereqs.include?("spec") +task default: :test task :version do require "diff/lcs/version" @@ -64,10 +74,68 @@ end RDoc::Task.new do |config| config.title = "diff-lcs" - # config.main = "lib/diff/lcs.rb" config.main = "README.md" config.rdoc_dir = "doc" - config.rdoc_files = hoe.spec.require_paths - ["Manifest.txt"] + hoe.spec.extra_rdoc_files + config.rdoc_files = hoe.spec.require_paths + hoe.spec.extra_rdoc_files - + FileList["integration/golden/*.txt", "Manifest.txt"].to_a config.markup = "markdown" end task docs: :rerdoc + +def rspec_to_golden(file) + File.join("integration/golden", File.basename(file, "_spec.rb")) + ".txt" +end + +def normalize_rspec_output(data) + data + .gsub(/Randomized with seed \d+/, "Randomized with seed XXXXX") + .gsub(/Finished in [\d.]+ seconds/, "Finished in X.XXXXX seconds") + .gsub(/files took [\d.]+ seconds to load/, "files took X.XXXXX seconds to load") +end + +def unbundled(&block) + if defined?(Bundler) + Bundler.with_unbundled_env(&block) + else + block.call + end +end + +rspecs = FileList["integration/compare/*_spec.rb"] + +namespace :integration do + desc "Compare RSpec output with and without diff-lcs 2" + task :compare do + require "tempfile" + base = Tempfile.create("baseline") { _1.path } + work = Tempfile.create("working") { _1.path } + + unbundled { sh "gem install rspec" } + + rspecs.to_a.each do |rspec_file| + basename = File.basename(rspec_file, "_spec.rb") + + base_contents = unbundled { `integration/runner rspec #{rspec_file} 2>&1` } + base_contents = normalize_rspec_output(base_contents) + + work_contents = unbundled { `integration/runner rspec -Ilib -rdiff/lcs #{rspec_file} 2>&1` } + work_contents = normalize_rspec_output(work_contents) + + if base_contents == work_contents + puts "#{basename}: OK" + else + puts "#{basename}: FAIL" + + File.write(base, base_contents) + File.write(work, work_contents) + + unbundled { sh "integration/runner -Ilib bin/ldiff -U #{base} #{work}" } + end + end + end +end + +desc "Run RSpec integration tests with diff-lcs 2.0" +task integration: ["integration:compare"] do + sh "rspec -Ilib -r diff/lcs integration/*_spec.rb" +end diff --git a/diff-lcs.gemspec b/diff-lcs.gemspec index 32f58556..ce787f24 100644 --- a/diff-lcs.gemspec +++ b/diff-lcs.gemspec @@ -1,34 +1,36 @@ # -*- encoding: utf-8 -*- -# stub: diff-lcs 2.0.0.beta.1 ruby lib +# stub: diff-lcs 2.0.0.beta.2 ruby lib Gem::Specification.new do |s| s.name = "diff-lcs".freeze - s.version = "2.0.0.beta.1".freeze + s.version = "2.0.0.beta.2".freeze s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= - s.metadata = { "bug_tracker_uri" => "https://github.com/halostatue/diff-lcs/issues", "changelog_uri" => "https://github.com/halostatue/diff-lcs/blob/main/CHANGELOG.md", "homepage_uri" => "https://github.com/halostatue/diff-lcs", "rubygems_mfa_required" => "true", "source_code_uri" => "https://github.com/halostatue/diff-lcs" } if s.respond_to? :metadata= + s.metadata = { "bug_tracker_uri" => "https://github.com/halostatue/diff-lcs/issues", "changelog_uri" => "https://github.com/halostatue/diff-lcs/blob/main/CHANGELOG.md", "documentation_uri" => "https://halostatue.github.io/diff-lcs/", "rubygems_mfa_required" => "true", "source_code_uri" => "https://github.com/halostatue/diff-lcs" } if s.respond_to? :metadata= s.require_paths = ["lib".freeze] s.authors = ["Austin Ziegler".freeze] - s.date = "2025-12-31" + s.date = "1980-01-02" s.description = "Diff::LCS computes the difference between two Enumerable sequences using the McIlroy-Hunt longest common subsequence (LCS) algorithm. It includes utilities to create a simple HTML diff output format and a standard diff-like tool. This is release 1.6.1, providing a simple extension that allows for Diff::LCS::Change objects to be treated implicitly as arrays and fixes a number of formatting issues. Ruby versions below 2.5 are soft-deprecated, which means that older versions are no longer part of the CI test suite. If any changes have been introduced that break those versions, bug reports and patches will be accepted, but it will be up to the reporter to verify any fixes prior to release. The next major release will completely break compatibility.".freeze s.email = ["halostatue@gmail.com".freeze] s.executables = ["ldiff".freeze] s.extra_rdoc_files = ["CHANGELOG.md".freeze, "CODE_OF_CONDUCT.md".freeze, "CONTRIBUTING.md".freeze, "CONTRIBUTORS.md".freeze, "LICENCE.md".freeze, "Manifest.txt".freeze, "README.md".freeze, "SECURITY.md".freeze, "licenses/COPYING.txt".freeze, "licenses/artistic.txt".freeze, "licenses/dco.txt".freeze] - s.files = ["CHANGELOG.md".freeze, "CODE_OF_CONDUCT.md".freeze, "CONTRIBUTING.md".freeze, "CONTRIBUTORS.md".freeze, "LICENCE.md".freeze, "Manifest.txt".freeze, "README.md".freeze, "Rakefile".freeze, "SECURITY.md".freeze, "bin/ldiff".freeze, "lib/diff-lcs.rb".freeze, "lib/diff/lcs.rb".freeze, "lib/diff/lcs/array.rb".freeze, "lib/diff/lcs/block.rb".freeze, "lib/diff/lcs/callbacks.rb".freeze, "lib/diff/lcs/change.rb".freeze, "lib/diff/lcs/hunk.rb".freeze, "lib/diff/lcs/internals.rb".freeze, "lib/diff/lcs/ldiff.rb".freeze, "lib/diff/lcs/string.rb".freeze, "lib/diff/lcs/version.rb".freeze, "licenses/COPYING.txt".freeze, "licenses/artistic.txt".freeze, "licenses/dco.txt".freeze, "spec/change_spec.rb".freeze, "spec/diff_spec.rb".freeze, "spec/fixtures/123_x".freeze, "spec/fixtures/456_x".freeze, "spec/fixtures/aX".freeze, "spec/fixtures/bXaX".freeze, "spec/fixtures/ds1.csv".freeze, "spec/fixtures/ds2.csv".freeze, "spec/fixtures/empty".freeze, "spec/fixtures/file1.bin".freeze, "spec/fixtures/file2.bin".freeze, "spec/fixtures/four_lines".freeze, "spec/fixtures/four_lines_with_missing_new_line".freeze, "spec/fixtures/ldiff/diff.missing_new_line1-e".freeze, "spec/fixtures/ldiff/diff.missing_new_line1-f".freeze, "spec/fixtures/ldiff/diff.missing_new_line2-e".freeze, "spec/fixtures/ldiff/diff.missing_new_line2-f".freeze, "spec/fixtures/ldiff/error.diff.chef-e".freeze, "spec/fixtures/ldiff/error.diff.chef-f".freeze, "spec/fixtures/ldiff/error.diff.missing_new_line1-e".freeze, "spec/fixtures/ldiff/error.diff.missing_new_line1-f".freeze, "spec/fixtures/ldiff/error.diff.missing_new_line2-e".freeze, "spec/fixtures/ldiff/error.diff.missing_new_line2-f".freeze, "spec/fixtures/ldiff/output.diff".freeze, "spec/fixtures/ldiff/output.diff-c".freeze, "spec/fixtures/ldiff/output.diff-u".freeze, "spec/fixtures/ldiff/output.diff.bin1".freeze, "spec/fixtures/ldiff/output.diff.bin1-c".freeze, "spec/fixtures/ldiff/output.diff.bin1-e".freeze, "spec/fixtures/ldiff/output.diff.bin1-f".freeze, "spec/fixtures/ldiff/output.diff.bin1-u".freeze, "spec/fixtures/ldiff/output.diff.bin2".freeze, "spec/fixtures/ldiff/output.diff.bin2-c".freeze, "spec/fixtures/ldiff/output.diff.bin2-e".freeze, "spec/fixtures/ldiff/output.diff.bin2-f".freeze, "spec/fixtures/ldiff/output.diff.bin2-u".freeze, "spec/fixtures/ldiff/output.diff.chef".freeze, "spec/fixtures/ldiff/output.diff.chef-c".freeze, "spec/fixtures/ldiff/output.diff.chef-u".freeze, "spec/fixtures/ldiff/output.diff.chef2".freeze, "spec/fixtures/ldiff/output.diff.chef2-c".freeze, "spec/fixtures/ldiff/output.diff.chef2-d".freeze, "spec/fixtures/ldiff/output.diff.chef2-u".freeze, "spec/fixtures/ldiff/output.diff.empty.vs.four_lines".freeze, "spec/fixtures/ldiff/output.diff.empty.vs.four_lines-c".freeze, "spec/fixtures/ldiff/output.diff.empty.vs.four_lines-e".freeze, "spec/fixtures/ldiff/output.diff.empty.vs.four_lines-f".freeze, "spec/fixtures/ldiff/output.diff.empty.vs.four_lines-u".freeze, "spec/fixtures/ldiff/output.diff.four_lines.vs.empty".freeze, "spec/fixtures/ldiff/output.diff.four_lines.vs.empty-c".freeze, "spec/fixtures/ldiff/output.diff.four_lines.vs.empty-e".freeze, "spec/fixtures/ldiff/output.diff.four_lines.vs.empty-f".freeze, "spec/fixtures/ldiff/output.diff.four_lines.vs.empty-u".freeze, "spec/fixtures/ldiff/output.diff.issue95_trailing_context".freeze, "spec/fixtures/ldiff/output.diff.issue95_trailing_context-c".freeze, "spec/fixtures/ldiff/output.diff.issue95_trailing_context-e".freeze, "spec/fixtures/ldiff/output.diff.issue95_trailing_context-f".freeze, "spec/fixtures/ldiff/output.diff.issue95_trailing_context-u".freeze, "spec/fixtures/ldiff/output.diff.missing_new_line1".freeze, "spec/fixtures/ldiff/output.diff.missing_new_line1-c".freeze, "spec/fixtures/ldiff/output.diff.missing_new_line1-e".freeze, "spec/fixtures/ldiff/output.diff.missing_new_line1-f".freeze, "spec/fixtures/ldiff/output.diff.missing_new_line1-u".freeze, "spec/fixtures/ldiff/output.diff.missing_new_line2".freeze, "spec/fixtures/ldiff/output.diff.missing_new_line2-c".freeze, "spec/fixtures/ldiff/output.diff.missing_new_line2-e".freeze, "spec/fixtures/ldiff/output.diff.missing_new_line2-f".freeze, "spec/fixtures/ldiff/output.diff.missing_new_line2-u".freeze, "spec/fixtures/new-chef".freeze, "spec/fixtures/new-chef2".freeze, "spec/fixtures/old-chef".freeze, "spec/fixtures/old-chef2".freeze, "spec/hunk_spec.rb".freeze, "spec/issues_spec.rb".freeze, "spec/lcs_spec.rb".freeze, "spec/ldiff_spec.rb".freeze, "spec/patch_spec.rb".freeze, "spec/sdiff_spec.rb".freeze, "spec/spec_helper.rb".freeze, "spec/traverse_balanced_spec.rb".freeze, "spec/traverse_sequences_spec.rb".freeze] + s.files = ["CHANGELOG.md".freeze, "CODE_OF_CONDUCT.md".freeze, "CONTRIBUTING.md".freeze, "CONTRIBUTORS.md".freeze, "LICENCE.md".freeze, "Manifest.txt".freeze, "README.md".freeze, "Rakefile".freeze, "SECURITY.md".freeze, "bin/ldiff".freeze, "integration/compare/array_diff_spec.rb".freeze, "integration/compare/hash_diff_spec.rb".freeze, "integration/compare/string_diff_spec.rb".freeze, "integration/rspec_differ_spec.rb".freeze, "integration/rspec_expectations_spec.rb".freeze, "integration/runner".freeze, "lib/diff-lcs.rb".freeze, "lib/diff/lcs.rb".freeze, "lib/diff/lcs/array.rb".freeze, "lib/diff/lcs/block.rb".freeze, "lib/diff/lcs/callbacks.rb".freeze, "lib/diff/lcs/change.rb".freeze, "lib/diff/lcs/hunk.rb".freeze, "lib/diff/lcs/internals.rb".freeze, "lib/diff/lcs/ldiff.rb".freeze, "lib/diff/lcs/string.rb".freeze, "lib/diff/lcs/version.rb".freeze, "licenses/COPYING.txt".freeze, "licenses/artistic.txt".freeze, "licenses/dco.txt".freeze, "spec/change_spec.rb".freeze, "spec/diff_spec.rb".freeze, "spec/hunk_spec.rb".freeze, "spec/issues_spec.rb".freeze, "spec/lcs_spec.rb".freeze, "spec/ldiff_spec.rb".freeze, "spec/patch_spec.rb".freeze, "spec/sdiff_spec.rb".freeze, "spec/spec_helper.rb".freeze, "spec/traverse_balanced_spec.rb".freeze, "spec/traverse_sequences_spec.rb".freeze, "test/fixtures/123_x".freeze, "test/fixtures/456_x".freeze, "test/fixtures/aX".freeze, "test/fixtures/bXaX".freeze, "test/fixtures/ds1.csv".freeze, "test/fixtures/ds2.csv".freeze, "test/fixtures/empty".freeze, "test/fixtures/file1.bin".freeze, "test/fixtures/file2.bin".freeze, "test/fixtures/four_lines".freeze, "test/fixtures/four_lines_with_missing_new_line".freeze, "test/fixtures/ldiff/diff.missing_new_line1-e".freeze, "test/fixtures/ldiff/diff.missing_new_line1-f".freeze, "test/fixtures/ldiff/diff.missing_new_line2-e".freeze, "test/fixtures/ldiff/diff.missing_new_line2-f".freeze, "test/fixtures/ldiff/error.diff.chef-e".freeze, "test/fixtures/ldiff/error.diff.chef-f".freeze, "test/fixtures/ldiff/error.diff.missing_new_line1-e".freeze, "test/fixtures/ldiff/error.diff.missing_new_line1-f".freeze, "test/fixtures/ldiff/error.diff.missing_new_line2-e".freeze, "test/fixtures/ldiff/error.diff.missing_new_line2-f".freeze, "test/fixtures/ldiff/output.diff".freeze, "test/fixtures/ldiff/output.diff-c".freeze, "test/fixtures/ldiff/output.diff-u".freeze, "test/fixtures/ldiff/output.diff.bin1".freeze, "test/fixtures/ldiff/output.diff.bin1-c".freeze, "test/fixtures/ldiff/output.diff.bin1-e".freeze, "test/fixtures/ldiff/output.diff.bin1-f".freeze, "test/fixtures/ldiff/output.diff.bin1-u".freeze, "test/fixtures/ldiff/output.diff.bin2".freeze, "test/fixtures/ldiff/output.diff.bin2-c".freeze, "test/fixtures/ldiff/output.diff.bin2-e".freeze, "test/fixtures/ldiff/output.diff.bin2-f".freeze, "test/fixtures/ldiff/output.diff.bin2-u".freeze, "test/fixtures/ldiff/output.diff.chef".freeze, "test/fixtures/ldiff/output.diff.chef-c".freeze, "test/fixtures/ldiff/output.diff.chef-u".freeze, "test/fixtures/ldiff/output.diff.chef2".freeze, "test/fixtures/ldiff/output.diff.chef2-c".freeze, "test/fixtures/ldiff/output.diff.chef2-d".freeze, "test/fixtures/ldiff/output.diff.chef2-u".freeze, "test/fixtures/ldiff/output.diff.empty.vs.four_lines".freeze, "test/fixtures/ldiff/output.diff.empty.vs.four_lines-c".freeze, "test/fixtures/ldiff/output.diff.empty.vs.four_lines-e".freeze, "test/fixtures/ldiff/output.diff.empty.vs.four_lines-f".freeze, "test/fixtures/ldiff/output.diff.empty.vs.four_lines-u".freeze, "test/fixtures/ldiff/output.diff.four_lines.vs.empty".freeze, "test/fixtures/ldiff/output.diff.four_lines.vs.empty-c".freeze, "test/fixtures/ldiff/output.diff.four_lines.vs.empty-e".freeze, "test/fixtures/ldiff/output.diff.four_lines.vs.empty-f".freeze, "test/fixtures/ldiff/output.diff.four_lines.vs.empty-u".freeze, "test/fixtures/ldiff/output.diff.issue95_trailing_context".freeze, "test/fixtures/ldiff/output.diff.issue95_trailing_context-c".freeze, "test/fixtures/ldiff/output.diff.issue95_trailing_context-e".freeze, "test/fixtures/ldiff/output.diff.issue95_trailing_context-f".freeze, "test/fixtures/ldiff/output.diff.issue95_trailing_context-u".freeze, "test/fixtures/ldiff/output.diff.missing_new_line1".freeze, "test/fixtures/ldiff/output.diff.missing_new_line1-c".freeze, "test/fixtures/ldiff/output.diff.missing_new_line1-e".freeze, "test/fixtures/ldiff/output.diff.missing_new_line1-f".freeze, "test/fixtures/ldiff/output.diff.missing_new_line1-u".freeze, "test/fixtures/ldiff/output.diff.missing_new_line2".freeze, "test/fixtures/ldiff/output.diff.missing_new_line2-c".freeze, "test/fixtures/ldiff/output.diff.missing_new_line2-e".freeze, "test/fixtures/ldiff/output.diff.missing_new_line2-f".freeze, "test/fixtures/ldiff/output.diff.missing_new_line2-u".freeze, "test/fixtures/new-chef".freeze, "test/fixtures/new-chef2".freeze, "test/fixtures/old-chef".freeze, "test/fixtures/old-chef2".freeze, "test/test_block.rb".freeze, "test/test_change.rb".freeze, "test/test_diff.rb".freeze, "test/test_helper.rb".freeze, "test/test_hunk.rb".freeze, "test/test_issues.rb".freeze, "test/test_lcs.rb".freeze, "test/test_ldiff.rb".freeze, "test/test_patch.rb".freeze, "test/test_sdiff.rb".freeze, "test/test_traverse_balanced.rb".freeze, "test/test_traverse_sequences.rb".freeze] s.homepage = "https://github.com/halostatue/diff-lcs".freeze s.licenses = ["MIT".freeze, "Artistic-1.0-Perl".freeze, "GPL-2.0-or-later".freeze] s.rdoc_options = ["--main".freeze, "README.md".freeze] s.required_ruby_version = Gem::Requirement.new([">= 3.2.0".freeze, "< 5".freeze]) - s.rubygems_version = "3.6.9".freeze + s.rubygems_version = "4.0.4".freeze s.summary = "Diff::LCS computes the difference between two Enumerable sequences using the McIlroy-Hunt longest common subsequence (LCS) algorithm".freeze s.specification_version = 4 s.add_development_dependency(%q.freeze, ["~> 4.0".freeze]) - s.add_development_dependency(%q.freeze, ["~> 2.1".freeze, ">= 2.1.1".freeze]) - s.add_development_dependency(%q.freeze, [">= 2.0".freeze, "< 4".freeze]) + s.add_development_dependency(%q.freeze, ["~> 3.0".freeze]) + s.add_development_dependency(%q.freeze, ["~> 6.0".freeze]) + s.add_development_dependency(%q.freeze, ["~> 1.0".freeze]) + s.add_development_dependency(%q.freeze, ["~> 1.1".freeze]) s.add_development_dependency(%q.freeze, [">= 10.0".freeze, "< 14".freeze]) - s.add_development_dependency(%q.freeze, [">= 6.3.1".freeze, "< 7".freeze]) + s.add_development_dependency(%q.freeze, [">= 6.0".freeze, "< 8".freeze]) s.add_development_dependency(%q.freeze, ["~> 0.9".freeze]) s.add_development_dependency(%q.freeze, ["~> 0.9".freeze]) s.add_development_dependency(%q.freeze, ["~> 1.50".freeze]) diff --git a/integration/compare/array_diff_spec.rb b/integration/compare/array_diff_spec.rb new file mode 100644 index 00000000..07221b3c --- /dev/null +++ b/integration/compare/array_diff_spec.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +RSpec.describe "Array diff failure" do + it "shows diff for array with different elements" do + expected = ["apple", "banana", "cherry", "date", "elderberry"] + actual = ["apple", "blueberry", "cherry", "date", "elderberry"] + + expect(actual).to eq(expected) + end +end diff --git a/integration/compare/hash_diff_spec.rb b/integration/compare/hash_diff_spec.rb new file mode 100644 index 00000000..d1457eb8 --- /dev/null +++ b/integration/compare/hash_diff_spec.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +RSpec.describe "Hash diff failure" do + it "shows diff for nested hash mismatch" do + expected = { + name: "John", + age: 30, + address: { + city: "New York", + zip: "10001" + } + } + + actual = { + name: "John", + age: 35, + address: { + city: "Boston", + zip: "10001" + } + } + + expect(actual).to eq(expected) + end +end diff --git a/integration/compare/string_diff_spec.rb b/integration/compare/string_diff_spec.rb new file mode 100644 index 00000000..d6128d65 --- /dev/null +++ b/integration/compare/string_diff_spec.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +RSpec.describe "String diff failure" do + it "shows diff for multiline string mismatch" do + expected = "line1\nline2\nline3\nline4\nline5\n" + actual = "line1\nchanged\nline3\nline4\nline5\n" + + expect(actual).to eq(expected) + end +end diff --git a/integration/rspec_differ_spec.rb b/integration/rspec_differ_spec.rb new file mode 100644 index 00000000..3caa8147 --- /dev/null +++ b/integration/rspec_differ_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +# Integration test to verify diff-lcs 2.0 works with RSpec's differ +# This runs RSpec with diff-lcs 1.x installed but loads the repo version + +RSpec.describe "Diff::LCS 2.0 with RSpec::Support::Differ" do + let(:differ) { RSpec::Support::Differ.new } + + it "produces diff output for multiline strings" do + expected = "foo\nzap\nbar\n" + actual = "foo\nbar\nzap\n" + + diff = differ.diff(actual, expected) + + expect(diff).to be_a(String) + expect(diff).not_to be_empty + end + + it "handles identical strings" do + str = "same\n" + diff = differ.diff(str, str) + + # May return empty or just newline depending on implementation + expect(diff.strip).to be_empty + end +end diff --git a/integration/rspec_expectations_spec.rb b/integration/rspec_expectations_spec.rb new file mode 100644 index 00000000..8e4772e2 --- /dev/null +++ b/integration/rspec_expectations_spec.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +# Integration test for RSpec expectation failures that use diff-lcs +# Verifies that diff output is generated correctly with diff-lcs 2.0 + +RSpec.describe "Diff::LCS 2.0 with RSpec expectations" do + it "produces diff for failed multiline string equality" do + expect { + expect("foo\nbar\nbaz").to eq("foo\nqux\nbaz") + }.to raise_error(RSpec::Expectations::ExpectationNotMetError) do |error| + expect(error.message).to include("Diff:") + expect(error.message).to match(/[-+](qux|bar)/) + end + end + + it "produces diff for failed hash equality" do + expect { + expect({a: 1, b: 2}).to eq({a: 1, b: 3}) + }.to raise_error(RSpec::Expectations::ExpectationNotMetError) do |error| + expect(error.message).to include("Diff:") + end + end + + it "does not crash when comparing complex objects" do + obj1 = {a: [1, 2, 3], b: "test"} + obj2 = {a: [1, 4, 3], b: "test"} + + expect { + expect(obj1).to eq(obj2) + }.to raise_error(RSpec::Expectations::ExpectationNotMetError) + end +end diff --git a/integration/runner b/integration/runner new file mode 100755 index 00000000..cc026df3 --- /dev/null +++ b/integration/runner @@ -0,0 +1,20 @@ +#!/usr/bin/env ruby + +require "rbconfig" +require "rubygems" + +if ENV["CI"] + vendor_dir = Dir.glob(File.join(Dir.pwd, "vendor", "bundle", "{ruby,jruby,truffleruby}", "*")).max + unless vendor_dir && Dir.exist?(vendor_dir) + fail "vendor bundle not found; expected vendor/bundle/ruby/*" + end + + # Prefer vendored gems, fall back to system gem dir + ENV["GEM_HOME"] = vendor_dir + ENV["GEM_PATH"] = "#{vendor_dir}:#{Gem.default_dir}" + + # Ensure Gem.path is updated for the running process (affects Gem.lookup) + Gem.use_paths(ENV["GEM_HOME"], ENV["GEM_PATH"].split(":")) +end + +exec RbConfig.ruby, "-S", *ARGV diff --git a/lib/diff/lcs/change.rb b/lib/diff/lcs/change.rb index 99ddb5e2..737d677a 100644 --- a/lib/diff/lcs/change.rb +++ b/lib/diff/lcs/change.rb @@ -10,7 +10,7 @@ class Diff::LCS::Change # (changed), '<' (tail changes from first sequence), or '>' (tail changes from second # sequence). The last two ('<>') are only found with Diff::LCS::diff and # Diff::LCS::sdiff. - VALID_ACTIONS = %w[+ - = ! > <].freeze + VALID_ACTIONS = %w[= - + ! > <].freeze def self.valid_action?(action) = VALID_ACTIONS.include?(action) @@ -37,6 +37,9 @@ def inspect(*_args) = "#<#{self.class}: #{to_a.inspect}>" def to_a = [action, position, element] alias_method :to_ary, :to_a + alias_method :deconstruct, :to_a + + def deconstruct_keys(_) = {action:, position:, element:} def self.from_a(arr) case arr @@ -59,8 +62,8 @@ def ==(other) end def <=>(other) - r = action <=> other.action - r = position <=> other.position if r.zero? + r = position <=> other.position + r = VALID_ACTIONS.index(action) <=> VALID_ACTIONS.index(other.action) if r.zero? r = element <=> other.element if r.zero? r end @@ -111,28 +114,27 @@ def initialize(action:, old_position:, old_element:, new_position:, new_element: def to_a = [action, [old_position, old_element], [new_position, new_element]] alias_method :to_ary, :to_a + alias_method :deconstruct, :to_a + + def deconstruct_keys(_) = {action:, old_position:, old_element:, new_position:, new_element:} def self.from_a(arr) = Diff::LCS::Change.from_a(arr) # Simplifies a context change for use in some diff callbacks. '<' actions are converted # to '-' and '>' actions are converted to '+'. def self.simplify(event) - ea = event.to_a - - case ea[0] + case event.action when "-" - ea[2][1] = nil + event.with(new_element: nil) when "<" - ea[0] = "-" - ea[2][1] = nil + event.with(action: "-", new_element: nil) when "+" - ea[1][1] = nil + event.with(old_element: nil) when ">" - ea[0] = "+" - ea[1][1] = nil + event.with(action: "+", old_element: nil) + else + event end - - from_a(ea) end def ==(other) @@ -145,9 +147,12 @@ def ==(other) end def <=>(other) - r = action <=> other.action - r = old_position <=> other.old_position if r.zero? + r = old_position <=> other.old_position r = new_position <=> other.new_position if r.zero? + if r.zero? + r = Diff::LCS::Change::VALID_ACTIONS.index(action) <=> + Diff::LCS::Change::VALID_ACTIONS.index(other.action) + end r = old_element <=> other.old_element if r.zero? r = new_element <=> other.new_element if r.zero? r diff --git a/lib/diff/lcs/ldiff.rb b/lib/diff/lcs/ldiff.rb index 0126cc55..f541a6a8 100644 --- a/lib/diff/lcs/ldiff.rb +++ b/lib/diff/lcs/ldiff.rb @@ -16,9 +16,14 @@ class Diff::LCS::Ldiff # :nodoc: MIT licence. COPYRIGHT - InputInfo = Struct.new(:filename, :data, :stat) do + InputInfo = Struct.new(:filename, :data, :mtime) do def initialize(filename) - super(filename, ::File.read(filename), ::File.stat(filename)) + if filename == "-" + super("", $stdin.read, Time.now) + return + end + + super(filename, ::File.read(filename), ::File.stat(filename).mtime) end end @@ -142,9 +147,9 @@ def diff?(info_old, info_new, format, output, binary: nil, lines: 0) output << "Files #{info_old.filename} and #{info_new.filename} differ\n" return true when :unified, :context - ft = info_old.stat.mtime.localtime.strftime("%Y-%m-%d %H:%M:%S.000000000 %z") + ft = info_old.mtime.localtime.strftime("%Y-%m-%d %H:%M:%S.000000000 %z") output << "#{char_old} #{info_old.filename}\t#{ft}\n" - ft = info_new.stat.mtime.localtime.strftime("%Y-%m-%d %H:%M:%S.000000000 %z") + ft = info_new.mtime.localtime.strftime("%Y-%m-%d %H:%M:%S.000000000 %z") output << "#{char_new} #{info_new.filename}\t#{ft}\n" when :ed output = [] diff --git a/lib/diff/lcs/version.rb b/lib/diff/lcs/version.rb index 9796d917..c6684cd2 100644 --- a/lib/diff/lcs/version.rb +++ b/lib/diff/lcs/version.rb @@ -2,6 +2,6 @@ module Diff module LCS - VERSION = "2.0.0.beta.1" + VERSION = "2.0.0.beta.2" end end diff --git a/spec/fixtures/ldiff/output.diff-c b/spec/fixtures/ldiff/output.diff-c deleted file mode 100644 index 0e1ad998..00000000 --- a/spec/fixtures/ldiff/output.diff-c +++ /dev/null @@ -1,7 +0,0 @@ -*** spec/fixtures/aX 2020-06-23 11:15:32.000000000 -0400 ---- spec/fixtures/bXaX 2020-06-23 11:15:32.000000000 -0400 -*************** -*** 1 **** -! aX ---- 1 ---- -! bXaX diff --git a/spec/fixtures/ldiff/output.diff-u b/spec/fixtures/ldiff/output.diff-u deleted file mode 100644 index b84f7180..00000000 --- a/spec/fixtures/ldiff/output.diff-u +++ /dev/null @@ -1,5 +0,0 @@ ---- spec/fixtures/aX 2020-06-23 11:15:32.000000000 -0400 -+++ spec/fixtures/bXaX 2020-06-23 11:15:32.000000000 -0400 -@@ -1 +1 @@ --aX -+bXaX diff --git a/spec/fixtures/ldiff/output.diff.bin2 b/spec/fixtures/ldiff/output.diff.bin2 deleted file mode 100644 index 41b625cd..00000000 --- a/spec/fixtures/ldiff/output.diff.bin2 +++ /dev/null @@ -1 +0,0 @@ -Binary files spec/fixtures/file1.bin and spec/fixtures/file2.bin differ diff --git a/spec/fixtures/ldiff/output.diff.bin2-c b/spec/fixtures/ldiff/output.diff.bin2-c deleted file mode 100644 index 41b625cd..00000000 --- a/spec/fixtures/ldiff/output.diff.bin2-c +++ /dev/null @@ -1 +0,0 @@ -Binary files spec/fixtures/file1.bin and spec/fixtures/file2.bin differ diff --git a/spec/fixtures/ldiff/output.diff.bin2-e b/spec/fixtures/ldiff/output.diff.bin2-e deleted file mode 100644 index 41b625cd..00000000 --- a/spec/fixtures/ldiff/output.diff.bin2-e +++ /dev/null @@ -1 +0,0 @@ -Binary files spec/fixtures/file1.bin and spec/fixtures/file2.bin differ diff --git a/spec/fixtures/ldiff/output.diff.bin2-f b/spec/fixtures/ldiff/output.diff.bin2-f deleted file mode 100644 index 41b625cd..00000000 --- a/spec/fixtures/ldiff/output.diff.bin2-f +++ /dev/null @@ -1 +0,0 @@ -Binary files spec/fixtures/file1.bin and spec/fixtures/file2.bin differ diff --git a/spec/fixtures/ldiff/output.diff.bin2-u b/spec/fixtures/ldiff/output.diff.bin2-u deleted file mode 100644 index 41b625cd..00000000 --- a/spec/fixtures/ldiff/output.diff.bin2-u +++ /dev/null @@ -1 +0,0 @@ -Binary files spec/fixtures/file1.bin and spec/fixtures/file2.bin differ diff --git a/spec/fixtures/ldiff/output.diff.chef-u b/spec/fixtures/ldiff/output.diff.chef-u deleted file mode 100644 index dbacd889..00000000 --- a/spec/fixtures/ldiff/output.diff.chef-u +++ /dev/null @@ -1,9 +0,0 @@ ---- spec/fixtures/old-chef 2020-06-23 23:18:20.000000000 -0400 -+++ spec/fixtures/new-chef 2020-06-23 23:18:20.000000000 -0400 -@@ -1,4 +1,4 @@ - { - "name": "x", -- "description": "hi" -+ "description": "lo" - } -\ No newline at end of file diff --git a/spec/fixtures/ldiff/output.diff.empty.vs.four_lines-c b/spec/fixtures/ldiff/output.diff.empty.vs.four_lines-c deleted file mode 100644 index be0e8274..00000000 --- a/spec/fixtures/ldiff/output.diff.empty.vs.four_lines-c +++ /dev/null @@ -1,9 +0,0 @@ -*** spec/fixtures/empty 2025-01-31 12:14:52.856031635 +0100 ---- spec/fixtures/four_lines 2025-01-31 12:13:45.476036544 +0100 -*************** -*** 0 **** ---- 1,4 ---- -+ one -+ two -+ three -+ four diff --git a/spec/fixtures/ldiff/output.diff.empty.vs.four_lines-u b/spec/fixtures/ldiff/output.diff.empty.vs.four_lines-u deleted file mode 100644 index 60bd55cc..00000000 --- a/spec/fixtures/ldiff/output.diff.empty.vs.four_lines-u +++ /dev/null @@ -1,7 +0,0 @@ ---- spec/fixtures/empty 2025-01-31 12:14:52.856031635 +0100 -+++ spec/fixtures/four_lines 2025-01-31 12:13:45.476036544 +0100 -@@ -0,0 +1,4 @@ -+one -+two -+three -+four diff --git a/spec/fixtures/ldiff/output.diff.four_lines.vs.empty-c b/spec/fixtures/ldiff/output.diff.four_lines.vs.empty-c deleted file mode 100644 index b216344d..00000000 --- a/spec/fixtures/ldiff/output.diff.four_lines.vs.empty-c +++ /dev/null @@ -1,9 +0,0 @@ -*** spec/fixtures/four_lines 2025-01-31 12:13:45.476036544 +0100 ---- spec/fixtures/empty 2025-01-31 12:14:52.856031635 +0100 -*************** -*** 1,4 **** -- one -- two -- three -- four ---- 0 ---- diff --git a/spec/fixtures/ldiff/output.diff.four_lines.vs.empty-u b/spec/fixtures/ldiff/output.diff.four_lines.vs.empty-u deleted file mode 100644 index 79e6d752..00000000 --- a/spec/fixtures/ldiff/output.diff.four_lines.vs.empty-u +++ /dev/null @@ -1,7 +0,0 @@ ---- spec/fixtures/four_lines 2025-01-31 12:13:45.476036544 +0100 -+++ spec/fixtures/empty 2025-01-31 12:14:52.856031635 +0100 -@@ -1,4 +0,0 @@ --one --two --three --four diff --git a/spec/fixtures/ldiff/output.diff.issue95_trailing_context-c b/spec/fixtures/ldiff/output.diff.issue95_trailing_context-c deleted file mode 100644 index 4b759fa6..00000000 --- a/spec/fixtures/ldiff/output.diff.issue95_trailing_context-c +++ /dev/null @@ -1,9 +0,0 @@ -*** spec/fixtures/123_x 2025-01-31 17:00:17.070615716 +0100 ---- spec/fixtures/456_x 2025-01-31 16:58:26.380624827 +0100 -*************** -*** 1,2 **** -! 123 - x ---- 1,2 ---- -! 456 - x diff --git a/spec/fixtures/ldiff/output.diff.issue95_trailing_context-u b/spec/fixtures/ldiff/output.diff.issue95_trailing_context-u deleted file mode 100644 index 7fbf0e2e..00000000 --- a/spec/fixtures/ldiff/output.diff.issue95_trailing_context-u +++ /dev/null @@ -1,6 +0,0 @@ ---- spec/fixtures/123_x 2025-01-31 17:00:17.070615716 +0100 -+++ spec/fixtures/456_x 2025-01-31 16:58:26.380624827 +0100 -@@ -1,2 +1,2 @@ --123 -+456 - x diff --git a/spec/fixtures/ldiff/output.diff.missing_new_line1-u b/spec/fixtures/ldiff/output.diff.missing_new_line1-u deleted file mode 100644 index 010518bf..00000000 --- a/spec/fixtures/ldiff/output.diff.missing_new_line1-u +++ /dev/null @@ -1,9 +0,0 @@ ---- spec/fixtures/four_lines 2025-01-31 12:17:43.926013315 +0100 -+++ spec/fixtures/four_lines_with_missing_new_line 2025-01-31 12:17:43.926013315 +0100 -@@ -1,4 +1,4 @@ - one - two - three --four -+four -\ No newline at end of file diff --git a/spec/fixtures/ldiff/output.diff.missing_new_line2-u b/spec/fixtures/ldiff/output.diff.missing_new_line2-u deleted file mode 100644 index 2481a9e0..00000000 --- a/spec/fixtures/ldiff/output.diff.missing_new_line2-u +++ /dev/null @@ -1,9 +0,0 @@ ---- spec/fixtures/four_lines_with_missing_new_line 2025-01-31 12:17:43.926013315 +0100 -+++ spec/fixtures/four_lines 2025-01-31 12:17:43.926013315 +0100 -@@ -1,4 +1,4 @@ - one - two - three --four -\ No newline at end of file -+four diff --git a/spec/hunk_spec.rb b/spec/hunk_spec.rb index e74b7bb6..34f8a521 100644 --- a/spec/hunk_spec.rb +++ b/spec/hunk_spec.rb @@ -2,71 +2,69 @@ require "spec_helper" -if String.method_defined?(:encoding) - require "diff/lcs/hunk" +require "diff/lcs/hunk" - describe Diff::LCS::Hunk do - let(:old_data) { ["Tu a un carté avec {count} itéms".encode("UTF-16LE")] } - let(:new_data) { ["Tu a un carte avec {count} items".encode("UTF-16LE")] } - let(:pieces) { Diff::LCS.diff old_data, new_data } - let(:hunk) { Diff::LCS::Hunk.new(old_data, new_data, pieces[0], 3, 0) } +describe Diff::LCS::Hunk do + let(:old_data) { ["Tu a un carté avec {count} itéms".encode("UTF-16LE")] } + let(:new_data) { ["Tu a un carte avec {count} items".encode("UTF-16LE")] } + let(:pieces) { Diff::LCS.diff old_data, new_data } + let(:hunk) { Diff::LCS::Hunk.new(old_data, new_data, pieces[0], 3, 0) } - it "produces a unified diff from the two pieces" do - expected = <<-EXPECTED.gsub(/^\s+/, "").encode("UTF-16LE").chomp + it "produces a unified diff from the two pieces" do + expected = <<-EXPECTED.gsub(/^\s+/, "").encode("UTF-16LE").chomp @@ -1 +1 @@ -Tu a un carté avec {count} itéms +Tu a un carte avec {count} items - EXPECTED + EXPECTED - expect(hunk.diff(:unified)).to eq(expected) - end + expect(hunk.diff(:unified)).to eq(expected) + end - it "produces a unified diff from the two pieces (last entry)" do - expected = <<-EXPECTED.gsub(/^\s+/, "").encode("UTF-16LE").chomp + it "produces a unified diff from the two pieces (last entry)" do + expected = <<-EXPECTED.gsub(/^\s+/, "").encode("UTF-16LE").chomp @@ -1 +1 @@ -Tu a un carté avec {count} itéms +Tu a un carte avec {count} items \\ No newline at end of file - EXPECTED + EXPECTED - expect(hunk.diff(:unified, true)).to eq(expected) - end + expect(hunk.diff(:unified, true)).to eq(expected) + end - it "produces a context diff from the two pieces" do - expected = <<-EXPECTED.gsub(/^\s+/, "").encode("UTF-16LE").chomp + it "produces a context diff from the two pieces" do + expected = <<-EXPECTED.gsub(/^\s+/, "").encode("UTF-16LE").chomp *************** *** 1 **** ! Tu a un carté avec {count} itéms --- 1 ---- ! Tu a un carte avec {count} items - EXPECTED + EXPECTED - expect(hunk.diff(:context)).to eq(expected) - end + expect(hunk.diff(:context)).to eq(expected) + end - it "produces an old diff from the two pieces" do - expected = <<-EXPECTED.gsub(/^ +/, "").encode("UTF-16LE").chomp + it "produces an old diff from the two pieces" do + expected = <<-EXPECTED.gsub(/^ +/, "").encode("UTF-16LE").chomp 1c1 < Tu a un carté avec {count} itéms --- > Tu a un carte avec {count} items - EXPECTED + EXPECTED - expect(hunk.diff(:old)).to eq(expected) - end + expect(hunk.diff(:old)).to eq(expected) + end - context "with empty first data set" do - let(:old_data) { [] } + context "with empty first data set" do + let(:old_data) { [] } - it "produces a unified diff" do - expected = <<-EXPECTED.gsub(/^\s+/, "").encode("UTF-16LE").chomp + it "produces a unified diff" do + expected = <<-EXPECTED.gsub(/^\s+/, "").encode("UTF-16LE").chomp @@ -0,0 +1 @@ +Tu a un carte avec {count} items - EXPECTED + EXPECTED - expect(hunk.diff(:unified)).to eq(expected) - end + expect(hunk.diff(:unified)).to eq(expected) end end end diff --git a/spec/ldiff_spec.rb b/spec/ldiff_spec.rb index 2367220c..ead2b1c2 100644 --- a/spec/ldiff_spec.rb +++ b/spec/ldiff_spec.rb @@ -27,11 +27,11 @@ def self.test_ldiff(fixture) desc = [ fixture[:flag], - "spec/fixtures/#{fixture[:left]}", - "spec/fixtures/#{fixture[:right]}", + "test/fixtures/#{fixture[:left]}", + "test/fixtures/#{fixture[:right]}", "#", "=>", - "spec/fixtures/ldiff/output.#{fixture[:name]}#{fixture[:flag]}" + "test/fixtures/ldiff/output.#{fixture[:name]}#{fixture[:flag]}" ].join(" ") it desc do @@ -49,7 +49,7 @@ def self.test_ldiff(fixture) def read_fixture(options, mode: "output", allow_missing: false) fixture = options.fetch(:name) flag = options.fetch(:flag) - name = "spec/fixtures/ldiff/#{mode}.#{fixture}#{flag}" + name = "test/fixtures/ldiff/#{mode}.#{fixture}#{flag}" return "" if !::File.exist?(name) && allow_missing @@ -74,7 +74,7 @@ def clean_output_timestamp(data) ^ [-+*]{3} \s* - spec/fixtures/(\S+) + test/fixtures/(\S+) \s* \d{4}-\d\d-\d\d \s* @@ -82,7 +82,7 @@ def clean_output_timestamp(data) \s* (?:[-+]\d{4}|Z) }x, - '*** spec/fixtures/\1 0000-00-00 :00 =>:00 =>00.000000000 -0000' + '*** test/fixtures/\1 0000-00-00 :00 =>:00 =>00.000000000 -0000' ) end @@ -92,7 +92,7 @@ def run_ldiff(options) right = options.fetch(:right) stdout, stderr = capture_subprocess_io do - system("ruby -Ilib bin/ldiff #{flag} spec/fixtures/#{left} spec/fixtures/#{right}") + system("ruby -Ilib bin/ldiff #{flag} test/fixtures/#{left} test/fixtures/#{right}") end [clean_data(stdout, flag), stderr, $?.exitstatus] diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 9cde3ab9..bcee3bee 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,13 +1,13 @@ # frozen_string_literal: true -require "rubygems" -require "pathname" - -require "psych" if RUBY_VERSION >= "1.9" +$LOAD_PATH.unshift File.expand_path(__dir__, "../lib") +require "rubygems" require "simplecov" require "simplecov-lcov" +require "diff/lcs" + SimpleCov::Formatter::LcovFormatter.config do |config| config.report_with_single_file = true config.lcov_file_name = "lcov.info" @@ -23,12 +23,6 @@ ]) end -file = Pathname.new(__FILE__).expand_path -path = file.parent -parent = path.parent - -$:.unshift parent.join("lib") - module CaptureSubprocessIO def _synchronize yield @@ -62,8 +56,6 @@ def _capture_subprocess_io private :_capture_subprocess_io end -require "diff-lcs" - module Diff::LCS::SpecHelper def hello "hello" diff --git a/spec/fixtures/123_x b/test/fixtures/123_x similarity index 100% rename from spec/fixtures/123_x rename to test/fixtures/123_x diff --git a/spec/fixtures/456_x b/test/fixtures/456_x similarity index 100% rename from spec/fixtures/456_x rename to test/fixtures/456_x diff --git a/spec/fixtures/aX b/test/fixtures/aX similarity index 100% rename from spec/fixtures/aX rename to test/fixtures/aX diff --git a/spec/fixtures/bXaX b/test/fixtures/bXaX similarity index 100% rename from spec/fixtures/bXaX rename to test/fixtures/bXaX diff --git a/spec/fixtures/ds1.csv b/test/fixtures/ds1.csv similarity index 100% rename from spec/fixtures/ds1.csv rename to test/fixtures/ds1.csv diff --git a/spec/fixtures/ds2.csv b/test/fixtures/ds2.csv similarity index 100% rename from spec/fixtures/ds2.csv rename to test/fixtures/ds2.csv diff --git a/spec/fixtures/empty b/test/fixtures/empty similarity index 100% rename from spec/fixtures/empty rename to test/fixtures/empty diff --git a/spec/fixtures/file1.bin b/test/fixtures/file1.bin similarity index 100% rename from spec/fixtures/file1.bin rename to test/fixtures/file1.bin diff --git a/spec/fixtures/file2.bin b/test/fixtures/file2.bin similarity index 100% rename from spec/fixtures/file2.bin rename to test/fixtures/file2.bin diff --git a/spec/fixtures/four_lines b/test/fixtures/four_lines similarity index 100% rename from spec/fixtures/four_lines rename to test/fixtures/four_lines diff --git a/spec/fixtures/four_lines_with_missing_new_line b/test/fixtures/four_lines_with_missing_new_line similarity index 100% rename from spec/fixtures/four_lines_with_missing_new_line rename to test/fixtures/four_lines_with_missing_new_line diff --git a/spec/fixtures/ldiff/diff.missing_new_line1-e b/test/fixtures/ldiff/diff.missing_new_line1-e similarity index 100% rename from spec/fixtures/ldiff/diff.missing_new_line1-e rename to test/fixtures/ldiff/diff.missing_new_line1-e diff --git a/spec/fixtures/ldiff/diff.missing_new_line1-f b/test/fixtures/ldiff/diff.missing_new_line1-f similarity index 100% rename from spec/fixtures/ldiff/diff.missing_new_line1-f rename to test/fixtures/ldiff/diff.missing_new_line1-f diff --git a/spec/fixtures/ldiff/diff.missing_new_line2-e b/test/fixtures/ldiff/diff.missing_new_line2-e similarity index 100% rename from spec/fixtures/ldiff/diff.missing_new_line2-e rename to test/fixtures/ldiff/diff.missing_new_line2-e diff --git a/spec/fixtures/ldiff/diff.missing_new_line2-f b/test/fixtures/ldiff/diff.missing_new_line2-f similarity index 100% rename from spec/fixtures/ldiff/diff.missing_new_line2-f rename to test/fixtures/ldiff/diff.missing_new_line2-f diff --git a/spec/fixtures/ldiff/error.diff.chef-e b/test/fixtures/ldiff/error.diff.chef-e similarity index 100% rename from spec/fixtures/ldiff/error.diff.chef-e rename to test/fixtures/ldiff/error.diff.chef-e diff --git a/spec/fixtures/ldiff/error.diff.chef-f b/test/fixtures/ldiff/error.diff.chef-f similarity index 100% rename from spec/fixtures/ldiff/error.diff.chef-f rename to test/fixtures/ldiff/error.diff.chef-f diff --git a/spec/fixtures/ldiff/error.diff.missing_new_line1-e b/test/fixtures/ldiff/error.diff.missing_new_line1-e similarity index 100% rename from spec/fixtures/ldiff/error.diff.missing_new_line1-e rename to test/fixtures/ldiff/error.diff.missing_new_line1-e diff --git a/spec/fixtures/ldiff/error.diff.missing_new_line1-f b/test/fixtures/ldiff/error.diff.missing_new_line1-f similarity index 100% rename from spec/fixtures/ldiff/error.diff.missing_new_line1-f rename to test/fixtures/ldiff/error.diff.missing_new_line1-f diff --git a/spec/fixtures/ldiff/error.diff.missing_new_line2-e b/test/fixtures/ldiff/error.diff.missing_new_line2-e similarity index 100% rename from spec/fixtures/ldiff/error.diff.missing_new_line2-e rename to test/fixtures/ldiff/error.diff.missing_new_line2-e diff --git a/spec/fixtures/ldiff/error.diff.missing_new_line2-f b/test/fixtures/ldiff/error.diff.missing_new_line2-f similarity index 100% rename from spec/fixtures/ldiff/error.diff.missing_new_line2-f rename to test/fixtures/ldiff/error.diff.missing_new_line2-f diff --git a/spec/fixtures/ldiff/output.diff b/test/fixtures/ldiff/output.diff similarity index 100% rename from spec/fixtures/ldiff/output.diff rename to test/fixtures/ldiff/output.diff diff --git a/test/fixtures/ldiff/output.diff-c b/test/fixtures/ldiff/output.diff-c new file mode 100644 index 00000000..dc426cb6 --- /dev/null +++ b/test/fixtures/ldiff/output.diff-c @@ -0,0 +1,7 @@ +*** test/fixtures/aX 2020-06-23 11:15:32.000000000 -0400 +--- test/fixtures/bXaX 2020-06-23 11:15:32.000000000 -0400 +*************** +*** 1 **** +! aX +--- 1 ---- +! bXaX diff --git a/test/fixtures/ldiff/output.diff-u b/test/fixtures/ldiff/output.diff-u new file mode 100644 index 00000000..bd7560a3 --- /dev/null +++ b/test/fixtures/ldiff/output.diff-u @@ -0,0 +1,5 @@ +--- test/fixtures/aX 2020-06-23 11:15:32.000000000 -0400 ++++ test/fixtures/bXaX 2020-06-23 11:15:32.000000000 -0400 +@@ -1 +1 @@ +-aX ++bXaX diff --git a/spec/fixtures/ldiff/output.diff.bin1 b/test/fixtures/ldiff/output.diff.bin1 similarity index 100% rename from spec/fixtures/ldiff/output.diff.bin1 rename to test/fixtures/ldiff/output.diff.bin1 diff --git a/spec/fixtures/ldiff/output.diff.bin1-c b/test/fixtures/ldiff/output.diff.bin1-c similarity index 100% rename from spec/fixtures/ldiff/output.diff.bin1-c rename to test/fixtures/ldiff/output.diff.bin1-c diff --git a/spec/fixtures/ldiff/output.diff.bin1-e b/test/fixtures/ldiff/output.diff.bin1-e similarity index 100% rename from spec/fixtures/ldiff/output.diff.bin1-e rename to test/fixtures/ldiff/output.diff.bin1-e diff --git a/spec/fixtures/ldiff/output.diff.bin1-f b/test/fixtures/ldiff/output.diff.bin1-f similarity index 100% rename from spec/fixtures/ldiff/output.diff.bin1-f rename to test/fixtures/ldiff/output.diff.bin1-f diff --git a/spec/fixtures/ldiff/output.diff.bin1-u b/test/fixtures/ldiff/output.diff.bin1-u similarity index 100% rename from spec/fixtures/ldiff/output.diff.bin1-u rename to test/fixtures/ldiff/output.diff.bin1-u diff --git a/test/fixtures/ldiff/output.diff.bin2 b/test/fixtures/ldiff/output.diff.bin2 new file mode 100644 index 00000000..79e97686 --- /dev/null +++ b/test/fixtures/ldiff/output.diff.bin2 @@ -0,0 +1 @@ +Binary files test/fixtures/file1.bin and test/fixtures/file2.bin differ diff --git a/test/fixtures/ldiff/output.diff.bin2-c b/test/fixtures/ldiff/output.diff.bin2-c new file mode 100644 index 00000000..79e97686 --- /dev/null +++ b/test/fixtures/ldiff/output.diff.bin2-c @@ -0,0 +1 @@ +Binary files test/fixtures/file1.bin and test/fixtures/file2.bin differ diff --git a/test/fixtures/ldiff/output.diff.bin2-e b/test/fixtures/ldiff/output.diff.bin2-e new file mode 100644 index 00000000..79e97686 --- /dev/null +++ b/test/fixtures/ldiff/output.diff.bin2-e @@ -0,0 +1 @@ +Binary files test/fixtures/file1.bin and test/fixtures/file2.bin differ diff --git a/test/fixtures/ldiff/output.diff.bin2-f b/test/fixtures/ldiff/output.diff.bin2-f new file mode 100644 index 00000000..79e97686 --- /dev/null +++ b/test/fixtures/ldiff/output.diff.bin2-f @@ -0,0 +1 @@ +Binary files test/fixtures/file1.bin and test/fixtures/file2.bin differ diff --git a/test/fixtures/ldiff/output.diff.bin2-u b/test/fixtures/ldiff/output.diff.bin2-u new file mode 100644 index 00000000..79e97686 --- /dev/null +++ b/test/fixtures/ldiff/output.diff.bin2-u @@ -0,0 +1 @@ +Binary files test/fixtures/file1.bin and test/fixtures/file2.bin differ diff --git a/spec/fixtures/ldiff/output.diff.chef b/test/fixtures/ldiff/output.diff.chef similarity index 100% rename from spec/fixtures/ldiff/output.diff.chef rename to test/fixtures/ldiff/output.diff.chef diff --git a/spec/fixtures/ldiff/output.diff.chef-c b/test/fixtures/ldiff/output.diff.chef-c similarity index 60% rename from spec/fixtures/ldiff/output.diff.chef-c rename to test/fixtures/ldiff/output.diff.chef-c index efbfa195..1c6cbb91 100644 --- a/spec/fixtures/ldiff/output.diff.chef-c +++ b/test/fixtures/ldiff/output.diff.chef-c @@ -1,5 +1,5 @@ -*** spec/fixtures/old-chef 2020-06-23 23:18:20.000000000 -0400 ---- spec/fixtures/new-chef 2020-06-23 23:18:20.000000000 -0400 +*** test/fixtures/old-chef 2020-06-23 23:18:20.000000000 -0400 +--- test/fixtures/new-chef 2020-06-23 23:18:20.000000000 -0400 *************** *** 1,4 **** { diff --git a/test/fixtures/ldiff/output.diff.chef-u b/test/fixtures/ldiff/output.diff.chef-u new file mode 100644 index 00000000..57a5ac9b --- /dev/null +++ b/test/fixtures/ldiff/output.diff.chef-u @@ -0,0 +1,9 @@ +--- test/fixtures/old-chef 2020-06-23 23:18:20.000000000 -0400 ++++ test/fixtures/new-chef 2020-06-23 23:18:20.000000000 -0400 +@@ -1,4 +1,4 @@ + { + "name": "x", +- "description": "hi" ++ "description": "lo" + } +\ No newline at end of file diff --git a/spec/fixtures/ldiff/output.diff.chef2 b/test/fixtures/ldiff/output.diff.chef2 similarity index 100% rename from spec/fixtures/ldiff/output.diff.chef2 rename to test/fixtures/ldiff/output.diff.chef2 diff --git a/spec/fixtures/ldiff/output.diff.chef2-c b/test/fixtures/ldiff/output.diff.chef2-c similarity index 71% rename from spec/fixtures/ldiff/output.diff.chef2-c rename to test/fixtures/ldiff/output.diff.chef2-c index 8349a7a8..35e0c2c1 100644 --- a/spec/fixtures/ldiff/output.diff.chef2-c +++ b/test/fixtures/ldiff/output.diff.chef2-c @@ -1,5 +1,5 @@ -*** spec/fixtures/old-chef2 2020-06-30 09:43:35.000000000 -0400 ---- spec/fixtures/new-chef2 2020-06-30 09:44:32.000000000 -0400 +*** test/fixtures/old-chef2 2020-06-30 09:43:35.000000000 -0400 +--- test/fixtures/new-chef2 2020-06-30 09:44:32.000000000 -0400 *************** *** 1,5 **** recipe[a::default] diff --git a/spec/fixtures/ldiff/output.diff.chef2-d b/test/fixtures/ldiff/output.diff.chef2-d similarity index 100% rename from spec/fixtures/ldiff/output.diff.chef2-d rename to test/fixtures/ldiff/output.diff.chef2-d diff --git a/spec/fixtures/ldiff/output.diff.chef2-u b/test/fixtures/ldiff/output.diff.chef2-u similarity index 66% rename from spec/fixtures/ldiff/output.diff.chef2-u rename to test/fixtures/ldiff/output.diff.chef2-u index ef025c7e..82f0da2f 100644 --- a/spec/fixtures/ldiff/output.diff.chef2-u +++ b/test/fixtures/ldiff/output.diff.chef2-u @@ -1,5 +1,5 @@ ---- spec/fixtures/old-chef2 2020-06-30 09:43:35.000000000 -0400 -+++ spec/fixtures/new-chef2 2020-06-30 09:44:32.000000000 -0400 +--- test/fixtures/old-chef2 2020-06-30 09:43:35.000000000 -0400 ++++ test/fixtures/new-chef2 2020-06-30 09:44:32.000000000 -0400 @@ -1,5 +1,4 @@ recipe[a::default] -recipe[b::default] diff --git a/spec/fixtures/ldiff/output.diff.empty.vs.four_lines b/test/fixtures/ldiff/output.diff.empty.vs.four_lines similarity index 100% rename from spec/fixtures/ldiff/output.diff.empty.vs.four_lines rename to test/fixtures/ldiff/output.diff.empty.vs.four_lines diff --git a/test/fixtures/ldiff/output.diff.empty.vs.four_lines-c b/test/fixtures/ldiff/output.diff.empty.vs.four_lines-c new file mode 100644 index 00000000..a1720b77 --- /dev/null +++ b/test/fixtures/ldiff/output.diff.empty.vs.four_lines-c @@ -0,0 +1,9 @@ +*** test/fixtures/empty 2025-01-31 12:14:52.856031635 +0100 +--- test/fixtures/four_lines 2025-01-31 12:13:45.476036544 +0100 +*************** +*** 0 **** +--- 1,4 ---- ++ one ++ two ++ three ++ four diff --git a/spec/fixtures/ldiff/output.diff.empty.vs.four_lines-e b/test/fixtures/ldiff/output.diff.empty.vs.four_lines-e similarity index 100% rename from spec/fixtures/ldiff/output.diff.empty.vs.four_lines-e rename to test/fixtures/ldiff/output.diff.empty.vs.four_lines-e diff --git a/spec/fixtures/ldiff/output.diff.empty.vs.four_lines-f b/test/fixtures/ldiff/output.diff.empty.vs.four_lines-f similarity index 100% rename from spec/fixtures/ldiff/output.diff.empty.vs.four_lines-f rename to test/fixtures/ldiff/output.diff.empty.vs.four_lines-f diff --git a/test/fixtures/ldiff/output.diff.empty.vs.four_lines-u b/test/fixtures/ldiff/output.diff.empty.vs.four_lines-u new file mode 100644 index 00000000..3e623e65 --- /dev/null +++ b/test/fixtures/ldiff/output.diff.empty.vs.four_lines-u @@ -0,0 +1,7 @@ +--- test/fixtures/empty 2025-01-31 12:14:52.856031635 +0100 ++++ test/fixtures/four_lines 2025-01-31 12:13:45.476036544 +0100 +@@ -0,0 +1,4 @@ ++one ++two ++three ++four diff --git a/spec/fixtures/ldiff/output.diff.four_lines.vs.empty b/test/fixtures/ldiff/output.diff.four_lines.vs.empty similarity index 100% rename from spec/fixtures/ldiff/output.diff.four_lines.vs.empty rename to test/fixtures/ldiff/output.diff.four_lines.vs.empty diff --git a/test/fixtures/ldiff/output.diff.four_lines.vs.empty-c b/test/fixtures/ldiff/output.diff.four_lines.vs.empty-c new file mode 100644 index 00000000..68c2646a --- /dev/null +++ b/test/fixtures/ldiff/output.diff.four_lines.vs.empty-c @@ -0,0 +1,9 @@ +*** test/fixtures/four_lines 2025-01-31 12:13:45.476036544 +0100 +--- test/fixtures/empty 2025-01-31 12:14:52.856031635 +0100 +*************** +*** 1,4 **** +- one +- two +- three +- four +--- 0 ---- diff --git a/spec/fixtures/ldiff/output.diff.four_lines.vs.empty-e b/test/fixtures/ldiff/output.diff.four_lines.vs.empty-e similarity index 100% rename from spec/fixtures/ldiff/output.diff.four_lines.vs.empty-e rename to test/fixtures/ldiff/output.diff.four_lines.vs.empty-e diff --git a/spec/fixtures/ldiff/output.diff.four_lines.vs.empty-f b/test/fixtures/ldiff/output.diff.four_lines.vs.empty-f similarity index 100% rename from spec/fixtures/ldiff/output.diff.four_lines.vs.empty-f rename to test/fixtures/ldiff/output.diff.four_lines.vs.empty-f diff --git a/test/fixtures/ldiff/output.diff.four_lines.vs.empty-u b/test/fixtures/ldiff/output.diff.four_lines.vs.empty-u new file mode 100644 index 00000000..8b055801 --- /dev/null +++ b/test/fixtures/ldiff/output.diff.four_lines.vs.empty-u @@ -0,0 +1,7 @@ +--- test/fixtures/four_lines 2025-01-31 12:13:45.476036544 +0100 ++++ test/fixtures/empty 2025-01-31 12:14:52.856031635 +0100 +@@ -1,4 +0,0 @@ +-one +-two +-three +-four diff --git a/spec/fixtures/ldiff/output.diff.issue95_trailing_context b/test/fixtures/ldiff/output.diff.issue95_trailing_context similarity index 100% rename from spec/fixtures/ldiff/output.diff.issue95_trailing_context rename to test/fixtures/ldiff/output.diff.issue95_trailing_context diff --git a/test/fixtures/ldiff/output.diff.issue95_trailing_context-c b/test/fixtures/ldiff/output.diff.issue95_trailing_context-c new file mode 100644 index 00000000..ad661b11 --- /dev/null +++ b/test/fixtures/ldiff/output.diff.issue95_trailing_context-c @@ -0,0 +1,9 @@ +*** test/fixtures/123_x 2025-01-31 17:00:17.070615716 +0100 +--- test/fixtures/456_x 2025-01-31 16:58:26.380624827 +0100 +*************** +*** 1,2 **** +! 123 + x +--- 1,2 ---- +! 456 + x diff --git a/spec/fixtures/ldiff/output.diff.issue95_trailing_context-e b/test/fixtures/ldiff/output.diff.issue95_trailing_context-e similarity index 100% rename from spec/fixtures/ldiff/output.diff.issue95_trailing_context-e rename to test/fixtures/ldiff/output.diff.issue95_trailing_context-e diff --git a/spec/fixtures/ldiff/output.diff.issue95_trailing_context-f b/test/fixtures/ldiff/output.diff.issue95_trailing_context-f similarity index 100% rename from spec/fixtures/ldiff/output.diff.issue95_trailing_context-f rename to test/fixtures/ldiff/output.diff.issue95_trailing_context-f diff --git a/test/fixtures/ldiff/output.diff.issue95_trailing_context-u b/test/fixtures/ldiff/output.diff.issue95_trailing_context-u new file mode 100644 index 00000000..776fc2be --- /dev/null +++ b/test/fixtures/ldiff/output.diff.issue95_trailing_context-u @@ -0,0 +1,6 @@ +--- test/fixtures/123_x 2025-01-31 17:00:17.070615716 +0100 ++++ test/fixtures/456_x 2025-01-31 16:58:26.380624827 +0100 +@@ -1,2 +1,2 @@ +-123 ++456 + x diff --git a/spec/fixtures/ldiff/output.diff.missing_new_line1 b/test/fixtures/ldiff/output.diff.missing_new_line1 similarity index 100% rename from spec/fixtures/ldiff/output.diff.missing_new_line1 rename to test/fixtures/ldiff/output.diff.missing_new_line1 diff --git a/spec/fixtures/ldiff/output.diff.missing_new_line1-c b/test/fixtures/ldiff/output.diff.missing_new_line1-c similarity index 53% rename from spec/fixtures/ldiff/output.diff.missing_new_line1-c rename to test/fixtures/ldiff/output.diff.missing_new_line1-c index 55d1ade5..8473e8d3 100644 --- a/spec/fixtures/ldiff/output.diff.missing_new_line1-c +++ b/test/fixtures/ldiff/output.diff.missing_new_line1-c @@ -1,5 +1,5 @@ -*** spec/fixtures/four_lines 2025-01-31 12:17:43.926013315 +0100 ---- spec/fixtures/four_lines_with_missing_new_line 2025-01-31 12:17:43.926013315 +0100 +*** test/fixtures/four_lines 2025-01-31 12:17:43.926013315 +0100 +--- test/fixtures/four_lines_with_missing_new_line 2025-01-31 12:17:43.926013315 +0100 *************** *** 1,4 **** one diff --git a/spec/fixtures/ldiff/output.diff.missing_new_line1-e b/test/fixtures/ldiff/output.diff.missing_new_line1-e similarity index 100% rename from spec/fixtures/ldiff/output.diff.missing_new_line1-e rename to test/fixtures/ldiff/output.diff.missing_new_line1-e diff --git a/spec/fixtures/ldiff/output.diff.missing_new_line1-f b/test/fixtures/ldiff/output.diff.missing_new_line1-f similarity index 100% rename from spec/fixtures/ldiff/output.diff.missing_new_line1-f rename to test/fixtures/ldiff/output.diff.missing_new_line1-f diff --git a/test/fixtures/ldiff/output.diff.missing_new_line1-u b/test/fixtures/ldiff/output.diff.missing_new_line1-u new file mode 100644 index 00000000..2f40d639 --- /dev/null +++ b/test/fixtures/ldiff/output.diff.missing_new_line1-u @@ -0,0 +1,9 @@ +--- test/fixtures/four_lines 2025-01-31 12:17:43.926013315 +0100 ++++ test/fixtures/four_lines_with_missing_new_line 2025-01-31 12:17:43.926013315 +0100 +@@ -1,4 +1,4 @@ + one + two + three +-four ++four +\ No newline at end of file diff --git a/spec/fixtures/ldiff/output.diff.missing_new_line2 b/test/fixtures/ldiff/output.diff.missing_new_line2 similarity index 100% rename from spec/fixtures/ldiff/output.diff.missing_new_line2 rename to test/fixtures/ldiff/output.diff.missing_new_line2 diff --git a/spec/fixtures/ldiff/output.diff.missing_new_line2-c b/test/fixtures/ldiff/output.diff.missing_new_line2-c similarity index 53% rename from spec/fixtures/ldiff/output.diff.missing_new_line2-c rename to test/fixtures/ldiff/output.diff.missing_new_line2-c index b4310305..27fe6713 100644 --- a/spec/fixtures/ldiff/output.diff.missing_new_line2-c +++ b/test/fixtures/ldiff/output.diff.missing_new_line2-c @@ -1,5 +1,5 @@ -*** spec/fixtures/four_lines_with_missing_new_line 2025-01-31 12:17:43.926013315 +0100 ---- spec/fixtures/four_lines 2025-01-31 12:17:43.926013315 +0100 +*** test/fixtures/four_lines_with_missing_new_line 2025-01-31 12:17:43.926013315 +0100 +--- test/fixtures/four_lines 2025-01-31 12:17:43.926013315 +0100 *************** *** 1,4 **** one diff --git a/spec/fixtures/ldiff/output.diff.missing_new_line2-e b/test/fixtures/ldiff/output.diff.missing_new_line2-e similarity index 100% rename from spec/fixtures/ldiff/output.diff.missing_new_line2-e rename to test/fixtures/ldiff/output.diff.missing_new_line2-e diff --git a/spec/fixtures/ldiff/output.diff.missing_new_line2-f b/test/fixtures/ldiff/output.diff.missing_new_line2-f similarity index 100% rename from spec/fixtures/ldiff/output.diff.missing_new_line2-f rename to test/fixtures/ldiff/output.diff.missing_new_line2-f diff --git a/test/fixtures/ldiff/output.diff.missing_new_line2-u b/test/fixtures/ldiff/output.diff.missing_new_line2-u new file mode 100644 index 00000000..527f9ea4 --- /dev/null +++ b/test/fixtures/ldiff/output.diff.missing_new_line2-u @@ -0,0 +1,9 @@ +--- test/fixtures/four_lines_with_missing_new_line 2025-01-31 12:17:43.926013315 +0100 ++++ test/fixtures/four_lines 2025-01-31 12:17:43.926013315 +0100 +@@ -1,4 +1,4 @@ + one + two + three +-four +\ No newline at end of file ++four diff --git a/spec/fixtures/new-chef b/test/fixtures/new-chef similarity index 100% rename from spec/fixtures/new-chef rename to test/fixtures/new-chef diff --git a/spec/fixtures/new-chef2 b/test/fixtures/new-chef2 similarity index 100% rename from spec/fixtures/new-chef2 rename to test/fixtures/new-chef2 diff --git a/spec/fixtures/old-chef b/test/fixtures/old-chef similarity index 100% rename from spec/fixtures/old-chef rename to test/fixtures/old-chef diff --git a/spec/fixtures/old-chef2 b/test/fixtures/old-chef2 similarity index 100% rename from spec/fixtures/old-chef2 rename to test/fixtures/old-chef2 diff --git a/test/test_block.rb b/test/test_block.rb new file mode 100644 index 00000000..b90b818f --- /dev/null +++ b/test/test_block.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require_relative "test_helper" +require "diff/lcs/block" + +class TestBlock < Minitest::Test + include Diff::LCS::TestHelper + + def test_op_unchanged + block = Diff::LCS::Block.from_chunk([]) + assert_equal "^", block.op + end + + def test_op_delete + changes = [Diff::LCS::Change.new("-", 0, "a")] + block = Diff::LCS::Block.from_chunk(changes) + assert_equal "-", block.op + end + + def test_op_insert + changes = [Diff::LCS::Change.new("+", 0, "a")] + block = Diff::LCS::Block.from_chunk(changes) + assert_equal "+", block.op + end + + def test_op_conflict + changes = [ + Diff::LCS::Change.new("-", 0, "a"), + Diff::LCS::Change.new("+", 0, "b") + ] + block = Diff::LCS::Block.from_chunk(changes) + assert_equal "!", block.op + end +end diff --git a/test/test_change.rb b/test/test_change.rb new file mode 100644 index 00000000..9ae41612 --- /dev/null +++ b/test/test_change.rb @@ -0,0 +1,234 @@ +# frozen_string_literal: true + +require "test_helper" + +class TestChange < Minitest::Test + def test_invalid_change + assert_raises RuntimeError, /Invalid CHange Action/ do + change("~", 0, "element") + end + + assert_raises RuntimeError, /Invalid Position Type/ do + change("+", 3.5, "element") + end + end + + def test_from_a + assert_kind_of Diff::LCS::Change, change_from_a(["+", 0, "element"]) + assert_kind_of Diff::LCS::Change, change_from_a(["+", 0, "element"], Diff::LCS::ContextChange) + assert_kind_of Diff::LCS::ContextChange, + change_from_a(["!", [1, "old_element"], [2, "new_element"]]) + assert_kind_of Diff::LCS::ContextChange, + change_from_a(["!", [1, "old_element"], [2, "new_element"]], Diff::LCS::ContextChange) + + assert_raises RuntimeError, "Invalid change array format provided." do + change_from_a(["+", 0]) + end + + assert_raises RuntimeError, "Invalid change array format provided." do + change_from_a(["+", 0], Diff::LCS::ContextChange) + end + end + + def test_spaceship_reflexive + c1 = change("=", 5, "x") + c2 = change("=", 5, "x") + assert_equal 0, c1 <=> c2 + end + + def test_spaceship_position_precedence + c1 = change("=", 5, "x") + c2 = change("=", 10, "x") + + assert_equal(-1, c1 <=> c2) + assert_equal 1, c2 <=> c1 + end + + def test_spaceship_action_precedence + valid_actions.each_cons(2) do |a1, a2| + c1 = change(a1, 5, "x") + c2 = change(a2, 5, "x") + + assert_equal(-1, c1 <=> c2, "#{a1} should sort before #{a2}") + assert_equal 1, c2 <=> c1 + end + end + + def test_spaceship_element_precedence + c1 = change("=", 5, "a") + c2 = change("=", 5, "z") + + assert_equal(-1, c1 <=> c2) + assert_equal 1, c2 <=> c1 + assert_equal 0, c1 <=> change("=", 5, "a") + end + + def test_add + change = change("+", 0, "element") + + assert change.adding? + + refute change.deleting? + refute change.unchanged? + refute change.changed? + refute change.finished_a? + refute change.finished_b? + end + + def test_delete + change = change("-", 0, "element") + + assert change.deleting? + + refute change.adding? + refute change.unchanged? + refute change.changed? + refute change.finished_a? + refute change.finished_b? + end + + def test_unchanged + change = change("=", 0, "element") + + assert change.unchanged? + + refute change.deleting? + refute change.adding? + refute change.changed? + refute change.finished_a? + refute change.finished_b? + end + + def test_changed + change = change("!", 0, "element") + + assert change.changed? + + refute change.deleting? + refute change.adding? + refute change.unchanged? + refute change.finished_a? + refute change.finished_b? + end + + def test_finished_a + change = change(">", 0, "element") + + assert change.finished_a? + + refute change.deleting? + refute change.adding? + refute change.unchanged? + refute change.changed? + refute change.finished_b? + end + + def test_finished_b + change = change("<", 0, "element") + + assert change.finished_b? + + refute change.deleting? + refute change.adding? + refute change.unchanged? + refute change.changed? + refute change.finished_a? + end + + def test_as_array + action, position, element = change("!", 0, "element") + assert_equal "!", action + assert_equal 0, position + assert_equal "element", element + end +end + +class TestContextChange < Minitest::Test + private def change(...) = Diff::LCS::ContextChange.new(...) + private def simplify(...) = Diff::LCS::ContextChange.simplify(change(...)) + + def test_invalid_change + assert_raises RuntimeError, /Invalid CHange Action/ do + change("~", 0, "old", 0, "new") + end + + assert_raises RuntimeError, /Invalid Position Type/ do + change("+", 3.5, "old", 0, "new") + end + + assert_raises RuntimeError, /Invalid Position Type/ do + change("+", 0, "old", 3.5, "new") + end + end + + def test_as_array + action, old, new = change("!", 1, "old_element", 2, "new_element") + + assert_equal "!", action + assert_equal [1, "old_element"], old + assert_equal [2, "new_element"], new + end + + def test_spaceship_reflexive + c1 = change("=", 5, "x", 10, "y") + c2 = change("=", 5, "x", 10, "y") + assert_equal 0, c1 <=> c2 + end + + def test_spaceship_position_precedence + c1 = change("=", 5, "x", 10, "y") + c2 = change("=", 15, "x", 10, "y") + + assert_equal(-1, c1 <=> c2) + assert_equal 1, c2 <=> c1 + + c1 = change("=", 5, "x", 10, "y") + c2 = change("=", 5, "x", 20, "y") + + assert_equal(-1, c1 <=> c2) + assert_equal 1, c2 <=> c1 + end + + def test_spaceship_action_precedence + valid_actions.each_cons(2) do |a1, a2| + c1 = change(a1, 5, "x", 10, "y") + c2 = change(a2, 5, "x", 10, "y") + + assert_equal(-1, c1 <=> c2, "#{a1} should sort before #{a2}") + assert_equal 1, c2 <=> c1 + end + end + + def test_spaceship_element_precedence + c1 = change("=", 5, "a", 10, "y") + c2 = change("=", 5, "z", 10, "y") + + assert_equal(-1, c1 <=> c2, "old_element precedence") + assert_equal 1, c2 <=> c1 + + c3 = change("=", 5, "x", 10, "a") + c4 = change("=", 5, "x", 10, "z") + + assert_equal(-1, c3 <=> c4, "new_element precedence") + assert_equal 1, c4 <=> c3 + assert_equal 0, c3 <=> Diff::LCS::ContextChange.new("=", 5, "x", 10, "a") + end + + def test_simplify + simplify("-", 5, "old", 10, "new") => {action:, new_element:} + assert_equal action, "-" + assert_nil new_element + + simplify("<", 5, "old", 10, "new") => {action:, new_element:} + assert_equal action, "-" + assert_nil new_element + + simplify("+", 5, "old", 10, "new") => {action:, old_element:} + assert_equal action, "+" + assert_nil old_element + + simplify(">", 5, "old", 10, "new") => {action:, old_element:} + assert_equal action, "+" + assert_nil old_element + end +end diff --git a/test/test_diff.rb b/test/test_diff.rb new file mode 100644 index 00000000..606215b2 --- /dev/null +++ b/test/test_diff.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +require_relative "test_helper" + +class TestDiff < Minitest::Test + def test_correctly_diffs_seq1_to_seq2 + assert_equal change_diff(correct_forward_diff), diff(seq1, seq2) + end + + def test_correctly_diffs_seq2_to_seq1 + assert_equal change_diff(correct_backward_diff), diff(seq2, seq1) + end + + def test_correctly_diffs_against_empty_sequence_forward + correct_diff = [ + [ + ["-", 0, "abcd"], + ["-", 1, "efgh"], + ["-", 2, "ijkl"], + ["-", 3, "mnopqrstuvwxyz"] + ] + ] + + assert_equal change_diff(correct_diff), diff(word_sequence, []) + end + + def test_correctly_diffs_against_empty_sequence_backward + correct_diff = [ + [ + ["+", 0, "abcd"], + ["+", 1, "efgh"], + ["+", 2, "ijkl"], + ["+", 3, "mnopqrstuvwxyz"] + ] + ] + + assert_equal change_diff(correct_diff), diff([], word_sequence) + end + + def test_correctly_diffs_xx_and_xaxb + left = "xx" + right = "xaxb" + assert_equal right, patch(left, diff(left, right)) + end + + def test_returns_empty_diff_with_hello_hello + assert_empty diff(hello, hello) + end + + def test_returns_empty_diff_with_hello_ary_hello_ary + assert_empty diff(hello_ary, hello_ary) + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb new file mode 100644 index 00000000..5e6bcdd3 --- /dev/null +++ b/test/test_helper.rb @@ -0,0 +1,225 @@ +# frozen_string_literal: true + +$LOAD_PATH.unshift File.expand_path(__dir__, "../lib") + +require "minitest/autorun" +require "simplecov" +require "simplecov-lcov" +require "open3" + +require "diff/lcs" + +module Diff::LCS::TestHelper + def hello = "hello" + def hello_ary = %w[h e l l o] + def seq1 = %w[a b c e h j l m n p] + def skipped_seq1 = %w[a h n p] + def seq2 = %w[b c d e f j k l m r s t] + def skipped_seq2 = %w[d f k r s t] + def word_sequence = %w[abcd efgh ijkl mnopqrstuvwxyz] + def correct_lcs = %w[b c e j l m] + + private def change(...) = Diff::LCS::Change.new(...) + private def change_from_a(array, klass = Diff::LCS::Change) = klass.from_a(array) + private def diff(...) = Diff::LCS.diff(...) + private def hunk(...) = Diff::LCS::Hunk.new(...) + private def internal_lcs(...) = Diff::LCS::Internals.lcs(...) + private def lcs(...) = Diff::LCS.lcs(...) + private def patch(...) = Diff::LCS.patch(...) + private def patch!(...) = Diff::LCS.patch!(...) + private def sdiff(...) = Diff::LCS.sdiff(...) + private def traverse_balanced(...) = Diff::LCS.traverse_balanced(...) + private def traverse_sequences(...) = Diff::LCS.traverse_sequences(...) + private def unpatch(...) = Diff::LCS.unpatch(...) + private def unpatch!(...) = Diff::LCS.unpatch!(...) + private def valid_actions = Diff::LCS::Change::VALID_ACTIONS + + def correct_forward_diff + [ + [["-", 0, "a"]], + [["+", 2, "d"]], + [["-", 4, "h"], ["+", 4, "f"]], + [["+", 6, "k"]], + [["-", 8, "n"], ["+", 9, "r"], ["-", 9, "p"], ["+", 10, "s"], ["+", 11, "t"]] + ] + end + + def correct_backward_diff + [ + [["+", 0, "a"]], + [["-", 2, "d"]], + [["-", 4, "f"], ["+", 4, "h"]], + [["-", 6, "k"]], + [["-", 9, "r"], ["+", 8, "n"], ["-", 10, "s"], ["+", 9, "p"], ["-", 11, "t"]] + ] + end + + def correct_forward_sdiff + [ + ["-", [0, "a"], [0, nil]], + ["=", [1, "b"], [0, "b"]], + ["=", [2, "c"], [1, "c"]], + ["+", [3, nil], [2, "d"]], + ["=", [3, "e"], [3, "e"]], + ["!", [4, "h"], [4, "f"]], + ["=", [5, "j"], [5, "j"]], + ["+", [6, nil], [6, "k"]], + ["=", [6, "l"], [7, "l"]], + ["=", [7, "m"], [8, "m"]], + ["!", [8, "n"], [9, "r"]], + ["!", [9, "p"], [10, "s"]], + ["+", [10, nil], [11, "t"]] + ] + end + + def reverse_sdiff(forward_sdiff) + forward_sdiff.map { |line| + line[1], line[2] = line[2], line[1] + case line[0] + when "-" then line[0] = "+" + when "+" then line[0] = "-" + end + line + } + end + + def change_diff(diff) = map_diffs(diff, Diff::LCS::Change) + def context_diff(diff) = map_diffs(diff, Diff::LCS::ContextChange) + + def map_diffs(diffs, klass = Diff::LCS::ContextChange) + diffs.map do |chunks| + if klass == Diff::LCS::ContextChange + klass.from_a(chunks) + else + chunks.map { |changes| klass.from_a(changes) } + end + end + end + + def balanced_traversal(s1, s2, callback_type) + callback = __send__(callback_type) + Diff::LCS.traverse_balanced(s1, s2, callback) + callback + end + + def balanced_reverse(change_result) + new_result = [] + change_result.each do |line| + line = [line[0], line[2], line[1]] + case line[0] + when "<" then line[0] = ">" + when ">" then line[0] = "<" + end + new_result << line + end + new_result.sort_by { |line| [line[1], line[2]] } + end + + def map_to_no_change(change_result) + new_result = [] + change_result.each do |line| + case line[0] + when "!" + new_result << ["<", line[1], line[2]] + new_result << [">", line[1] + 1, line[2]] + else + new_result << line + end + end + new_result + end + + def assert_nil_or_match_values(ee, ii, s1, s2) + assert(ee.nil? || s1[ii] == s2[ee]) + end + + def assert_correctly_maps_sequence(actual, s1, s2) + actual.each_index { |ii| assert_nil_or_match_values(actual[ii], ii, s1, s2) } + end + + class SimpleCallback + attr_reader :matched_a, :matched_b, :discards_a, :discards_b, :done_a, :done_b + + def initialize + reset + end + + def reset + @matched_a = [] + @matched_b = [] + @discards_a = [] + @discards_b = [] + @done_a = [] + @done_b = [] + self + end + + def match(event) + @matched_a << event.old_element + @matched_b << event.new_element + end + + def discard_b(event) + @discards_b << event.new_element + end + + def discard_a(event) + @discards_a << event.old_element + end + + def finished_a(event) + @done_a << [event.old_element, event.old_position, event.new_element, event.new_position] + end + + def finished_b(event) + @done_b << [event.old_element, event.old_position, event.new_element, event.new_position] + end + end + + def simple_callback = SimpleCallback.new + + class SimpleCallbackNoFinishers < SimpleCallback + undef :finished_a + undef :finished_b + end + + def simple_callback_no_finishers = SimpleCallbackNoFinishers.new + + class BalancedCallback + attr_reader :result + + def initialize + reset + end + + def reset + @result = [] + end + + def match(event) + @result << ["=", event.old_position, event.new_position] + end + + def discard_a(event) + @result << ["<", event.old_position, event.new_position] + end + + def discard_b(event) + @result << [">", event.old_position, event.new_position] + end + + def change(event) + @result << ["!", event.old_position, event.new_position] + end + end + + def balanced_callback = BalancedCallback.new + + class BalancedCallbackNoChange < BalancedCallback + undef :change + end + + def balanced_callback_no_change = BalancedCallbackNoChange.new + + Minitest::Test.send(:include, self) +end diff --git a/test/test_hunk.rb b/test/test_hunk.rb new file mode 100644 index 00000000..18b4c91a --- /dev/null +++ b/test/test_hunk.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +require "test_helper" + +require "diff/lcs/hunk" + +class TestHunk < Minitest::Test + def setup + @old_data = ["Tu a un carté avec {count} itéms".encode("UTF-16LE")] + @new_data = ["Tu a un carte avec {count} items".encode("UTF-16LE")] + @pieces = diff(@old_data, @new_data) + @hunk = hunk(@old_data, @new_data, @pieces[0], 3, 0) + end + + def test_produces_a_unified_diff_from_the_two_pieces + expected = <<-EXPECTED.gsub(/^\s+/, "").encode("UTF-16LE").chomp + @@ -1 +1 @@ + -Tu a un carté avec {count} itéms + +Tu a un carte avec {count} items + EXPECTED + + assert_equal expected, @hunk.diff(:unified) + end + + def test_produces_a_unified_diff_from_the_two_pieces_last_entry + expected = <<-EXPECTED.gsub(/^\s+/, "").encode("UTF-16LE").chomp + @@ -1 +1 @@ + -Tu a un carté avec {count} itéms + +Tu a un carte avec {count} items + \\ No newline at end of file + EXPECTED + + assert_equal expected, @hunk.diff(:unified, true) + end + + def test_produces_a_context_diff_from_the_two_pieces + expected = <<-EXPECTED.gsub(/^\s+/, "").encode("UTF-16LE").chomp + *************** + *** 1 **** + ! Tu a un carté avec {count} itéms + --- 1 ---- + ! Tu a un carte avec {count} items + EXPECTED + + assert_equal expected, @hunk.diff(:context) + end + + def test_produces_an_old_diff_from_the_two_pieces + expected = <<-EXPECTED.gsub(/^ +/, "").encode("UTF-16LE").chomp + 1c1 + < Tu a un carté avec {count} itéms + --- + > Tu a un carte avec {count} items + + EXPECTED + + assert_equal expected, @hunk.diff(:old) + end + + def test_with_empty_first_data_set_produces_a_unified_diff + old_data = [] + pieces = diff(old_data, @new_data) + hunk = hunk(old_data, @new_data, pieces[0], 3, 0) + + expected = <<-EXPECTED.gsub(/^\s+/, "").encode("UTF-16LE").chomp + @@ -0,0 +1 @@ + +Tu a un carte avec {count} items + EXPECTED + + assert_equal expected, hunk.diff(:unified) + end +end diff --git a/test/test_issues.rb b/test/test_issues.rb new file mode 100644 index 00000000..55993ce8 --- /dev/null +++ b/test/test_issues.rb @@ -0,0 +1,168 @@ +# frozen_string_literal: true + +require "test_helper" +require "diff/lcs/hunk" + +class TestIssues < Minitest::Test + def test_issue_1_string_forward_diff + s1 = "aX" + s2 = "bXaX" + forward_diff = [ + [ + ["+", 0, "b"], + ["+", 1, "X"] + ] + ] + + diff_s1_s2 = diff(s1, s2) + + assert_equal change_diff(forward_diff), diff_s1_s2 + assert_equal s2, patch(s1, diff_s1_s2) + assert_equal s1, patch(s2, diff_s1_s2) + end + + def test_issue_1_string_reverse_diff + s1 = "bXaX" + s2 = "aX" + forward_diff = [ + [ + ["-", 0, "b"], + ["-", 1, "X"] + ] + ] + + diff_s1_s2 = diff(s1, s2) + + assert_equal change_diff(forward_diff), diff_s1_s2 + assert_equal s2, patch(s1, diff_s1_s2) + assert_equal s1, patch(s2, diff_s1_s2) + end + + def test_issue_1_array_forward_diff + s1 = %w[a X] + s2 = %w[b X a X] + forward_diff = [ + [ + ["+", 0, "b"], + ["+", 1, "X"] + ] + ] + + diff_s1_s2 = diff(s1, s2) + + assert_equal change_diff(forward_diff), diff_s1_s2 + assert_equal s2, patch(s1, diff_s1_s2) + assert_equal s1, patch(s2, diff_s1_s2) + end + + def test_issue_1_array_reverse_diff + s1 = %w[b X a X] + s2 = %w[a X] + forward_diff = [ + [ + ["-", 0, "b"], + ["-", 1, "X"] + ] + ] + + diff_s1_s2 = diff(s1, s2) + + assert_equal change_diff(forward_diff), diff_s1_s2 + assert_equal s2, patch(s1, diff_s1_s2) + assert_equal s1, patch(s2, diff_s1_s2) + end + + def test_issue_57_should_fail_with_correct_error + assert_raises(Minitest::Assertion) do + actual = {category: "app.rack.request"} + expected = {category: "rack.middleware", title: "Anonymous Middleware"} + assert_equal expected, actual + end + end + + def diff_lines(old_lines, new_lines) + file_length_difference = 0 + previous_hunk = nil + output = [] + + diff(old_lines, new_lines).each do |piece| + hunk = hunk(old_lines, new_lines, piece, 3, file_length_difference) + file_length_difference = hunk.file_length_difference + maybe_contiguous_hunks = previous_hunk.nil? || hunk.merge(previous_hunk) + + output << "#{previous_hunk.diff(:unified)}\n" unless maybe_contiguous_hunks + + previous_hunk = hunk + end + output << "#{previous_hunk.diff(:unified, true)}\n" unless previous_hunk.nil? + output.join + end + + def test_issue_65_should_not_misplace_new_chunk + old_data = [ + "recipe[a::default]", "recipe[b::default]", "recipe[c::default]", + "recipe[d::default]", "recipe[e::default]", "recipe[f::default]", + "recipe[g::default]", "recipe[h::default]", "recipe[i::default]", + "recipe[j::default]", "recipe[k::default]", "recipe[l::default]", + "recipe[m::default]", "recipe[n::default]" + ] + + new_data = [ + "recipe[a::default]", "recipe[c::default]", "recipe[d::default]", + "recipe[e::default]", "recipe[f::default]", "recipe[g::default]", + "recipe[h::default]", "recipe[i::default]", "recipe[j::default]", + "recipe[k::default]", "recipe[l::default]", "recipe[m::default]", + "recipe[n::default]", "recipe[o::new]", "recipe[p::new]", + "recipe[q::new]", "recipe[r::new]" + ] + + expected = <<~EODIFF + @@ -1,5 +1,4 @@ + recipe[a::default] + -recipe[b::default] + recipe[c::default] + recipe[d::default] + recipe[e::default] + @@ -12,3 +11,7 @@ + recipe[l::default] + recipe[m::default] + recipe[n::default] + +recipe[o::new] + +recipe[p::new] + +recipe[q::new] + +recipe[r::new] + EODIFF + + assert_equal expected, diff_lines(old_data, new_data) + end + + def test_issue_107_should_produce_unified_output_with_correct_context + old_data = <<~DATA_OLD.strip.split("\n").map(&:chomp) + { + "name": "x", + "description": "hi" + } + DATA_OLD + + new_data = <<~DATA_NEW.strip.split("\n").map(&:chomp) + { + "name": "x", + "description": "lo" + } + DATA_NEW + + diff = diff(old_data, new_data) + hunk = hunk(old_data, new_data, diff.first, 3, 0) + + expected = <<~EXPECTED.chomp + @@ -1,4 +1,4 @@ + { + "name": "x", + - "description": "hi" + + "description": "lo" + } + EXPECTED + + assert_equal expected, hunk.diff(:unified) + end +end diff --git a/test/test_lcs.rb b/test/test_lcs.rb new file mode 100644 index 00000000..548b45b4 --- /dev/null +++ b/test/test_lcs.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require_relative "test_helper" + +class TestLCSInternals < Minitest::Test + def test_returns_meaningful_lcs_array_with_seq1_seq2 + res = internal_lcs(seq1, seq2) + assert_equal correct_lcs.size, res.compact.size + assert_correctly_maps_sequence(res, seq1, seq2) + + x_seq1 = (0...res.size).map { |ix| res[ix] ? seq1[ix] : nil }.compact + x_seq2 = (0...res.size).map { |ix| res[ix] ? seq2[res[ix]] : nil }.compact + + assert_equal correct_lcs, x_seq1 + assert_equal correct_lcs, x_seq2 + end + + def test_returns_all_indexes_with_hello_hello + assert_equal (0...hello.size).to_a, internal_lcs(hello, hello) + end + + def test_returns_all_indexes_with_hello_ary_hello_ary + assert_equal (0...hello_ary.size).to_a, internal_lcs(hello_ary, hello_ary) + end +end + +class TestLCS < Minitest::Test + def test_returns_correct_compacted_values + res = lcs(seq1, seq2) + assert_equal correct_lcs, res + assert_equal res, res.compact + end + + def test_is_transitive + res = lcs(seq2, seq1) + assert_equal correct_lcs, res + assert_equal res, res.compact + end + + def test_returns_hello_chars_with_hello_hello + assert_equal hello.chars, lcs(hello, hello) + end + + def test_returns_hello_ary_with_hello_ary_hello_ary + assert_equal hello_ary, lcs(hello_ary, hello_ary) + end +end diff --git a/test/test_ldiff.rb b/test/test_ldiff.rb new file mode 100644 index 00000000..32db8550 --- /dev/null +++ b/test/test_ldiff.rb @@ -0,0 +1,89 @@ +# frozen_string_literal: true + +require "test_helper" + +class TestLdiff < Minitest::Test + FIXTURES = [ + {name: "diff", left: "aX", right: "bXaX", diff: 1}, + {name: "diff.missing_new_line1", left: "four_lines", right: "four_lines_with_missing_new_line", diff: 1}, + {name: "diff.missing_new_line2", left: "four_lines_with_missing_new_line", right: "four_lines", diff: 1}, + {name: "diff.issue95_trailing_context", left: "123_x", right: "456_x", diff: 1}, + {name: "diff.four_lines.vs.empty", left: "four_lines", right: "empty", diff: 1}, + {name: "diff.empty.vs.four_lines", left: "empty", right: "four_lines", diff: 1}, + {name: "diff.bin1", left: "file1.bin", right: "file1.bin", diff: 0}, + {name: "diff.bin2", left: "file1.bin", right: "file2.bin", diff: 1}, + {name: "diff.chef", left: "old-chef", right: "new-chef", diff: 1}, + {name: "diff.chef2", left: "old-chef2", right: "new-chef2", diff: 1} + ].product([nil, "-c", "-u"]).map { |(fixture, flag)| + fixture = fixture.dup + fixture[:flag] = flag + fixture + } + + FIXTURES.each do |fixture| + test_name = "test_ldiff_#{fixture[:name]}_#{fixture[:flag] || "default"}" + .tr(".", "_") + .tr("-", "_") + + define_method(test_name) do + stdout, stderr, status = run_ldiff(fixture) + assert_equal fixture[:diff], status + assert_equal read_fixture(fixture, mode: "error", allow_missing: true), stderr + assert_equal read_fixture(fixture, mode: "output", allow_missing: false), stdout + end + end + + private + + def read_fixture(options, mode: "output", allow_missing: false) + fixture = options.fetch(:name) + flag = options.fetch(:flag) + name = "test/fixtures/ldiff/#{mode}.#{fixture}#{flag}" + + return "" if !::File.exist?(name) && allow_missing + + data = IO.__send__(IO.respond_to?(:binread) ? :binread : :read, name) + clean_data(data, flag) + end + + def clean_data(data, flag) + data = + case flag + when "-c", "-u" + clean_output_timestamp(data) + else + data + end + data.gsub(/\r\n?/, "\n") + end + + def clean_output_timestamp(data) + data.gsub( + %r{ + ^ + [-+*]{3} + \s* + test/fixtures/(\S+) + \s* + \d{4}-\d\d-\d\d + \s* + \d\d:\d\d:\d\d(?:\.\d+) + \s* + (?:[-+]\d{4}|Z) + }x, + '*** test/fixtures/\1 0000-00-00 :00 =>:00 =>00.000000000 -0000' + ) + end + + def run_ldiff(options) + flag = options.fetch(:flag) + left = options.fetch(:left) + right = options.fetch(:right) + + stdout, stderr = capture_subprocess_io do + system("ruby -Ilib bin/ldiff #{flag} test/fixtures/#{left} test/fixtures/#{right}") + end + + [clean_data(stdout, flag), stderr, $?.exitstatus] + end +end diff --git a/test/test_patch.rb b/test/test_patch.rb new file mode 100644 index 00000000..42c99b3d --- /dev/null +++ b/test/test_patch.rb @@ -0,0 +1,362 @@ +# frozen_string_literal: true + +require_relative "test_helper" + +class TestPatch < Minitest::Test + def patch_sequences_correctly(s1, s2, patch_set) + assert_equal s2, patch(s1, patch_set) + assert_equal s2, patch(s1, patch_set, :patch) + assert_equal s2, patch!(s1, patch_set) + assert_equal s1, patch(s2, patch_set) + assert_equal s1, patch(s2, patch_set, :unpatch) + assert_equal s1, unpatch!(s2, patch_set) + end + + def test_diff_patchset_empty_returns_source_string + diff = diff(hello, hello) + assert_equal hello, patch(hello, diff) + end + + def test_diff_patchset_empty_returns_source_array + diff = diff(hello_ary, hello_ary) + assert_equal hello_ary, patch(hello_ary, diff) + end + + def test_diff_patchset_default_callbacks_forward + patch_set = diff(seq1, seq2) + patch_sequences_correctly(seq1, seq2, patch_set) + end + + def test_diff_patchset_default_callbacks_reverse + patch_set = diff(seq2, seq1) + patch_sequences_correctly(seq2, seq1, patch_set) + end + + def test_diff_patchset_context_callbacks_forward + patch_set = diff(seq1, seq2, Diff::LCS::ContextDiffCallbacks) + patch_sequences_correctly(seq1, seq2, patch_set) + end + + def test_diff_patchset_context_callbacks_reverse + patch_set = diff(seq2, seq1, Diff::LCS::ContextDiffCallbacks) + patch_sequences_correctly(seq2, seq1, patch_set) + end + + def test_diff_patchset_sdiff_callbacks_forward + patch_set = diff(seq1, seq2, Diff::LCS::SDiffCallbacks) + patch_sequences_correctly(seq1, seq2, patch_set) + end + + def test_diff_patchset_sdiff_callbacks_reverse + patch_set = diff(seq2, seq1, Diff::LCS::SDiffCallbacks) + patch_sequences_correctly(seq2, seq1, patch_set) + end + + def test_sdiff_patchset_empty_returns_source_string + assert_equal hello, patch(hello, sdiff(hello, hello)) + end + + def test_sdiff_patchset_empty_returns_source_array + assert_equal hello_ary, patch(hello_ary, sdiff(hello_ary, hello_ary)) + end + + def test_sdiff_patchset_diff_callbacks_forward + patch_set = sdiff(seq1, seq2, Diff::LCS::DiffCallbacks) + patch_sequences_correctly(seq1, seq2, patch_set) + end + + def test_sdiff_patchset_diff_callbacks_reverse + patch_set = sdiff(seq2, seq1, Diff::LCS::DiffCallbacks) + patch_sequences_correctly(seq2, seq1, patch_set) + end + + def test_sdiff_patchset_context_callbacks_forward + patch_set = sdiff(seq1, seq2, Diff::LCS::ContextDiffCallbacks) + patch_sequences_correctly(seq1, seq2, patch_set) + end + + def test_sdiff_patchset_context_callbacks_reverse + patch_set = sdiff(seq2, seq1, Diff::LCS::ContextDiffCallbacks) + patch_sequences_correctly(seq2, seq1, patch_set) + end + + def test_sdiff_patchset_sdiff_callbacks_forward + patch_set = sdiff(seq1, seq2) + patch_sequences_correctly(seq1, seq2, patch_set) + end + + def test_sdiff_patchset_sdiff_callbacks_reverse + patch_set = sdiff(seq2, seq1) + patch_sequences_correctly(seq2, seq1, patch_set) + end + + def test_bug_891_diff_default_callbacks_autodiscover_s1_to_s2 + s1 = %w[a b c d e f g h i j k] # standard:disable Layout/SpaceInsideArrayPercentLiteral + s2 = %w[a b c d D e f g h i j k] + patch_set = diff(s1, s2) + assert_equal s2, patch(s1, patch_set) + end + + def test_bug_891_diff_default_callbacks_autodiscover_s2_to_s1 + s1 = %w[a b c d e f g h i j k] # standard:disable Layout/SpaceInsideArrayPercentLiteral + s2 = %w[a b c d D e f g h i j k] + patch_set = diff(s2, s1) + assert_equal s2, patch(s1, patch_set) + end + + def test_bug_891_diff_default_callbacks_left_to_right + s1 = %w[a b c d e f g h i j k] # standard:disable Layout/SpaceInsideArrayPercentLiteral + s2 = %w[a b c d D e f g h i j k] + patch_set_s1_s2 = diff(s1, s2) + patch_set_s2_s1 = diff(s2, s1) + assert_equal s1, patch(s2, patch_set_s2_s1) + assert_equal s1, patch(s2, patch_set_s1_s2) + end + + def test_bug_891_diff_default_callbacks_explicit_patch + s1 = %w[a b c d e f g h i j k] # standard:disable Layout/SpaceInsideArrayPercentLiteral + s2 = %w[a b c d D e f g h i j k] + patch_set_s1_s2 = diff(s1, s2) + patch_set_s2_s1 = diff(s2, s1) + assert_equal s2, patch(s1, patch_set_s1_s2, :patch) + assert_equal s1, patch(s2, patch_set_s2_s1, :patch) + assert_equal s2, patch!(s1, patch_set_s1_s2) + assert_equal s1, patch!(s2, patch_set_s2_s1) + end + + def test_bug_891_diff_default_callbacks_explicit_unpatch + s1 = %w[a b c d e f g h i j k] # standard:disable Layout/SpaceInsideArrayPercentLiteral + s2 = %w[a b c d D e f g h i j k] + patch_set_s1_s2 = diff(s1, s2) + patch_set_s2_s1 = diff(s2, s1) + assert_equal s1, patch(s2, patch_set_s1_s2, :unpatch) + assert_equal s2, patch(s1, patch_set_s2_s1, :unpatch) + assert_equal s1, unpatch!(s2, patch_set_s1_s2) + assert_equal s2, unpatch!(s1, patch_set_s2_s1) + end + + def test_bug_891_diff_context_callbacks_autodiscover_s1_to_s2 + s1 = %w[a b c d e f g h i j k] # standard:disable Layout/SpaceInsideArrayPercentLiteral + s2 = %w[a b c d D e f g h i j k] + patch_set = diff(s1, s2, Diff::LCS::ContextDiffCallbacks) + assert_equal s2, patch(s1, patch_set) + end + + def test_bug_891_diff_context_callbacks_autodiscover_s2_to_s1 + s1 = %w[a b c d e f g h i j k] # standard:disable Layout/SpaceInsideArrayPercentLiteral + s2 = %w[a b c d D e f g h i j k] + patch_set = diff(s2, s1, Diff::LCS::ContextDiffCallbacks) + assert_equal s2, patch(s1, patch_set) + end + + def test_bug_891_diff_context_callbacks_left_to_right + s1 = %w[a b c d e f g h i j k] # standard:disable Layout/SpaceInsideArrayPercentLiteral + s2 = %w[a b c d D e f g h i j k] + patch_set_s1_s2 = diff(s1, s2, Diff::LCS::ContextDiffCallbacks) + patch_set_s2_s1 = diff(s2, s1, Diff::LCS::ContextDiffCallbacks) + assert_equal s1, patch(s2, patch_set_s2_s1) + assert_equal s1, patch(s2, patch_set_s1_s2) + end + + def test_bug_891_diff_context_callbacks_explicit_patch + s1 = %w[a b c d e f g h i j k] # standard:disable Layout/SpaceInsideArrayPercentLiteral + s2 = %w[a b c d D e f g h i j k] + patch_set_s1_s2 = diff(s1, s2, Diff::LCS::ContextDiffCallbacks) + patch_set_s2_s1 = diff(s2, s1, Diff::LCS::ContextDiffCallbacks) + assert_equal s2, patch(s1, patch_set_s1_s2, :patch) + assert_equal s1, patch(s2, patch_set_s2_s1, :patch) + assert_equal s2, patch!(s1, patch_set_s1_s2) + assert_equal s1, patch!(s2, patch_set_s2_s1) + end + + def test_bug_891_diff_context_callbacks_explicit_unpatch + s1 = %w[a b c d e f g h i j k] # standard:disable Layout/SpaceInsideArrayPercentLiteral + s2 = %w[a b c d D e f g h i j k] + patch_set_s1_s2 = diff(s1, s2, Diff::LCS::ContextDiffCallbacks) + patch_set_s2_s1 = diff(s2, s1, Diff::LCS::ContextDiffCallbacks) + assert_equal s1, patch(s2, patch_set_s1_s2, :unpatch) + assert_equal s2, patch(s1, patch_set_s2_s1, :unpatch) + assert_equal s1, unpatch!(s2, patch_set_s1_s2) + assert_equal s2, unpatch!(s1, patch_set_s2_s1) + end + + def test_bug_891_diff_sdiff_callbacks_autodiscover_s1_to_s2 + s1 = %w[a b c d e f g h i j k] # standard:disable Layout/SpaceInsideArrayPercentLiteral + s2 = %w[a b c d D e f g h i j k] + patch_set = diff(s1, s2, Diff::LCS::SDiffCallbacks) + assert_equal s2, patch(s1, patch_set) + end + + def test_bug_891_diff_sdiff_callbacks_autodiscover_s2_to_s1 + s1 = %w[a b c d e f g h i j k] # standard:disable Layout/SpaceInsideArrayPercentLiteral + s2 = %w[a b c d D e f g h i j k] + patch_set = diff(s2, s1, Diff::LCS::SDiffCallbacks) + assert_equal s2, patch(s1, patch_set) + end + + def test_bug_891_diff_sdiff_callbacks_left_to_right + s1 = %w[a b c d e f g h i j k] # standard:disable Layout/SpaceInsideArrayPercentLiteral + s2 = %w[a b c d D e f g h i j k] + patch_set_s1_s2 = diff(s1, s2, Diff::LCS::SDiffCallbacks) + patch_set_s2_s1 = diff(s2, s1, Diff::LCS::SDiffCallbacks) + assert_equal s1, patch(s2, patch_set_s2_s1) + assert_equal s1, patch(s2, patch_set_s1_s2) + end + + def test_bug_891_diff_sdiff_callbacks_explicit_patch + s1 = %w[a b c d e f g h i j k] # standard:disable Layout/SpaceInsideArrayPercentLiteral + s2 = %w[a b c d D e f g h i j k] + patch_set_s1_s2 = diff(s1, s2, Diff::LCS::SDiffCallbacks) + patch_set_s2_s1 = diff(s2, s1, Diff::LCS::SDiffCallbacks) + assert_equal s2, patch(s1, patch_set_s1_s2, :patch) + assert_equal s1, patch(s2, patch_set_s2_s1, :patch) + assert_equal s2, patch!(s1, patch_set_s1_s2) + assert_equal s1, patch!(s2, patch_set_s2_s1) + end + + def test_bug_891_diff_sdiff_callbacks_explicit_unpatch + s1 = %w[a b c d e f g h i j k] # standard:disable Layout/SpaceInsideArrayPercentLiteral + s2 = %w[a b c d D e f g h i j k] + patch_set_s1_s2 = diff(s1, s2, Diff::LCS::SDiffCallbacks) + patch_set_s2_s1 = diff(s2, s1, Diff::LCS::SDiffCallbacks) + assert_equal s1, patch(s2, patch_set_s1_s2, :unpatch) + assert_equal s2, patch(s1, patch_set_s2_s1, :unpatch) + assert_equal s1, unpatch!(s2, patch_set_s1_s2) + assert_equal s2, unpatch!(s1, patch_set_s2_s1) + end + + def test_bug_891_sdiff_default_callbacks_autodiscover_s1_to_s2 + s1 = %w[a b c d e f g h i j k] # standard:disable Layout/SpaceInsideArrayPercentLiteral + s2 = %w[a b c d D e f g h i j k] + patch_set = sdiff(s1, s2) + assert_equal s2, patch(s1, patch_set) + end + + def test_bug_891_sdiff_default_callbacks_autodiscover_s2_to_s1 + s1 = %w[a b c d e f g h i j k] # standard:disable Layout/SpaceInsideArrayPercentLiteral + s2 = %w[a b c d D e f g h i j k] + patch_set = sdiff(s2, s1) + assert_equal s2, patch(s1, patch_set) + end + + def test_bug_891_sdiff_default_callbacks_left_to_right + s1 = %w[a b c d e f g h i j k] # standard:disable Layout/SpaceInsideArrayPercentLiteral + s2 = %w[a b c d D e f g h i j k] + patch_set_s1_s2 = sdiff(s1, s2) + patch_set_s2_s1 = sdiff(s2, s1) + assert_equal s1, patch(s2, patch_set_s2_s1) + assert_equal s1, patch(s2, patch_set_s1_s2) + end + + def test_bug_891_sdiff_default_callbacks_explicit_patch + s1 = %w[a b c d e f g h i j k] # standard:disable Layout/SpaceInsideArrayPercentLiteral + s2 = %w[a b c d D e f g h i j k] + patch_set_s1_s2 = sdiff(s1, s2) + patch_set_s2_s1 = sdiff(s2, s1) + assert_equal s2, patch(s1, patch_set_s1_s2, :patch) + assert_equal s1, patch(s2, patch_set_s2_s1, :patch) + assert_equal s2, patch!(s1, patch_set_s1_s2) + assert_equal s1, patch!(s2, patch_set_s2_s1) + end + + def test_bug_891_sdiff_default_callbacks_explicit_unpatch + s1 = %w[a b c d e f g h i j k] # standard:disable Layout/SpaceInsideArrayPercentLiteral + s2 = %w[a b c d D e f g h i j k] + patch_set_s1_s2 = sdiff(s1, s2) + patch_set_s2_s1 = sdiff(s2, s1) + assert_equal s1, patch(s2, patch_set_s1_s2, :unpatch) + assert_equal s2, patch(s1, patch_set_s2_s1, :unpatch) + assert_equal s1, unpatch!(s2, patch_set_s1_s2) + assert_equal s2, unpatch!(s1, patch_set_s2_s1) + end + + def test_bug_891_sdiff_context_callbacks_autodiscover_s1_to_s2 + s1 = %w[a b c d e f g h i j k] # standard:disable Layout/SpaceInsideArrayPercentLiteral + s2 = %w[a b c d D e f g h i j k] + patch_set = sdiff(s1, s2, Diff::LCS::ContextDiffCallbacks) + assert_equal s2, patch(s1, patch_set) + end + + def test_bug_891_sdiff_context_callbacks_autodiscover_s2_to_s1 + s1 = %w[a b c d e f g h i j k] # standard:disable Layout/SpaceInsideArrayPercentLiteral + s2 = %w[a b c d D e f g h i j k] + patch_set = sdiff(s2, s1, Diff::LCS::ContextDiffCallbacks) + assert_equal s2, patch(s1, patch_set) + end + + def test_bug_891_sdiff_context_callbacks_left_to_right + s1 = %w[a b c d e f g h i j k] # standard:disable Layout/SpaceInsideArrayPercentLiteral + s2 = %w[a b c d D e f g h i j k] + patch_set_s1_s2 = sdiff(s1, s2, Diff::LCS::ContextDiffCallbacks) + patch_set_s2_s1 = sdiff(s2, s1, Diff::LCS::ContextDiffCallbacks) + assert_equal s1, patch(s2, patch_set_s2_s1) + assert_equal s1, patch(s2, patch_set_s1_s2) + end + + def test_bug_891_sdiff_context_callbacks_explicit_patch + s1 = %w[a b c d e f g h i j k] # standard:disable Layout/SpaceInsideArrayPercentLiteral + s2 = %w[a b c d D e f g h i j k] + patch_set_s1_s2 = sdiff(s1, s2, Diff::LCS::ContextDiffCallbacks) + patch_set_s2_s1 = sdiff(s2, s1, Diff::LCS::ContextDiffCallbacks) + assert_equal s2, patch(s1, patch_set_s1_s2, :patch) + assert_equal s1, patch(s2, patch_set_s2_s1, :patch) + assert_equal s2, patch!(s1, patch_set_s1_s2) + assert_equal s1, patch!(s2, patch_set_s2_s1) + end + + def test_bug_891_sdiff_context_callbacks_explicit_unpatch + s1 = %w[a b c d e f g h i j k] # standard:disable Layout/SpaceInsideArrayPercentLiteral + s2 = %w[a b c d D e f g h i j k] + patch_set_s1_s2 = sdiff(s1, s2, Diff::LCS::ContextDiffCallbacks) + patch_set_s2_s1 = sdiff(s2, s1, Diff::LCS::ContextDiffCallbacks) + assert_equal s1, patch(s2, patch_set_s1_s2, :unpatch) + assert_equal s2, patch(s1, patch_set_s2_s1, :unpatch) + assert_equal s1, unpatch!(s2, patch_set_s1_s2) + assert_equal s2, unpatch!(s1, patch_set_s2_s1) + end + + def test_bug_891_sdiff_diff_callbacks_autodiscover_s1_to_s2 + s1 = %w[a b c d e f g h i j k] # standard:disable Layout/SpaceInsideArrayPercentLiteral + s2 = %w[a b c d D e f g h i j k] + patch_set = sdiff(s1, s2, Diff::LCS::DiffCallbacks) + assert_equal s2, patch(s1, patch_set) + end + + def test_bug_891_sdiff_diff_callbacks_autodiscover_s2_to_s1 + s1 = %w[a b c d e f g h i j k] # standard:disable Layout/SpaceInsideArrayPercentLiteral + s2 = %w[a b c d D e f g h i j k] + patch_set = sdiff(s2, s1, Diff::LCS::DiffCallbacks) + assert_equal s2, patch(s1, patch_set) + end + + def test_bug_891_sdiff_diff_callbacks_left_to_right + s1 = %w[a b c d e f g h i j k] # standard:disable Layout/SpaceInsideArrayPercentLiteral + s2 = %w[a b c d D e f g h i j k] + patch_set_s1_s2 = sdiff(s1, s2, Diff::LCS::DiffCallbacks) + patch_set_s2_s1 = sdiff(s2, s1, Diff::LCS::DiffCallbacks) + assert_equal s1, patch(s2, patch_set_s2_s1) + assert_equal s1, patch(s2, patch_set_s1_s2) + end + + def test_bug_891_sdiff_diff_callbacks_explicit_patch + s1 = %w[a b c d e f g h i j k] # standard:disable Layout/SpaceInsideArrayPercentLiteral + s2 = %w[a b c d D e f g h i j k] + patch_set_s1_s2 = sdiff(s1, s2, Diff::LCS::DiffCallbacks) + patch_set_s2_s1 = sdiff(s2, s1, Diff::LCS::DiffCallbacks) + assert_equal s2, patch(s1, patch_set_s1_s2, :patch) + assert_equal s1, patch(s2, patch_set_s2_s1, :patch) + assert_equal s2, patch!(s1, patch_set_s1_s2) + assert_equal s1, patch!(s2, patch_set_s2_s1) + end + + def test_bug_891_sdiff_diff_callbacks_explicit_unpatch + s1 = %w[a b c d e f g h i j k] # standard:disable Layout/SpaceInsideArrayPercentLiteral + s2 = %w[a b c d D e f g h i j k] + patch_set_s1_s2 = sdiff(s1, s2, Diff::LCS::DiffCallbacks) + patch_set_s2_s1 = sdiff(s2, s1, Diff::LCS::DiffCallbacks) + assert_equal s1, patch(s2, patch_set_s1_s2, :unpatch) + assert_equal s2, patch(s1, patch_set_s2_s1, :unpatch) + assert_equal s1, unpatch!(s2, patch_set_s1_s2) + assert_equal s2, unpatch!(s1, patch_set_s2_s1) + end +end diff --git a/test/test_sdiff.rb b/test/test_sdiff.rb new file mode 100644 index 00000000..6c481dfe --- /dev/null +++ b/test/test_sdiff.rb @@ -0,0 +1,167 @@ +# frozen_string_literal: true + +require_relative "test_helper" + +class TestSDiff < Minitest::Test + def compare_sequences_correctly(s1, s2, result) + assert_equal context_diff(result), sdiff(s1, s2) + assert_equal context_diff(reverse_sdiff(result)), sdiff(s2, s1) + end + + def test_seq1_seq2 + compare_sequences_correctly(seq1, seq2, correct_forward_sdiff) + end + + def test_abc_def_yyy_xxx_ghi_jkl + s1 = %w[abc def yyy xxx ghi jkl] + s2 = %w[abc dxf xxx ghi jkl] + result = [ + ["=", [0, "abc"], [0, "abc"]], + ["!", [1, "def"], [1, "dxf"]], + ["-", [2, "yyy"], [2, nil]], + ["=", [3, "xxx"], [2, "xxx"]], + ["=", [4, "ghi"], [3, "ghi"]], + ["=", [5, "jkl"], [4, "jkl"]] + ] + compare_sequences_correctly(s1, s2, result) + end + + def test_a_b_c_d_e_vs_a_e + s1 = %w[a b c d e] + s2 = %w[a e] + result = [ + ["=", [0, "a"], [0, "a"]], + ["-", [1, "b"], [1, nil]], + ["-", [2, "c"], [1, nil]], + ["-", [3, "d"], [1, nil]], + ["=", [4, "e"], [1, "e"]] + ] + compare_sequences_correctly(s1, s2, result) + end + + def test_a_e_vs_a_b_c_d_e + s1 = %w[a e] + s2 = %w[a b c d e] + result = [ + ["=", [0, "a"], [0, "a"]], + ["+", [1, nil], [1, "b"]], + ["+", [1, nil], [2, "c"]], + ["+", [1, nil], [3, "d"]], + ["=", [1, "e"], [4, "e"]] + ] + compare_sequences_correctly(s1, s2, result) + end + + def test_v_x_a_e_vs_w_y_a_b_c_d_e + s1 = %w[v x a e] + s2 = %w[w y a b c d e] + result = [ + ["!", [0, "v"], [0, "w"]], + ["!", [1, "x"], [1, "y"]], + ["=", [2, "a"], [2, "a"]], + ["+", [3, nil], [3, "b"]], + ["+", [3, nil], [4, "c"]], + ["+", [3, nil], [5, "d"]], + ["=", [3, "e"], [6, "e"]] + ] + compare_sequences_correctly(s1, s2, result) + end + + def test_x_a_e_vs_a_b_c_d_e + s1 = %w[x a e] + s2 = %w[a b c d e] + result = [ + ["-", [0, "x"], [0, nil]], + ["=", [1, "a"], [0, "a"]], + ["+", [2, nil], [1, "b"]], + ["+", [2, nil], [2, "c"]], + ["+", [2, nil], [3, "d"]], + ["=", [2, "e"], [4, "e"]] + ] + compare_sequences_correctly(s1, s2, result) + end + + def test_a_e_vs_x_a_b_c_d_e + s1 = %w[a e] + s2 = %w[x a b c d e] + result = [ + ["+", [0, nil], [0, "x"]], + ["=", [0, "a"], [1, "a"]], + ["+", [1, nil], [2, "b"]], + ["+", [1, nil], [3, "c"]], + ["+", [1, nil], [4, "d"]], + ["=", [1, "e"], [5, "e"]] + ] + compare_sequences_correctly(s1, s2, result) + end + + def test_a_e_v_vs_x_a_b_c_d_e_w_x + s1 = %w[a e v] + s2 = %w[x a b c d e w x] + result = [ + ["+", [0, nil], [0, "x"]], + ["=", [0, "a"], [1, "a"]], + ["+", [1, nil], [2, "b"]], + ["+", [1, nil], [3, "c"]], + ["+", [1, nil], [4, "d"]], + ["=", [1, "e"], [5, "e"]], + ["!", [2, "v"], [6, "w"]], + ["+", [3, nil], [7, "x"]] + ] + compare_sequences_correctly(s1, s2, result) + end + + def test_empty_vs_a_b_c + s1 = %w[] + s2 = %w[a b c] + result = [ + ["+", [0, nil], [0, "a"]], + ["+", [0, nil], [1, "b"]], + ["+", [0, nil], [2, "c"]] + ] + compare_sequences_correctly(s1, s2, result) + end + + def test_a_b_c_vs_1 + s1 = %w[a b c] + s2 = %w[1] + result = [ + ["!", [0, "a"], [0, "1"]], + ["-", [1, "b"], [1, nil]], + ["-", [2, "c"], [1, nil]] + ] + compare_sequences_correctly(s1, s2, result) + end + + def test_a_b_c_vs_c + s1 = %w[a b c] + s2 = %w[c] + result = [ + ["-", [0, "a"], [0, nil]], + ["-", [1, "b"], [0, nil]], + ["=", [2, "c"], [0, "c"]] + ] + compare_sequences_correctly(s1, s2, result) + end + + def test_abcd_efgh_ijkl_mnop_vs_empty + s1 = %w[abcd efgh ijkl mnop] + s2 = [] + result = [ + ["-", [0, "abcd"], [0, nil]], + ["-", [1, "efgh"], [0, nil]], + ["-", [2, "ijkl"], [0, nil]], + ["-", [3, "mnop"], [0, nil]] + ] + compare_sequences_correctly(s1, s2, result) + end + + def test_nested_array_vs_empty + s1 = [[1, 2]] + s2 = [] + result = [ + ["-", [0, [1, 2]], [0, nil]] + ] + compare_sequences_correctly(s1, s2, result) + end +end diff --git a/test/test_traverse_balanced.rb b/test/test_traverse_balanced.rb new file mode 100644 index 00000000..48c76072 --- /dev/null +++ b/test/test_traverse_balanced.rb @@ -0,0 +1,322 @@ +# frozen_string_literal: true + +require "test_helper" + +class TestTraverseBalanced < Minitest::Test + def balanced_traversal(s1, s2, callback_type) + callback = send(callback_type) + traverse_balanced(s1, s2, callback) + callback + end + + def balanced_reverse(change_result) + new_result = [] + change_result.each do |line| + line = [line[0], line[2], line[1]] + case line[0] + when "<" + line[0] = ">" + when ">" + line[0] = "<" + end + new_result << line + end + new_result.sort_by { |line| [line[1], line[2]] } + end + + def map_to_no_change(change_result) + new_result = [] + change_result.each do |line| + case line[0] + when "!" + new_result << ["<", line[1], line[2]] + new_result << [">", line[1] + 1, line[2]] + else + new_result << line + end + end + new_result + end + + class BalancedCallback + def initialize + reset + end + + attr_reader :result + + def reset + @result = [] + end + + def match(event) + @result << ["=", event.old_position, event.new_position] + end + + def discard_a(event) + @result << ["<", event.old_position, event.new_position] + end + + def discard_b(event) + @result << [">", event.old_position, event.new_position] + end + + def change(event) + @result << ["!", event.old_position, event.new_position] + end + end + + def balanced_callback + BalancedCallback.new + end + + class BalancedCallbackNoChange < BalancedCallback + undef :change + end + + def balanced_callback_no_change + BalancedCallbackNoChange.new + end + + def assert_traversal_with_change(s1, s2, result) + traversal = balanced_traversal(s1, s2, :balanced_callback) + assert_equal result, traversal.result + + traversal = balanced_traversal(s2, s1, :balanced_callback) + assert_equal balanced_reverse(result), traversal.result + end + + def assert_traversal_without_change(s1, s2, result) + traversal = balanced_traversal(s1, s2, :balanced_callback_no_change) + assert_equal map_to_no_change(result), traversal.result + + traversal = balanced_traversal(s2, s1, :balanced_callback_no_change) + assert_equal map_to_no_change(balanced_reverse(result)), traversal.result + end + + def test_identical_string_sequences_abc + s1 = s2 = "abc" + result = [ + ["=", 0, 0], + ["=", 1, 1], + ["=", 2, 2] + ] + assert_traversal_with_change(s1, s2, result) + assert_traversal_without_change(s1, s2, result) + end + + def test_identical_array_sequences_a_b_c + s1 = s2 = %w[a b c] + result = [ + ["=", 0, 0], + ["=", 1, 1], + ["=", 2, 2] + ] + assert_traversal_with_change(s1, s2, result) + assert_traversal_without_change(s1, s2, result) + end + + def test_sequences_a_b_c_and_a_x_c + s1 = %w[a b c] + s2 = %w[a x c] + result = [ + ["=", 0, 0], + ["!", 1, 1], + ["=", 2, 2] + ] + assert_traversal_with_change(s1, s2, result) + assert_traversal_without_change(s1, s2, result) + end + + def test_sequences_a_x_y_c_and_a_v_w_c + s1 = %w[a x y c] + s2 = %w[a v w c] + result = [ + ["=", 0, 0], + ["!", 1, 1], + ["!", 2, 2], + ["=", 3, 3] + ] + assert_traversal_with_change(s1, s2, result) + assert_traversal_without_change(s1, s2, result) + end + + def test_sequences_x_y_c_and_v_w_c + s1 = %w[x y c] + s2 = %w[v w c] + result = [ + ["!", 0, 0], + ["!", 1, 1], + ["=", 2, 2] + ] + assert_traversal_with_change(s1, s2, result) + assert_traversal_without_change(s1, s2, result) + end + + def test_sequences_a_x_y_z_and_b_v_w + s1 = %w[a x y z] + s2 = %w[b v w] + result = [ + ["!", 0, 0], + ["!", 1, 1], + ["!", 2, 2], + ["<", 3, 3] + ] + assert_traversal_with_change(s1, s2, result) + assert_traversal_without_change(s1, s2, result) + end + + def test_sequences_a_z_and_a + s1 = %w[a z] + s2 = %w[a] + result = [ + ["=", 0, 0], + ["<", 1, 1] + ] + assert_traversal_with_change(s1, s2, result) + assert_traversal_without_change(s1, s2, result) + end + + def test_sequences_z_a_and_a + s1 = %w[z a] + s2 = %w[a] + result = [ + ["<", 0, 0], + ["=", 1, 0] + ] + assert_traversal_with_change(s1, s2, result) + assert_traversal_without_change(s1, s2, result) + end + + def test_sequences_a_b_c_and_x_y_z + s1 = %w[a b c] + s2 = %w[x y z] + result = [ + ["!", 0, 0], + ["!", 1, 1], + ["!", 2, 2] + ] + assert_traversal_with_change(s1, s2, result) + assert_traversal_without_change(s1, s2, result) + end + + def test_strings_a_b_c_and_a_x_c + s1 = "a b c" + s2 = "a x c" + result = [ + ["=", 0, 0], + ["=", 1, 1], + ["!", 2, 2], + ["=", 3, 3], + ["=", 4, 4] + ] + assert_traversal_with_change(s1, s2, result) + assert_traversal_without_change(s1, s2, result) + end + + def test_strings_a_x_y_c_and_a_v_w_c + s1 = "a x y c" + s2 = "a v w c" + result = [ + ["=", 0, 0], + ["=", 1, 1], + ["!", 2, 2], + ["=", 3, 3], + ["!", 4, 4], + ["=", 5, 5], + ["=", 6, 6] + ] + assert_traversal_with_change(s1, s2, result) + assert_traversal_without_change(s1, s2, result) + end + + def test_strings_x_y_c_and_v_w_c + s1 = "x y c" + s2 = "v w c" + result = [ + ["!", 0, 0], + ["=", 1, 1], + ["!", 2, 2], + ["=", 3, 3], + ["=", 4, 4] + ] + assert_traversal_with_change(s1, s2, result) + assert_traversal_without_change(s1, s2, result) + end + + def test_strings_a_z_and_a + s1 = "a z" + s2 = "a" + result = [ + ["=", 0, 0], + ["<", 1, 1], + ["<", 2, 1] + ] + assert_traversal_with_change(s1, s2, result) + assert_traversal_without_change(s1, s2, result) + end + + def test_strings_z_a_and_a + s1 = "z a" + s2 = "a" + result = [ + ["<", 0, 0], + ["<", 1, 0], + ["=", 2, 0] + ] + assert_traversal_with_change(s1, s2, result) + assert_traversal_without_change(s1, s2, result) + end + + def test_strings_a_b_c_and_x_y_z + s1 = "a b c" + s2 = "x y z" + result = [ + ["!", 0, 0], + ["=", 1, 1], + ["!", 2, 2], + ["=", 3, 3], + ["!", 4, 4] + ] + assert_traversal_with_change(s1, s2, result) + assert_traversal_without_change(s1, s2, result) + end + + def test_strings_abcd_efgh_ijkl_mnopqrstuvwxyz_and_empty + s1 = "abcd efgh ijkl mnopqrstuvwxyz" + s2 = "" + result = [ + ["<", 0, 0], + ["<", 1, 0], + ["<", 2, 0], + ["<", 3, 0], + ["<", 4, 0], + ["<", 5, 0], + ["<", 6, 0], + ["<", 7, 0], + ["<", 8, 0], + ["<", 9, 0], + ["<", 10, 0], + ["<", 11, 0], + ["<", 12, 0], + ["<", 13, 0], + ["<", 14, 0], + ["<", 15, 0], + ["<", 16, 0], + ["<", 17, 0], + ["<", 18, 0], + ["<", 19, 0], + ["<", 20, 0], + ["<", 21, 0], + ["<", 22, 0], + ["<", 23, 0], + ["<", 24, 0], + ["<", 25, 0], + ["<", 26, 0], + ["<", 27, 0], + ["<", 28, 0] + ] + assert_traversal_with_change(s1, s2, result) + assert_traversal_without_change(s1, s2, result) + end +end diff --git a/test/test_traverse_sequences.rb b/test/test_traverse_sequences.rb new file mode 100644 index 00000000..833bfab1 --- /dev/null +++ b/test/test_traverse_sequences.rb @@ -0,0 +1,187 @@ +# frozen_string_literal: true + +require_relative "test_helper" + +class TestTraverseSequences < Minitest::Test + def test_callback_with_no_finishers_over_seq1_seq2_has_correct_lcs_result_on_left_matches + callback_s1_s2 = simple_callback_no_finishers + traverse_sequences(seq1, seq2, callback_s1_s2) + + callback_s2_s1 = simple_callback_no_finishers + traverse_sequences(seq2, seq1, callback_s2_s1) + + assert_equal correct_lcs, callback_s1_s2.matched_a + assert_equal correct_lcs, callback_s2_s1.matched_a + end + + def test_callback_with_no_finishers_over_seq1_seq2_has_correct_lcs_result_on_right_matches + callback_s1_s2 = simple_callback_no_finishers + traverse_sequences(seq1, seq2, callback_s1_s2) + + callback_s2_s1 = simple_callback_no_finishers + traverse_sequences(seq2, seq1, callback_s2_s1) + + assert_equal correct_lcs, callback_s1_s2.matched_b + assert_equal correct_lcs, callback_s2_s1.matched_b + end + + def test_callback_with_no_finishers_over_seq1_seq2_has_correct_skipped_sequences_with_left_sequence + callback_s1_s2 = simple_callback_no_finishers + traverse_sequences(seq1, seq2, callback_s1_s2) + + callback_s2_s1 = simple_callback_no_finishers + traverse_sequences(seq2, seq1, callback_s2_s1) + + assert_equal skipped_seq1, callback_s1_s2.discards_a + assert_equal skipped_seq2, callback_s2_s1.discards_a + end + + def test_callback_with_no_finishers_over_seq1_seq2_has_correct_skipped_sequences_with_right_sequence + callback_s1_s2 = simple_callback_no_finishers + traverse_sequences(seq1, seq2, callback_s1_s2) + + callback_s2_s1 = simple_callback_no_finishers + traverse_sequences(seq2, seq1, callback_s2_s1) + + assert_equal skipped_seq2, callback_s1_s2.discards_b + assert_equal skipped_seq1, callback_s2_s1.discards_b + end + + def test_callback_with_no_finishers_over_seq1_seq2_does_not_have_done_markers + callback_s1_s2 = simple_callback_no_finishers + traverse_sequences(seq1, seq2, callback_s1_s2) + + callback_s2_s1 = simple_callback_no_finishers + traverse_sequences(seq2, seq1, callback_s2_s1) + + assert_empty callback_s1_s2.done_a + assert_empty callback_s1_s2.done_b + assert_empty callback_s2_s1.done_a + assert_empty callback_s2_s1.done_b + end + + def test_callback_with_no_finishers_over_hello_hello_has_correct_lcs_result_on_left_matches + callback = simple_callback_no_finishers + traverse_sequences(hello, hello, callback) + + assert_equal hello.chars, callback.matched_a + end + + def test_callback_with_no_finishers_over_hello_hello_has_correct_lcs_result_on_right_matches + callback = simple_callback_no_finishers + traverse_sequences(hello, hello, callback) + + assert_equal hello.chars, callback.matched_b + end + + def test_callback_with_no_finishers_over_hello_hello_has_correct_skipped_sequences_with_left_sequence + callback = simple_callback_no_finishers + traverse_sequences(hello, hello, callback) + + assert_empty callback.discards_a + end + + def test_callback_with_no_finishers_over_hello_hello_has_correct_skipped_sequences_with_right_sequence + callback = simple_callback_no_finishers + traverse_sequences(hello, hello, callback) + + assert_empty callback.discards_b + end + + def test_callback_with_no_finishers_over_hello_hello_does_not_have_done_markers + callback = simple_callback_no_finishers + traverse_sequences(hello, hello, callback) + + assert_empty callback.done_a + assert_empty callback.done_b + end + + def test_callback_with_no_finishers_over_hello_ary_hello_ary_has_correct_lcs_result_on_left_matches + callback = simple_callback_no_finishers + traverse_sequences(hello_ary, hello_ary, callback) + + assert_equal hello_ary, callback.matched_a + end + + def test_callback_with_no_finishers_over_hello_ary_hello_ary_has_correct_lcs_result_on_right_matches + callback = simple_callback_no_finishers + traverse_sequences(hello_ary, hello_ary, callback) + + assert_equal hello_ary, callback.matched_b + end + + def test_callback_with_no_finishers_over_hello_ary_hello_ary_has_correct_skipped_sequences_with_left_sequence + callback = simple_callback_no_finishers + traverse_sequences(hello_ary, hello_ary, callback) + + assert_empty callback.discards_a + end + + def test_callback_with_no_finishers_over_hello_ary_hello_ary_has_correct_skipped_sequences_with_right_sequence + callback = simple_callback_no_finishers + traverse_sequences(hello_ary, hello_ary, callback) + + assert_empty callback.discards_b + end + + def test_callback_with_no_finishers_over_hello_ary_hello_ary_does_not_have_done_markers + callback = simple_callback_no_finishers + traverse_sequences(hello_ary, hello_ary, callback) + + assert_empty callback.done_a + assert_empty callback.done_b + end + + def test_callback_with_finisher_has_correct_lcs_result_on_left_matches + callback_s1_s2 = simple_callback + traverse_sequences(seq1, seq2, callback_s1_s2) + callback_s2_s1 = simple_callback + traverse_sequences(seq2, seq1, callback_s2_s1) + + assert_equal correct_lcs, callback_s1_s2.matched_a + assert_equal correct_lcs, callback_s2_s1.matched_a + end + + def test_callback_with_finisher_has_correct_lcs_result_on_right_matches + callback_s1_s2 = simple_callback + traverse_sequences(seq1, seq2, callback_s1_s2) + callback_s2_s1 = simple_callback + traverse_sequences(seq2, seq1, callback_s2_s1) + + assert_equal correct_lcs, callback_s1_s2.matched_b + assert_equal correct_lcs, callback_s2_s1.matched_b + end + + def test_callback_with_finisher_has_correct_skipped_sequences_for_left_sequence + callback_s1_s2 = simple_callback + traverse_sequences(seq1, seq2, callback_s1_s2) + callback_s2_s1 = simple_callback + traverse_sequences(seq2, seq1, callback_s2_s1) + + assert_equal skipped_seq1, callback_s1_s2.discards_a + assert_equal skipped_seq2, callback_s2_s1.discards_a + end + + def test_callback_with_finisher_has_correct_skipped_sequences_for_right_sequence + callback_s1_s2 = simple_callback + traverse_sequences(seq1, seq2, callback_s1_s2) + callback_s2_s1 = simple_callback + traverse_sequences(seq2, seq1, callback_s2_s1) + + assert_equal skipped_seq2, callback_s1_s2.discards_b + assert_equal skipped_seq1, callback_s2_s1.discards_b + end + + def test_callback_with_finisher_has_done_markers_differently_sized_sequences + callback_s1_s2 = simple_callback + traverse_sequences(seq1, seq2, callback_s1_s2) + callback_s2_s1 = simple_callback + traverse_sequences(seq2, seq1, callback_s2_s1) + + assert_equal [["p", 9, "t", 11]], callback_s1_s2.done_a + assert_empty callback_s1_s2.done_b + + assert_empty callback_s2_s1.done_a + assert_equal [["t", 11, "p", 9]], callback_s2_s1.done_b + end +end