116 Commits

Author SHA1 Message Date
Sylvestre Ledru 80b993141b cargo-dist: generate more targets 2024-05-19 11:59:26 +02:00
Daniel Hofstetter d922313c8c Merge pull request #71 from uutils/renovate/libfuzzer-sys-0.x
Update Rust crate libfuzzer-sys to 0.4.7
2024-05-01 13:37:01 +02:00
Daniel Hofstetter 3e246ab36c Merge pull request #72 from uutils/renovate/pretty_assertions-1.x
Update Rust crate pretty_assertions to 1.4.0
2024-05-01 13:29:09 +02:00
renovate[bot] 4b70969ff1 Update Rust crate pretty_assertions to 1.4.0 2024-05-01 09:56:55 +00:00
renovate[bot] 767c6f6c4a Update Rust crate libfuzzer-sys to 0.4.7 2024-05-01 09:56:50 +00:00
Sylvestre Ledru 1f896ca1ac Merge pull request #69 from oSoMoN/cargo-dist-version-0.13.3
CI: Update 'cargo dist' to version 0.13.3
2024-05-01 08:31:36 +02:00
Olivier Tilloy 713bd210ab CI: Update 'cargo dist' to version 0.13.3 2024-04-30 23:55:49 +02:00
Olivier Tilloy 61314eaf4e Merge pull request #65 from uutils/renovate/unicode-width-0.x
Update Rust crate unicode-width to 0.1.12
2024-04-30 18:47:39 +02:00
renovate[bot] bf9147733d Update Rust crate unicode-width to 0.1.12 2024-04-30 05:30:32 +00:00
Daniel Hofstetter ce8457cbdb Merge pull request #67 from oSoMoN/macos-ci-install-gpatch
CI: install GNU patch on MacOS (fixes #66)
2024-04-30 07:14:02 +02:00
Olivier Tilloy df778c610b CI: install GNU patch on MacOS (fixes #66) 2024-04-29 22:55:08 +02:00
Sylvestre Ledru d92132e721 version 0.4.1 2024-04-27 13:12:58 +02:00
Sylvestre Ledru 99d4d02985 add missing copyright 2024-04-27 13:12:16 +02:00
Olivier Tilloy e7dc6558c6 Merge pull request #56 from TanmayPatil105/handle-directory-input
Handle directory-file and file-directory comparisons in the diff
2024-04-23 22:40:28 +02:00
Tanmay Patil 8c6a648aef Merge branch 'main' into handle-directory-input 2024-04-23 23:11:31 +05:30
Tanmay Patil 0304391bc5 Create test files in temporary directory 2024-04-23 22:44:06 +05:30
Sylvestre Ledru 8de0ca60d1 Merge pull request #52 from oSoMoN/long-options
Handle long option names for the supported output styles…
2024-04-23 18:44:47 +02:00
Sylvestre Ledru 43b9c524d9 Merge pull request #62 from oSoMoN/integration-no-hardcoded-filename
Un-hardcode a test filename in an integration test (fixes #61)
2024-04-23 18:36:33 +02:00
Olivier Tilloy 3dc3fdf5cd Un-hardcode a test filename in an integration test (fixes #61) 2024-04-23 18:00:56 +02:00
Olivier Tilloy b7261a43f4 Break out the logic to match context/unified diff params into separate functions, for improved readability 2024-04-22 18:01:00 +02:00
Olivier Tilloy 37fe1ae808 Handle --normal, -e and --ed options 2024-04-22 18:01:00 +02:00
Olivier Tilloy 22d973fce6 Parse all valid arguments accepted by GNU diff to request a regular context (with an optional number of lines) 2024-04-22 18:01:00 +02:00
Olivier Tilloy fe28610f21 Parse all valid arguments accepted by GNU diff to request a unified context (with an optional number of lines) 2024-04-22 18:01:00 +02:00
Sylvestre Ledru 3a8eddfe2c Fix typos 2024-04-21 16:07:01 +02:00
Tanmay Patil 476e69ee20 Windows: Fix tests 2024-04-21 18:06:15 +05:30
Tanmay Patil 65993d6a13 Add tests for diff FILE DIRECTORY 2024-04-21 16:10:48 +05:30
Tanmay Patil 39d2ece187 Handle directory-file and file-directory comparisons in the diff
GNU diff treats `diff DIRECTORY FILE` as `diff DIRECTORY/FILE FILE`
2024-04-21 16:10:48 +05:30
Sylvestre Ledru 46a26e896b Merge pull request #58 from oSoMoN/ed-diff-tests-fix-path
Move test assertions in the cfg block where they belong (fixes #3)
2024-04-21 09:37:58 +02:00
Olivier Tilloy 14799eea89 Move test assertions in the cfg block where they belong 2024-04-21 00:13:52 +02:00
Olivier Tilloy 831348d1fc Fix file path in ed diff tests 2024-04-21 00:12:43 +02:00
Sylvestre Ledru 00a5c0ba44 Merge pull request #57 from oSoMoN/windows-fix-path-for-ci
CI: On Windows, use GNU's patch.exe instead of Strawberry Perl patch
2024-04-20 19:44:40 +02:00
Olivier Tilloy bf104648c1 CI: On Windows, use GNU's patch.exe instead of Strawberry Perl patch 2024-04-20 19:30:34 +02:00
Olivier Tilloy 5669f164b3 Merge pull request #34 from uutils/renovate/regex-1.x
Update Rust crate regex to 1.10.4
2024-04-17 19:19:00 +02:00
Sylvestre Ledru 11bf271666 Merge pull request #9 from uutils/renovate/codecov-codecov-action-4.x
Update codecov/codecov-action action to v4
2024-04-16 22:18:53 +02:00
renovate[bot] 674974d5e6 Update Rust crate regex to 1.10.4 2024-04-16 19:54:53 +00:00
Sylvestre Ledru 2ba35db431 Merge pull request #4 from uutils/renovate/diff-0.x
Update Rust crate diff to 0.1.13
2024-04-16 21:53:45 +02:00
renovate[bot] fcec7277c9 Update codecov/codecov-action action to v4 2024-04-16 19:35:04 +00:00
renovate[bot] b8efad6b90 Update Rust crate diff to 0.1.13 2024-04-16 19:35:01 +00:00
Sylvestre Ledru 68e2f51983 Merge pull request #54 from uutils/renovate/chrono-0.x
Update Rust crate chrono to 0.4.38
2024-04-16 21:34:16 +02:00
Sylvestre Ledru 4edaee190f Merge pull request #55 from oSoMoN/use-codecov-token
Use the private Codecov token stored as a secret,
2024-04-16 21:34:01 +02:00
Olivier Tilloy 7f7821f558 Use the private Codecov token stored as a secret,
to work around rate-limiting issues like https://github.com/codecov/codecov-action/issues/557
2024-04-16 18:37:59 +02:00
renovate[bot] 1149a247dd Update Rust crate chrono to 0.4.38 2024-04-16 16:20:52 +00:00
Olivier Tilloy 1b311c6673 Merge pull request #33 from TanmayPatil105/context-diff-modification-time
Display modification times of input files in context and unified diff
2024-04-16 18:20:00 +02:00
Tanmay Patil aedd0684d1 Replace only the first two occurences of timestamp regex 2024-04-16 10:41:38 +05:30
Tanmay Patil 54c02bdf0b Use NamedTempFile instead of manually creating files 2024-04-16 10:17:09 +05:30
Tanmay Patil ba7cb0aef9 Do not create dummy files
Since we now returning SystemTime::now() for invalid file input,
there is no need to crate dummy files
2024-04-14 22:56:37 +05:30
Tanmay Patil 33783d094e Improve tests 2024-04-14 17:16:53 +05:30
Tanmay Patil 900e1c3a68 Tests: Replace modification time in diff with "TIMESTAMP" placeholder 2024-04-14 13:43:30 +05:30
Tanmay Patil 0a77fe12b9 Add tests for get_modification_time function 2024-04-13 21:31:13 +05:30
Tanmay Patil 86bd05c739 Merge branch 'context-diff-modification-time' of github.com:TanmayPatil105/diffutils into context-diff-modification-time 2024-04-10 22:31:09 +05:30
Tanmay Patil 00e18a6b0c Define assert_diff_eq macro for context&unified diff comparison 2024-04-10 22:20:48 +05:30
Tanmay f6eb0835b0 Merge branch 'main' into context-diff-modification-time 2024-04-10 22:13:18 +05:30
Sylvestre Ledru be66ff3299 Merge pull request #47 from oSoMoN/handle-stdin-filename
Handle the rewrite of "-" to "/dev/stdin" in main to leave the filenames unchanged (fixes #46)
2024-04-09 09:53:53 +02:00
Olivier Tilloy e1c319f96b Add an integration test for reading from "/dev/stdin" on unix-like systems 2024-04-08 22:36:14 +02:00
Olivier Tilloy 84ad116845 Use io::stdin() to read from standard input in a portable manner 2024-04-08 20:21:24 +02:00
Olivier Tilloy 6dc34fed44 Handle the rewrite of "-" to "/dev/stdin" in main to leave the filenames unchanged (fixes #46) 2024-04-08 20:21:24 +02:00
Sylvestre Ledru 9507ca28d7 Merge pull request #51 from oSoMoN/unit-tests-for-conflicting-output-style
Unit test to verify that conflicting output styles result in an error
2024-04-06 08:47:53 +02:00
Olivier Tilloy c325291696 Unit test to verify that conflicting output styles result in an error 2024-04-05 23:22:26 +02:00
Olivier Tilloy c08e0b6e1f Merge pull request #25 from uutils/renovate/tempfile-3.x
chore(deps): update rust crate tempfile to 3.10.1
2024-04-04 22:50:50 +02:00
Tanmay Patil 72da7fca40 Show current time if fs::metadata errors 2024-04-04 20:01:11 +05:30
Tanmay 61fb0657c1 Merge branch 'main' into context-diff-modification-time 2024-04-04 19:56:13 +05:30
Sylvestre Ledru 096aa1dad9 Merge pull request #50 from cakebaker/disable_tests_using_ed_on_windows
Disable tests on Windows that use `ed`
2024-04-04 08:48:41 +02:00
Daniel Hofstetter 2d9e625a5b Disable tests on Windows that use ed 2024-04-04 08:30:54 +02:00
Daniel Hofstetter d863fe443a Merge pull request #48 from uutils/clip
Run clippy pedantic fixes
2024-04-04 07:45:18 +02:00
renovate[bot] 6be94d8683 chore(deps): update rust crate tempfile to 3.10.1 2024-04-03 22:31:44 +00:00
Sylvestre Ledru 44ef772e4a release: version 0.4.0 2024-04-04 00:30:46 +02:00
Sylvestre Ledru bbfca84e17 chore: wow shiny new cargo-dist CI 2024-04-04 00:29:50 +02:00
Sylvestre Ledru 281098d751 Run clippy pedantic fixes 2024-04-03 23:25:48 +02:00
Tanmay Patil a3a372ff36 Display modification times of input files in unified diff 2024-04-04 00:13:41 +05:30
Tanmay Patil 5b814f8530 Fix tests 2024-04-03 10:50:52 +05:30
Sylvestre Ledru 34a5cc7340 Merge pull request #45 from oSoMoN/stdin-tests
Unit and integration tests for reading data from standard input
2024-04-03 00:02:21 +02:00
Olivier Tilloy 6a152cdc7f Add an integration test for reading data from stdin 2024-04-02 22:34:42 +02:00
Olivier Tilloy b8fada8faa Add unit tests for missing arguments 2024-04-02 21:10:41 +02:00
Olivier Tilloy a213272d0c Add tests for when '-' is used to signify to use standard input 2024-04-02 20:41:01 +02:00
Sylvestre Ledru 25e4a17421 Merge pull request #44 from oSoMoN/fix-codecov-badge
Fix the link to the Codecov badge
2024-04-02 18:34:51 +02:00
Olivier Tilloy 589039ab4c Fix the link to the Codecov badge 2024-04-02 18:24:09 +02:00
Sylvestre Ledru f83fccb542 Merge pull request #41 from uutils/sylvestre-patch-1
Disable the fail fast (closes: #40)
2024-04-02 07:33:43 +02:00
Sylvestre Ledru 76c4714f78 Disable the fail fast (closes: #40) 2024-04-01 23:15:23 +02:00
Tanmay Patil 88a7568b52 Merge branch 'main' into context-diff-modification-time 2024-04-01 13:05:37 +05:30
Sylvestre Ledru b135b6f218 Merge pull request #37 from oSoMoN/diff-arguments
Pass a Params reference to the various diff() functions, instead of a long list of arguments
2024-04-01 00:46:37 +02:00
Sylvestre Ledru 314e3a7320 Merge pull request #38 from oSoMoN/readme-grammar
README: minor grammar correction
2024-04-01 00:44:36 +02:00
Olivier Tilloy 6a73657b3a README: minor grammar correction 2024-04-01 00:26:30 +02:00
Olivier Tilloy e9f0630aaf Update fuzzers 2024-03-31 23:39:43 +02:00
Olivier Tilloy e6a0ba28c5 Pass a Params reference to the various diff() functions, instead of a long list of arguments 2024-03-31 21:27:28 +02:00
Tanmay Patil 80c9944bf7 Create foo/bar in target/context-diff 2024-03-31 22:57:51 +05:30
Sylvestre Ledru 043c5f9493 Merge branch 'main' into context-diff-modification-time 2024-03-31 16:17:23 +02:00
Tanmay Patil 9ff8f89626 Fix tests 2024-03-31 16:14:44 +05:30
Sylvestre Ledru 0ab824abda Merge pull request #36 from oSoMoN/default-params
Implement the Default trait for Params
2024-03-31 09:26:02 +02:00
Olivier Tilloy f60fefaf6e Implement the Default trait for Params 2024-03-30 23:50:58 +01:00
Sylvestre Ledru 14e77548fd Merge pull request #28 from oSoMoN/implement-expand-tabs-option
Implement `-t`/`--expand-tabs` option (fixes #26) and `--tabsize=NUM` option (fixes #27)
2024-03-30 15:28:53 +01:00
Olivier Tilloy f2fd2127ed Politely ask clippy to not complain about too many arguments 2024-03-28 18:10:31 +01:00
Olivier Tilloy cfc68d58bc Fix fuzzers' invokations 2024-03-28 18:10:31 +01:00
Olivier Tilloy e0283083f2 Implement --tabsize option 2024-03-28 18:10:29 +01:00
Olivier Tilloy 8d65c2badd Implement -t/--expand-tabs option 2024-03-28 18:09:03 +01:00
Tanmay Patil 42eb15b87a Display modification times of input files in context diff
Fixes #31
2024-03-27 22:46:23 +05:30
Sylvestre Ledru a304ac0a68 Merge pull request #30 from cakebaker/fix_clippy_warnings
clippy: fix warnings from useless_vec lint
2024-03-24 19:28:03 +01:00
Daniel Hofstetter f916f1ce86 clippy: fix warnings from useless_vec lint 2024-03-24 14:05:44 +01:00
Olivier Tilloy 4ed7ea1553 Implement -q/--brief option (fixes #19) (#20)
* Implement -q/--brief option

* Optimization: stop analyzing the files as soon as there are any differences

* Unit tests for the stop_early parameter

* Simplify checks
2024-03-19 11:45:06 +01:00
hanbings 62e10c6d6c ci: fuzzers in the Github Actions CI. (#29)
* Add fuzzing CI.

* fuzz dependency change: diffutils -> diffutilslib

* fix fuzz build.
2024-03-18 21:50:33 +01:00
Olivier Tilloy c68d386170 Implement -s/--report-identical-files option (fixes #23) 2024-03-01 14:45:41 +01:00
Olivier Tilloy a89f30afa0 Ed diff: compact ranges of lines where possible (fixes #21) 2024-02-24 11:55:54 +01:00
Olivier Tilloy 0a67bf9fb8 Add integration tests that check the exit codes and stdout/stderr 2024-02-22 17:26:24 +01:00
Olivier Tilloy 1241db4806 Match GNU diff's implementation for exit codes (fixes #17) 2024-02-22 17:26:24 +01:00
renovate[bot] 3bc8668f78 fix(deps): update rust crate libfuzzer-sys to 0.4 (#5)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-17 18:19:02 +01:00
Sylvestre Ledru c90eee442f Add the example section 2024-02-17 17:07:31 +01:00
Sylvestre Ledru 6c29f02527 improve the readme and example 2024-02-17 17:06:14 +01:00
Olivier Tilloy 790ef1e633 Run the GNU test suite in CI (fixes #8) (#13) 2024-02-17 15:27:52 +01:00
Sylvestre Ledru 4c1a752f11 Merge pull request #12 from oSoMoN/trivial-typo-error-message
Fix trivial typo in error message
2024-02-09 21:21:43 +01:00
Olivier Tilloy 54a5407bec Fix trivial typo in error message 2024-02-09 17:51:50 +01:00
Sylvestre Ledru 02632e915c Merge pull request #11 from oSoMoN/compact-range-of-lines
Normal diff: compact ranges of lines where possible (fixes #10)
2024-02-09 13:57:51 +01:00
Olivier Tilloy 3f9556aa05 Add comments to ease readability and maintainability 2024-02-09 10:28:39 +01:00
Olivier Tilloy a94c6a60cf Normal diff: compact ranges of lines where possible (fixes #10) 2024-02-09 10:28:39 +01:00
Sylvestre Ledru c28973c019 coverage: remove the fail fast to continue for linux even if windows fails 2024-02-09 10:28:27 +01:00
Sylvestre Ledru a660f7440c Merge pull request #1 from uutils/renovate/configure
Configure Renovate
2024-01-27 09:54:27 +01:00
Sylvestre Ledru 6a69a39852 add badges 2024-01-27 09:51:04 +01:00
renovate[bot] b55cbf2ca2 Add renovate.json 2024-01-22 18:51:27 +01:00
25 changed files with 2923 additions and 305 deletions
+37 -4
View File
@@ -10,6 +10,7 @@ jobs:
name: cargo check
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macOS-latest, windows-latest]
steps:
@@ -21,11 +22,19 @@ jobs:
name: cargo test
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macOS-latest, windows-latest]
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: install GNU patch on MacOS
if: runner.os == 'macOS'
run: brew install gpatch
- name: set up PATH on Windows
# Needed to use GNU's patch.exe instead of Strawberry Perl patch
if: runner.os == 'Windows'
run: echo "C:\Program Files\Git\usr\bin" >> $env:GITHUB_PATH
- run: cargo test
fmt:
@@ -41,6 +50,7 @@ jobs:
name: cargo clippy -- -D warnings
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macOS-latest, windows-latest]
steps:
@@ -49,11 +59,28 @@ jobs:
- run: rustup component add clippy
- run: cargo clippy -- -D warnings
gnu-testsuite:
name: GNU test suite
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- run: cargo build --release
# do not fail, the report is merely informative (at least until all tests pass reliably)
- run: ./tests/run-upstream-testsuite.sh release || true
env:
TERM: xterm
- uses: actions/upload-artifact@v4
with:
name: test-results.json
path: tests/test-results.json
- run: ./tests/print-test-results.sh tests/test-results.json
coverage:
name: Code Coverage
runs-on: ${{ matrix.job.os }}
strategy:
fail-fast: true
fail-fast: false
matrix:
job:
- { os: ubuntu-latest , features: unix }
@@ -83,6 +110,13 @@ jobs:
- name: rust toolchain ~ install
uses: dtolnay/rust-toolchain@nightly
- name: install GNU patch on MacOS
if: runner.os == 'macOS'
run: brew install gpatch
- name: set up PATH on Windows
# Needed to use GNU's patch.exe instead of Strawberry Perl patch
if: runner.os == 'Windows'
run: echo "C:\Program Files\Git\usr\bin" >> $env:GITHUB_PATH
- name: Test
run: cargo test ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} --no-fail-fast
env:
@@ -122,10 +156,9 @@ jobs:
grcov . --output-type lcov --output-path "${COVERAGE_REPORT_FILE}" --branch --ignore build.rs --ignore "vendor/*" --ignore "/*" --ignore "[a-zA-Z]:/*" --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()"
echo "report=${COVERAGE_REPORT_FILE}" >> $GITHUB_OUTPUT
- name: Upload coverage results (to Codecov.io)
uses: codecov/codecov-action@v3
# if: steps.vars.outputs.HAS_CODECOV_TOKEN
uses: codecov/codecov-action@v4
with:
# token: ${{ secrets.CODECOV_TOKEN }}
token: ${{ secrets.CODECOV_TOKEN }}
file: ${{ steps.coverage.outputs.report }}
## flags: IntegrationTests, UnitTests, ${{ steps.vars.outputs.CODECOV_FLAGS }}
flags: ${{ steps.vars.outputs.CODECOV_FLAGS }}
+72
View File
@@ -0,0 +1,72 @@
name: Fuzzing
# spell-checker:ignore fuzzer
on:
pull_request:
push:
branches:
- main
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
jobs:
fuzz-build:
name: Build the fuzzers
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@nightly
- name: Install `cargo-fuzz`
run: cargo install cargo-fuzz
- uses: Swatinem/rust-cache@v2
with:
shared-key: "cargo-fuzz-cache-key"
cache-directories: "fuzz/target"
- name: Run `cargo-fuzz build`
run: cargo +nightly fuzz build
fuzz-run:
needs: fuzz-build
name: Run the fuzzers
runs-on: ubuntu-latest
timeout-minutes: 5
env:
RUN_FOR: 60
strategy:
matrix:
test-target:
- { name: fuzz_ed, should_pass: true }
- { name: fuzz_normal, should_pass: true }
- { name: fuzz_patch, should_pass: true }
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@nightly
- name: Install `cargo-fuzz`
run: cargo install cargo-fuzz
- uses: Swatinem/rust-cache@v2
with:
shared-key: "cargo-fuzz-cache-key"
cache-directories: "fuzz/target"
- name: Restore Cached Corpus
uses: actions/cache/restore@v4
with:
key: corpus-cache-${{ matrix.test-target.name }}
path: |
fuzz/corpus/${{ matrix.test-target.name }}
- name: Run ${{ matrix.test-target.name }} for XX seconds
shell: bash
continue-on-error: ${{ !matrix.test-target.name.should_pass }}
run: |
cargo +nightly fuzz run ${{ matrix.test-target.name }} -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0
- name: Save Corpus Cache
uses: actions/cache/save@v4
with:
key: corpus-cache-${{ matrix.test-target.name }}
path: |
fuzz/corpus/${{ matrix.test-target.name }}
+271
View File
@@ -0,0 +1,271 @@
# Copyright 2022-2024, axodotdev
# SPDX-License-Identifier: MIT or Apache-2.0
#
# CI that:
#
# * checks for a Git Tag that looks like a release
# * builds artifacts with cargo-dist (archives, installers, hashes)
# * uploads those artifacts to temporary workflow zip
# * on success, uploads the artifacts to a GitHub Release
#
# Note that the GitHub Release will be created with a generated
# title/body based on your changelogs.
name: Release
permissions:
contents: write
# This task will run whenever you push a git tag that looks like a version
# like "1.0.0", "v0.1.0-prerelease.1", "my-app/0.1.0", "releases/v1.0.0", etc.
# Various formats will be parsed into a VERSION and an optional PACKAGE_NAME, where
# PACKAGE_NAME must be the name of a Cargo package in your workspace, and VERSION
# must be a Cargo-style SemVer Version (must have at least major.minor.patch).
#
# If PACKAGE_NAME is specified, then the announcement will be for that
# package (erroring out if it doesn't have the given version or isn't cargo-dist-able).
#
# If PACKAGE_NAME isn't specified, then the announcement will be for all
# (cargo-dist-able) packages in the workspace with that version (this mode is
# intended for workspaces with only one dist-able package, or with all dist-able
# packages versioned/released in lockstep).
#
# If you push multiple tags at once, separate instances of this workflow will
# spin up, creating an independent announcement for each one. However, GitHub
# will hard limit this to 3 tags per commit, as it will assume more tags is a
# mistake.
#
# If there's a prerelease-style suffix to the version, then the release(s)
# will be marked as a prerelease.
on:
push:
tags:
- '**[0-9]+.[0-9]+.[0-9]+*'
pull_request:
jobs:
# Run 'cargo dist plan' (or host) to determine what tasks we need to do
plan:
runs-on: ubuntu-latest
outputs:
val: ${{ steps.plan.outputs.manifest }}
tag: ${{ !github.event.pull_request && github.ref_name || '' }}
tag-flag: ${{ !github.event.pull_request && format('--tag={0}', github.ref_name) || '' }}
publishing: ${{ !github.event.pull_request }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Install cargo-dist
# we specify bash to get pipefail; it guards against the `curl` command
# failing. otherwise `sh` won't catch that `curl` returned non-0
shell: bash
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.13.3/cargo-dist-installer.sh | sh"
# sure would be cool if github gave us proper conditionals...
# so here's a doubly-nested ternary-via-truthiness to try to provide the best possible
# functionality based on whether this is a pull_request, and whether it's from a fork.
# (PRs run on the *source* but secrets are usually on the *target* -- that's *good*
# but also really annoying to build CI around when it needs secrets to work right.)
- id: plan
run: |
cargo dist ${{ (!github.event.pull_request && format('host --steps=create --tag={0}', github.ref_name)) || 'plan' }} --output-format=json > plan-dist-manifest.json
echo "cargo dist ran successfully"
cat plan-dist-manifest.json
echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT"
- name: "Upload dist-manifest.json"
uses: actions/upload-artifact@v4
with:
name: artifacts-plan-dist-manifest
path: plan-dist-manifest.json
# Build and packages all the platform-specific things
build-local-artifacts:
name: build-local-artifacts (${{ join(matrix.targets, ', ') }})
# Let the initial task tell us to not run (currently very blunt)
needs:
- plan
if: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix.include != null && (needs.plan.outputs.publishing == 'true' || fromJson(needs.plan.outputs.val).ci.github.pr_run_mode == 'upload') }}
strategy:
fail-fast: false
# Target platforms/runners are computed by cargo-dist in create-release.
# Each member of the matrix has the following arguments:
#
# - runner: the github runner
# - dist-args: cli flags to pass to cargo dist
# - install-dist: expression to run to install cargo-dist on the runner
#
# Typically there will be:
# - 1 "global" task that builds universal installers
# - N "local" tasks that build each platform's binaries and platform-specific installers
matrix: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix }}
runs-on: ${{ matrix.runner }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BUILD_MANIFEST_NAME: target/distrib/${{ join(matrix.targets, '-') }}-dist-manifest.json
steps:
- name: enable windows longpaths
run: |
git config --global core.longpaths true
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: swatinem/rust-cache@v2
with:
key: ${{ join(matrix.targets, '-') }}
- name: Install cargo-dist
run: ${{ matrix.install_dist }}
# Get the dist-manifest
- name: Fetch local artifacts
uses: actions/download-artifact@v4
with:
pattern: artifacts-*
path: target/distrib/
merge-multiple: true
- name: Install dependencies
run: |
${{ matrix.packages_install }}
- name: Build artifacts
run: |
# Actually do builds and make zips and whatnot
cargo dist build ${{ needs.plan.outputs.tag-flag }} --print=linkage --output-format=json ${{ matrix.dist_args }} > dist-manifest.json
echo "cargo dist ran successfully"
- id: cargo-dist
name: Post-build
# We force bash here just because github makes it really hard to get values up
# to "real" actions without writing to env-vars, and writing to env-vars has
# inconsistent syntax between shell and powershell.
shell: bash
run: |
# Parse out what we just built and upload it to scratch storage
echo "paths<<EOF" >> "$GITHUB_OUTPUT"
jq --raw-output ".upload_files[]" dist-manifest.json >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"
cp dist-manifest.json "$BUILD_MANIFEST_NAME"
- name: "Upload artifacts"
uses: actions/upload-artifact@v4
with:
name: artifacts-build-local-${{ join(matrix.targets, '_') }}
path: |
${{ steps.cargo-dist.outputs.paths }}
${{ env.BUILD_MANIFEST_NAME }}
# Build and package all the platform-agnostic(ish) things
build-global-artifacts:
needs:
- plan
- build-local-artifacts
runs-on: "ubuntu-20.04"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Install cargo-dist
shell: bash
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.13.3/cargo-dist-installer.sh | sh"
# Get all the local artifacts for the global tasks to use (for e.g. checksums)
- name: Fetch local artifacts
uses: actions/download-artifact@v4
with:
pattern: artifacts-*
path: target/distrib/
merge-multiple: true
- id: cargo-dist
shell: bash
run: |
cargo dist build ${{ needs.plan.outputs.tag-flag }} --output-format=json "--artifacts=global" > dist-manifest.json
echo "cargo dist ran successfully"
# Parse out what we just built and upload it to scratch storage
echo "paths<<EOF" >> "$GITHUB_OUTPUT"
jq --raw-output ".upload_files[]" dist-manifest.json >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"
cp dist-manifest.json "$BUILD_MANIFEST_NAME"
- name: "Upload artifacts"
uses: actions/upload-artifact@v4
with:
name: artifacts-build-global
path: |
${{ steps.cargo-dist.outputs.paths }}
${{ env.BUILD_MANIFEST_NAME }}
# Determines if we should publish/announce
host:
needs:
- plan
- build-local-artifacts
- build-global-artifacts
# Only run if we're "publishing", and only if local and global didn't fail (skipped is fine)
if: ${{ always() && needs.plan.outputs.publishing == 'true' && (needs.build-global-artifacts.result == 'skipped' || needs.build-global-artifacts.result == 'success') && (needs.build-local-artifacts.result == 'skipped' || needs.build-local-artifacts.result == 'success') }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
runs-on: "ubuntu-20.04"
outputs:
val: ${{ steps.host.outputs.manifest }}
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Install cargo-dist
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.13.3/cargo-dist-installer.sh | sh"
# Fetch artifacts from scratch-storage
- name: Fetch artifacts
uses: actions/download-artifact@v4
with:
pattern: artifacts-*
path: target/distrib/
merge-multiple: true
# This is a harmless no-op for GitHub Releases, hosting for that happens in "announce"
- id: host
shell: bash
run: |
cargo dist host ${{ needs.plan.outputs.tag-flag }} --steps=upload --steps=release --output-format=json > dist-manifest.json
echo "artifacts uploaded and released successfully"
cat dist-manifest.json
echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT"
- name: "Upload dist-manifest.json"
uses: actions/upload-artifact@v4
with:
# Overwrite the previous copy
name: artifacts-dist-manifest
path: dist-manifest.json
# Create a GitHub Release while uploading all files to it
announce:
needs:
- plan
- host
# use "always() && ..." to allow us to wait for all publish jobs while
# still allowing individual publish jobs to skip themselves (for prereleases).
# "host" however must run to completion, no skipping allowed!
if: ${{ always() && needs.host.result == 'success' }}
runs-on: "ubuntu-20.04"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: "Download GitHub Artifacts"
uses: actions/download-artifact@v4
with:
pattern: artifacts-*
path: artifacts
merge-multiple: true
- name: Cleanup
run: |
# Remove the granular manifests
rm -f artifacts/*-dist-manifest.json
- name: Create GitHub Release
uses: ncipollo/release-action@v1
with:
tag: ${{ needs.plan.outputs.tag }}
name: ${{ fromJson(needs.host.outputs.val).announcement_title }}
body: ${{ fromJson(needs.host.outputs.val).announcement_github_body }}
prerelease: ${{ fromJson(needs.host.outputs.val).announcement_is_prerelease }}
artifacts: "artifacts/*"
Generated
+557 -1
View File
File diff suppressed because it is too large Load Diff
+28 -3
View File
@@ -1,6 +1,6 @@
[package]
name = "diffutils"
version = "0.3.0"
version = "0.4.1"
edition = "2021"
description = "A CLI app for generating diff files"
license = "MIT OR Apache-2.0"
@@ -15,7 +15,32 @@ name = "diffutils"
path = "src/main.rs"
[dependencies]
diff = "0.1.10"
chrono = "0.4.38"
diff = "0.1.13"
regex = "1.10.4"
same-file = "1.0.6"
unicode-width = "0.1.12"
[dev-dependencies]
pretty_assertions = "1"
pretty_assertions = "1.4.0"
assert_cmd = "2.0.14"
predicates = "3.1.0"
tempfile = "3.10.1"
# The profile that 'cargo dist' will build with
[profile.dist]
inherits = "release"
lto = "thin"
# Config for 'cargo dist'
[workspace.metadata.dist]
# The preferred cargo-dist version to use in CI (Cargo.toml SemVer syntax)
cargo-dist-version = "0.13.3"
# CI backends to support
ci = ["github"]
# The installers to generate for each app
installers = []
# Target platforms to build apps for (Rust target-triple syntax)
targets = ["aarch64-apple-darwin", "aarch64-unknown-linux-gnu", "aarch64-unknown-linux-musl", "x86_64-apple-darwin", "x86_64-unknown-linux-gnu", "x86_64-unknown-linux-musl", "x86_64-pc-windows-msvc"]
# Publish jobs to run in CI
pr-run-mode = "plan"
+3
View File
@@ -1,3 +1,6 @@
Copyright (c) Michael Howell
Copyright (c) uutils developers
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
+3
View File
@@ -1,3 +1,6 @@
Copyright (c) Michael Howell
Copyright (c) uutils developers
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
+49 -51
View File
@@ -1,58 +1,56 @@
The goal of this package is to be a dropped in replacement for the [diffutils commands](https://www.gnu.org/software/diffutils/) in Rust.
[![Crates.io](https://img.shields.io/crates/v/diffutils.svg)](https://crates.io/crates/diffutils)
[![Discord](https://img.shields.io/badge/discord-join-7289DA.svg?logo=discord&longCache=true&style=flat)](https://discord.gg/wQVJbvJ)
[![License](http://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/uutils/diffutils/blob/main/LICENSE)
[![dependency status](https://deps.rs/repo/github/uutils/diffutils/status.svg)](https://deps.rs/repo/github/uutils/diffutils)
[![CodeCov](https://codecov.io/gh/uutils/diffutils/branch/main/graph/badge.svg)](https://codecov.io/gh/uutils/diffutils)
The goal of this package is to be a drop-in replacement for the [diffutils commands](https://www.gnu.org/software/diffutils/) in Rust.
Based on the incomplete diff generator in https://github.com/rust-lang/rust/blob/master/src/tools/compiletest/src/runtest.rs, and made to be compatible with GNU's diff and patch tools.
## Installation
Ensure you have Rust installed on your system. You can install Rust through [rustup](https://rustup.rs/).
Clone the repository and build the project using Cargo:
```bash
git clone https://github.com/uutils/diffutils.git
cd diffutils
cargo build --release
```
~/diffutils$ cargo run -- diff -u3 Cargo.lock Cargo.toml
## Example
```bash
cat <<EOF >fruits_old.txt
Apple
Banana
Cherry
EOF
cat <<EOF >fruits_new.txt
Apple
Fig
Cherry
EOF
$ cargo run -- -u fruits_old.txt fruits_new.txt
Finished dev [unoptimized + debuginfo] target(s) in 0.00s
Running `target/debug/diff -u3 Cargo.lock Cargo.toml`
--- Cargo.lock
+++ Cargo.toml
@@ -1,39 +1,7 @@
-# This file is automatically @generated by Cargo.
-# It is not intended for manual editing.
-version = 3
-
-[[package]]
-name = "context-diff"
-version = "0.1.0"
-dependencies = [
- "diff 0.1.12",
-]
-
-[[package]]
-name = "diff"
-version = "0.1.0"
-dependencies = [
- "context-diff",
- "normal-diff",
- "unified-diff",
-]
-
-[[package]]
-name = "diff"
-version = "0.1.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499"
-
-[[package]]
-name = "normal-diff"
-version = "0.1.0"
-dependencies = [
- "diff 0.1.12",
-]
-
-[[package]]
-name = "unified-diff"
-version = "0.3.0"
-dependencies = [
- "diff 0.1.12",
+[workspace]
+members = [
+ "lib/unified-diff",
+ "lib/context-diff",
+ "lib/normal-diff",
+ "bin/diff",
]
Running `target/debug/diffutils -u fruits_old.txt fruits_new.txt`
--- fruits_old.txt
+++ fruits_new.txt
@@ -1,3 +1,3 @@
Apple
-Banana
+Fig
Cherry
```
## License
diffutils is licensed under the MIT and Apache Licenses - see the `LICENSE-MIT` or `LICENSE-APACHE` files for details
+1 -1
View File
@@ -9,7 +9,7 @@ edition = "2018"
cargo-fuzz = true
[dependencies]
libfuzzer-sys = "0.3"
libfuzzer-sys = "0.4.7"
diffutils = { path = "../" }
# Prevent this from interfering with workspaces
+10 -2
View File
@@ -1,11 +1,19 @@
#![no_main]
#[macro_use]
extern crate libfuzzer_sys;
use diffutils::{ed_diff, normal_diff, unified_diff};
use diffutilslib::ed_diff;
use diffutilslib::ed_diff::DiffError;
use diffutilslib::params::Params;
use std::fs::{self, File};
use std::io::Write;
use std::process::Command;
fn diff_w(expected: &[u8], actual: &[u8], filename: &str) -> Result<Vec<u8>, DiffError> {
let mut output = ed_diff::diff(expected, actual, &Params::default())?;
writeln!(&mut output, "w {filename}").unwrap();
Ok(output)
}
fuzz_target!(|x: (Vec<u8>, Vec<u8>)| {
let (mut from, mut to) = x;
from.push(b'\n');
@@ -30,7 +38,7 @@ fuzz_target!(|x: (Vec<u8>, Vec<u8>)| {
} else {
return;
}
let diff = ed_diff::diff_w(&from, &to, "target/fuzz.file").unwrap();
let diff = diff_w(&from, &to, "target/fuzz.file").unwrap();
File::create("target/fuzz.file.original")
.unwrap()
.write_all(&from)
+3 -2
View File
@@ -1,7 +1,8 @@
#![no_main]
#[macro_use]
extern crate libfuzzer_sys;
use diffutils::{normal_diff, unified_diff};
use diffutilslib::normal_diff;
use diffutilslib::params::Params;
use std::fs::{self, File};
use std::io::Write;
@@ -21,7 +22,7 @@ fuzz_target!(|x: (Vec<u8>, Vec<u8>)| {
} else {
return
}*/
let diff = normal_diff::diff(&from, &to);
let diff = normal_diff::diff(&from, &to, &Params::default());
File::create("target/fuzz.file.original")
.unwrap()
.write_all(&from)
+8 -4
View File
@@ -1,7 +1,8 @@
#![no_main]
#[macro_use]
extern crate libfuzzer_sys;
use diffutils::{normal_diff, unified_diff};
use diffutilslib::params::Params;
use diffutilslib::unified_diff;
use std::fs::{self, File};
use std::io::Write;
use std::process::Command;
@@ -22,10 +23,13 @@ fuzz_target!(|x: (Vec<u8>, Vec<u8>, u8)| {
}*/
let diff = unified_diff::diff(
&from,
"a/fuzz.file",
&to,
"target/fuzz.file",
context as usize,
&Params {
from: "a/fuzz.file".into(),
to: "target/fuzz.file".into(),
context_count: context as usize,
..Default::default()
}
);
File::create("target/fuzz.file.original")
.unwrap()
+6
View File
@@ -0,0 +1,6 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended"
]
}
+160 -29
View File
@@ -6,6 +6,10 @@
use std::collections::VecDeque;
use std::io::Write;
use crate::params::Params;
use crate::utils::do_write_line;
use crate::utils::get_modification_time;
#[derive(Debug, PartialEq)]
pub enum DiffLine {
Context(Vec<u8>),
@@ -41,7 +45,12 @@ impl Mismatch {
}
// Produces a diff between the expected output and actual output.
fn make_diff(expected: &[u8], actual: &[u8], context_size: usize) -> Vec<Mismatch> {
fn make_diff(
expected: &[u8],
actual: &[u8],
context_size: usize,
stop_early: bool,
) -> Vec<Mismatch> {
let mut line_number_expected = 1;
let mut line_number_actual = 1;
let mut context_queue: VecDeque<&[u8]> = VecDeque::with_capacity(context_size);
@@ -191,6 +200,10 @@ fn make_diff(expected: &[u8], actual: &[u8], context_size: usize) -> Vec<Mismatc
line_number_actual += 1;
}
}
if stop_early && !results.is_empty() {
// Optimization: stop analyzing the files as soon as there are any differences
return results;
}
}
results.push(mismatch);
@@ -254,18 +267,24 @@ fn make_diff(expected: &[u8], actual: &[u8], context_size: usize) -> Vec<Mismatc
}
#[must_use]
pub fn diff(
expected: &[u8],
expected_filename: &str,
actual: &[u8],
actual_filename: &str,
context_size: usize,
) -> Vec<u8> {
let mut output = format!("*** {expected_filename}\t\n--- {actual_filename}\t\n").into_bytes();
let diff_results = make_diff(expected, actual, context_size);
pub fn diff(expected: &[u8], actual: &[u8], params: &Params) -> Vec<u8> {
let from_modified_time = get_modification_time(&params.from.to_string_lossy());
let to_modified_time = get_modification_time(&params.to.to_string_lossy());
let mut output = format!(
"*** {0}\t{1}\n--- {2}\t{3}\n",
params.from.to_string_lossy(),
from_modified_time,
params.to.to_string_lossy(),
to_modified_time
)
.into_bytes();
let diff_results = make_diff(expected, actual, params.context_count, params.brief);
if diff_results.is_empty() {
return Vec::new();
};
}
if params.brief {
return output;
}
for result in diff_results {
let mut line_number_expected = result.line_number_expected;
let mut line_number_actual = result.line_number_actual;
@@ -301,17 +320,20 @@ pub fn diff(
match line {
DiffLine::Context(e) => {
write!(output, " ").expect("write to Vec is infallible");
output.write_all(&e).expect("write to Vec is infallible");
do_write_line(&mut output, &e, params.expand_tabs, params.tabsize)
.expect("write to Vec is infallible");
writeln!(output).unwrap();
}
DiffLine::Change(e) => {
write!(output, "! ").expect("write to Vec is infallible");
output.write_all(&e).expect("write to Vec is infallible");
do_write_line(&mut output, &e, params.expand_tabs, params.tabsize)
.expect("write to Vec is infallible");
writeln!(output).unwrap();
}
DiffLine::Add(e) => {
write!(output, "- ").expect("write to Vec is infallible");
output.write_all(&e).expect("write to Vec is infallible");
do_write_line(&mut output, &e, params.expand_tabs, params.tabsize)
.expect("write to Vec is infallible");
writeln!(output).unwrap();
}
}
@@ -328,17 +350,20 @@ pub fn diff(
match line {
DiffLine::Context(e) => {
write!(output, " ").expect("write to Vec is infallible");
output.write_all(&e).expect("write to Vec is infallible");
do_write_line(&mut output, &e, params.expand_tabs, params.tabsize)
.expect("write to Vec is infallible");
writeln!(output).unwrap();
}
DiffLine::Change(e) => {
write!(output, "! ").expect("write to Vec is infallible");
output.write_all(&e).expect("write to Vec is infallible");
do_write_line(&mut output, &e, params.expand_tabs, params.tabsize)
.expect("write to Vec is infallible");
writeln!(output).unwrap();
}
DiffLine::Add(e) => {
write!(output, "+ ").expect("write to Vec is infallible");
output.write_all(&e).expect("write to Vec is infallible");
do_write_line(&mut output, &e, params.expand_tabs, params.tabsize)
.expect("write to Vec is infallible");
writeln!(output).unwrap();
}
}
@@ -404,8 +429,16 @@ mod tests {
}
// This test diff is intentionally reversed.
// We want it to turn the alef into bet.
let diff =
diff(&alef, "a/alef", &bet, &format!("{target}/alef"), 2);
let diff = diff(
&alef,
&bet,
&Params {
from: "a/alef".into(),
to: (&format!("{target}/alef")).into(),
context_count: 2,
..Default::default()
},
);
File::create(&format!("{target}/ab.diff"))
.unwrap()
.write_all(&diff)
@@ -422,7 +455,7 @@ mod tests {
.stdin(File::open(&format!("{target}/ab.diff")).unwrap())
.output()
.unwrap();
assert!(output.status.success(), "{:?}", output);
assert!(output.status.success(), "{output:?}");
//println!("{}", String::from_utf8_lossy(&output.stdout));
//println!("{}", String::from_utf8_lossy(&output.stderr));
let alef = fs::read(&format!("{target}/alef")).unwrap();
@@ -477,8 +510,16 @@ mod tests {
}
// This test diff is intentionally reversed.
// We want it to turn the alef into bet.
let diff =
diff(&alef, "a/alef_", &bet, &format!("{target}/alef_"), 2);
let diff = diff(
&alef,
&bet,
&Params {
from: "a/alef_".into(),
to: (&format!("{target}/alef_")).into(),
context_count: 2,
..Default::default()
},
);
File::create(&format!("{target}/ab_.diff"))
.unwrap()
.write_all(&diff)
@@ -495,7 +536,7 @@ mod tests {
.stdin(File::open(&format!("{target}/ab_.diff")).unwrap())
.output()
.unwrap();
assert!(output.status.success(), "{:?}", output);
assert!(output.status.success(), "{output:?}");
//println!("{}", String::from_utf8_lossy(&output.stdout));
//println!("{}", String::from_utf8_lossy(&output.stderr));
let alef = fs::read(&format!("{target}/alef_")).unwrap();
@@ -553,8 +594,16 @@ mod tests {
};
// This test diff is intentionally reversed.
// We want it to turn the alef into bet.
let diff =
diff(&alef, "a/alefx", &bet, &format!("{target}/alefx"), 2);
let diff = diff(
&alef,
&bet,
&Params {
from: "a/alefx".into(),
to: (&format!("{target}/alefx")).into(),
context_count: 2,
..Default::default()
},
);
File::create(&format!("{target}/abx.diff"))
.unwrap()
.write_all(&diff)
@@ -571,7 +620,7 @@ mod tests {
.stdin(File::open(&format!("{target}/abx.diff")).unwrap())
.output()
.unwrap();
assert!(output.status.success(), "{:?}", output);
assert!(output.status.success(), "{output:?}");
//println!("{}", String::from_utf8_lossy(&output.stdout));
//println!("{}", String::from_utf8_lossy(&output.stderr));
let alef = fs::read(&format!("{target}/alefx")).unwrap();
@@ -632,8 +681,16 @@ mod tests {
}
// This test diff is intentionally reversed.
// We want it to turn the alef into bet.
let diff =
diff(&alef, "a/alefr", &bet, &format!("{target}/alefr"), 2);
let diff = diff(
&alef,
&bet,
&Params {
from: "a/alefr".into(),
to: (&format!("{target}/alefr")).into(),
context_count: 2,
..Default::default()
},
);
File::create(&format!("{target}/abr.diff"))
.unwrap()
.write_all(&diff)
@@ -650,7 +707,7 @@ mod tests {
.stdin(File::open(&format!("{target}/abr.diff")).unwrap())
.output()
.unwrap();
assert!(output.status.success(), "{:?}", output);
assert!(output.status.success(), "{output:?}");
//println!("{}", String::from_utf8_lossy(&output.stdout));
//println!("{}", String::from_utf8_lossy(&output.stderr));
let alef = fs::read(&format!("{target}/alefr")).unwrap();
@@ -662,4 +719,78 @@ mod tests {
}
}
}
#[test]
fn test_stop_early() {
use crate::assert_diff_eq;
let from_filename = "foo";
let from = ["a", "b", "c", ""].join("\n");
let to_filename = "bar";
let to = ["a", "d", "c", ""].join("\n");
let diff_full = diff(
from.as_bytes(),
to.as_bytes(),
&Params {
from: from_filename.into(),
to: to_filename.into(),
..Default::default()
},
);
let expected_full = [
"*** foo\tTIMESTAMP",
"--- bar\tTIMESTAMP",
"***************",
"*** 1,3 ****",
" a",
"! b",
" c",
"--- 1,3 ----",
" a",
"! d",
" c",
"",
]
.join("\n");
assert_diff_eq!(diff_full, expected_full);
let diff_brief = diff(
from.as_bytes(),
to.as_bytes(),
&Params {
from: from_filename.into(),
to: to_filename.into(),
brief: true,
..Default::default()
},
);
let expected_brief = ["*** foo\tTIMESTAMP", "--- bar\tTIMESTAMP", ""].join("\n");
assert_diff_eq!(diff_brief, expected_brief);
let nodiff_full = diff(
from.as_bytes(),
from.as_bytes(),
&Params {
from: from_filename.into(),
to: to_filename.into(),
..Default::default()
},
);
assert!(nodiff_full.is_empty());
let nodiff_brief = diff(
from.as_bytes(),
from.as_bytes(),
&Params {
from: from_filename.into(),
to: to_filename.into(),
brief: true,
..Default::default()
},
);
assert!(nodiff_brief.is_empty());
}
}
+112 -46
View File
@@ -5,6 +5,9 @@
use std::io::Write;
use crate::params::Params;
use crate::utils::do_write_line;
#[derive(Debug, PartialEq)]
struct Mismatch {
pub line_number_expected: usize,
@@ -42,7 +45,7 @@ impl Mismatch {
}
// Produces a diff between the expected output and actual output.
fn make_diff(expected: &[u8], actual: &[u8]) -> Result<Vec<Mismatch>, DiffError> {
fn make_diff(expected: &[u8], actual: &[u8], stop_early: bool) -> Result<Vec<Mismatch>, DiffError> {
let mut line_number_expected = 1;
let mut line_number_actual = 1;
let mut results = Vec::new();
@@ -94,6 +97,10 @@ fn make_diff(expected: &[u8], actual: &[u8]) -> Result<Vec<Mismatch>, DiffError>
}
}
}
if stop_early && !results.is_empty() {
// Optimization: stop analyzing the files as soon as there are any differences
return Ok(results);
}
}
if !mismatch.actual.is_empty() || !mismatch.expected.is_empty() {
@@ -103,9 +110,13 @@ fn make_diff(expected: &[u8], actual: &[u8]) -> Result<Vec<Mismatch>, DiffError>
Ok(results)
}
pub fn diff(expected: &[u8], actual: &[u8]) -> Result<Vec<u8>, DiffError> {
pub fn diff(expected: &[u8], actual: &[u8], params: &Params) -> Result<Vec<u8>, DiffError> {
let mut output = Vec::new();
let diff_results = make_diff(expected, actual)?;
let diff_results = make_diff(expected, actual, params.brief)?;
if params.brief && !diff_results.is_empty() {
write!(&mut output, "\0").unwrap();
return Ok(output);
}
let mut lines_offset = 0;
for result in diff_results {
let line_number_expected: isize = result.line_number_expected as isize + lines_offset;
@@ -122,6 +133,7 @@ pub fn diff(expected: &[u8], actual: &[u8]) -> Result<Vec<u8>, DiffError> {
expected_count + line_number_expected - 1
)
.unwrap(),
(1, _) => writeln!(&mut output, "{line_number_expected}c").unwrap(),
_ => writeln!(
&mut output,
"{},{}c",
@@ -136,7 +148,7 @@ pub fn diff(expected: &[u8], actual: &[u8]) -> Result<Vec<u8>, DiffError> {
if actual == b"." {
writeln!(&mut output, "..\n.\ns/.//\na").unwrap();
} else {
output.write_all(actual).unwrap();
do_write_line(&mut output, actual, params.expand_tabs, params.tabsize).unwrap();
writeln!(&mut output).unwrap();
}
}
@@ -151,11 +163,20 @@ mod tests {
use super::*;
use pretty_assertions::assert_eq;
pub fn diff_w(expected: &[u8], actual: &[u8], filename: &str) -> Result<Vec<u8>, DiffError> {
let mut output = diff(expected, actual)?;
let mut output = diff(expected, actual, &Params::default())?;
writeln!(&mut output, "w {filename}").unwrap();
Ok(output)
}
#[test]
fn test_basic() {
let from = b"a\n";
let to = b"b\n";
let diff = diff(from, to, &Params::default()).unwrap();
let expected = ["1c", "b", ".", ""].join("\n");
assert_eq!(diff, expected.as_bytes());
}
#[test]
fn test_permutations() {
let target = "target/ed-diff/";
@@ -167,9 +188,8 @@ mod tests {
for &d in &[0, 1, 2] {
for &e in &[0, 1, 2] {
for &f in &[0, 1, 2] {
use std::fs::{self, File};
use std::fs::File;
use std::io::Write;
use std::process::Command;
let mut alef = Vec::new();
let mut bet = Vec::new();
alef.write_all(if a == 0 { b"a\n" } else { b"b\n" })
@@ -205,7 +225,7 @@ mod tests {
// This test diff is intentionally reversed.
// We want it to turn the alef into bet.
let diff = diff_w(&alef, &bet, &format!("{target}/alef")).unwrap();
File::create("target/ab.ed")
File::create(&format!("{target}/ab.ed"))
.unwrap()
.write_all(&diff)
.unwrap();
@@ -215,16 +235,20 @@ mod tests {
fb.write_all(&bet[..]).unwrap();
let _ = fa;
let _ = fb;
let output = Command::new("ed")
.arg(&format!("{target}/alef"))
.stdin(File::open("target/ab.ed").unwrap())
.output()
.unwrap();
assert!(output.status.success(), "{:?}", output);
//println!("{}", String::from_utf8_lossy(&output.stdout));
//println!("{}", String::from_utf8_lossy(&output.stderr));
let alef = fs::read(&format!("{target}/alef")).unwrap();
assert_eq!(alef, bet);
#[cfg(not(windows))] // there's no ed on windows
{
use std::process::Command;
let output = Command::new("ed")
.arg(&format!("{target}/alef"))
.stdin(File::open(&format!("{target}/ab.ed")).unwrap())
.output()
.unwrap();
assert!(output.status.success(), "{output:?}");
//println!("{}", String::from_utf8_lossy(&output.stdout));
//println!("{}", String::from_utf8_lossy(&output.stderr));
let alef = std::fs::read(&format!("{target}/alef")).unwrap();
assert_eq!(alef, bet);
}
}
}
}
@@ -244,9 +268,8 @@ mod tests {
for &d in &[0, 1, 2] {
for &e in &[0, 1, 2] {
for &f in &[0, 1, 2] {
use std::fs::{self, File};
use std::fs::File;
use std::io::Write;
use std::process::Command;
let mut alef = Vec::new();
let mut bet = Vec::new();
alef.write_all(if a == 0 { b"\n" } else { b"b\n" }).unwrap();
@@ -275,27 +298,31 @@ mod tests {
}
// This test diff is intentionally reversed.
// We want it to turn the alef into bet.
let diff = diff_w(&alef, &bet, "target/alef_").unwrap();
File::create("target/ab_.ed")
let diff = diff_w(&alef, &bet, &format!("{target}/alef_")).unwrap();
File::create(&format!("{target}/ab_.ed"))
.unwrap()
.write_all(&diff)
.unwrap();
let mut fa = File::create("target/alef_").unwrap();
let mut fa = File::create(&format!("{target}/alef_")).unwrap();
fa.write_all(&alef[..]).unwrap();
let mut fb = File::create(&format!("{target}/bet_")).unwrap();
fb.write_all(&bet[..]).unwrap();
let _ = fa;
let _ = fb;
let output = Command::new("ed")
.arg("target/alef_")
.stdin(File::open("target/ab_.ed").unwrap())
.output()
.unwrap();
assert!(output.status.success(), "{:?}", output);
//println!("{}", String::from_utf8_lossy(&output.stdout));
//println!("{}", String::from_utf8_lossy(&output.stderr));
let alef = fs::read("target/alef_").unwrap();
assert_eq!(alef, bet);
#[cfg(not(windows))] // there's no ed on windows
{
use std::process::Command;
let output = Command::new("ed")
.arg(&format!("{target}/alef_"))
.stdin(File::open(&format!("{target}/ab_.ed")).unwrap())
.output()
.unwrap();
assert!(output.status.success(), "{output:?}");
//println!("{}", String::from_utf8_lossy(&output.stdout));
//println!("{}", String::from_utf8_lossy(&output.stderr));
let alef = std::fs::read(&format!("{target}/alef_")).unwrap();
assert_eq!(alef, bet);
}
}
}
}
@@ -315,9 +342,8 @@ mod tests {
for &d in &[0, 1, 2] {
for &e in &[0, 1, 2] {
for &f in &[0, 1, 2] {
use std::fs::{self, File};
use std::fs::File;
use std::io::Write;
use std::process::Command;
let mut alef = Vec::new();
let mut bet = Vec::new();
alef.write_all(if a == 0 { b"a\n" } else { b"f\n" })
@@ -353,7 +379,7 @@ mod tests {
// This test diff is intentionally reversed.
// We want it to turn the alef into bet.
let diff = diff_w(&alef, &bet, &format!("{target}/alefr")).unwrap();
File::create("target/abr.ed")
File::create(&format!("{target}/abr.ed"))
.unwrap()
.write_all(&diff)
.unwrap();
@@ -363,16 +389,20 @@ mod tests {
fb.write_all(&bet[..]).unwrap();
let _ = fa;
let _ = fb;
let output = Command::new("ed")
.arg(&format!("{target}/alefr"))
.stdin(File::open("target/abr.ed").unwrap())
.output()
.unwrap();
assert!(output.status.success(), "{:?}", output);
//println!("{}", String::from_utf8_lossy(&output.stdout));
//println!("{}", String::from_utf8_lossy(&output.stderr));
let alef = fs::read(&format!("{target}/alefr")).unwrap();
assert_eq!(alef, bet);
#[cfg(not(windows))] // there's no ed on windows
{
use std::process::Command;
let output = Command::new("ed")
.arg(&format!("{target}/alefr"))
.stdin(File::open(&format!("{target}/abr.ed")).unwrap())
.output()
.unwrap();
assert!(output.status.success(), "{output:?}");
//println!("{}", String::from_utf8_lossy(&output.stdout));
//println!("{}", String::from_utf8_lossy(&output.stderr));
let alef = std::fs::read(&format!("{target}/alefr")).unwrap();
assert_eq!(alef, bet);
}
}
}
}
@@ -380,4 +410,40 @@ mod tests {
}
}
}
#[test]
fn test_stop_early() {
let from = ["a", "b", "c", ""].join("\n");
let to = ["a", "d", "c", ""].join("\n");
let diff_full = diff(from.as_bytes(), to.as_bytes(), &Params::default()).unwrap();
let expected_full = ["2c", "d", ".", ""].join("\n");
assert_eq!(diff_full, expected_full.as_bytes());
let diff_brief = diff(
from.as_bytes(),
to.as_bytes(),
&Params {
brief: true,
..Default::default()
},
)
.unwrap();
let expected_brief = "\0".as_bytes();
assert_eq!(diff_brief, expected_brief);
let nodiff_full = diff(from.as_bytes(), from.as_bytes(), &Params::default()).unwrap();
assert!(nodiff_full.is_empty());
let nodiff_brief = diff(
from.as_bytes(),
from.as_bytes(),
&Params {
brief: true,
..Default::default()
},
)
.unwrap();
assert!(nodiff_brief.is_empty());
}
}
+3
View File
@@ -1,7 +1,10 @@
pub mod context_diff;
pub mod ed_diff;
pub mod macros;
pub mod normal_diff;
pub mod params;
pub mod unified_diff;
pub mod utils;
// Re-export the public functions/types you need
pub use context_diff::diff as context_diff;
+25
View File
@@ -0,0 +1,25 @@
// asserts equality of the actual diff and expected diff
// considering datetime varitations
//
// It replaces the modification time in the actual diff
// with placeholder "TIMESTAMP" and then asserts the equality
//
// For eg.
// let brief = "*** fruits_old.txt\t2024-03-24 23:43:05.189597645 +0530\n
// --- fruits_new.txt\t2024-03-24 23:35:08.922581904 +0530\n";
//
// replaced = "*** fruits_old.txt\tTIMESTAMP\n
// --- fruits_new.txt\tTIMESTAMP\n";
#[macro_export]
macro_rules! assert_diff_eq {
($actual:expr, $expected:expr) => {{
use regex::Regex;
use std::str;
let diff = str::from_utf8(&$actual).unwrap();
let re = Regex::new(r"\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d+ [+-]\d{4}").unwrap();
let actual = re.replacen(diff, 2, "TIMESTAMP");
assert_eq!(actual, $expected);
}};
}
+72 -36
View File
@@ -3,58 +3,94 @@
// For the full copyright and license information, please view the LICENSE-*
// files that was distributed with this source code.
use crate::params::{parse_params, Format, Params};
use crate::params::{parse_params, Format};
use std::env;
use std::ffi::OsString;
use std::fs;
use std::io::{self, Write};
use std::io::{self, Read, Write};
use std::process::{exit, ExitCode};
mod context_diff;
mod ed_diff;
mod macros;
mod normal_diff;
mod params;
mod unified_diff;
mod utils;
fn main() -> Result<(), String> {
// Exit codes are documented at
// https://www.gnu.org/software/diffutils/manual/html_node/Invoking-diff.html.
// An exit status of 0 means no differences were found,
// 1 means some differences were found,
// and 2 means trouble.
fn main() -> ExitCode {
let opts = env::args_os();
let Params {
from,
to,
context_count,
format,
} = parse_params(opts)?;
// read files
let from_content = match fs::read(&from) {
Ok(from_content) => from_content,
Err(e) => {
return Err(format!("Failed to read from-file: {e}"));
let params = parse_params(opts).unwrap_or_else(|error| {
eprintln!("{error}");
exit(2);
});
// if from and to are the same file, no need to perform any comparison
let maybe_report_identical_files = || {
if params.report_identical_files {
println!(
"Files {} and {} are identical",
params.from.to_string_lossy(),
params.to.to_string_lossy(),
);
}
};
let to_content = match fs::read(&to) {
if params.from == "-" && params.to == "-"
|| same_file::is_same_file(&params.from, &params.to).unwrap_or(false)
{
maybe_report_identical_files();
return ExitCode::SUCCESS;
}
// read files
fn read_file_contents(filepath: &OsString) -> io::Result<Vec<u8>> {
if filepath == "-" {
let mut content = Vec::new();
io::stdin().read_to_end(&mut content).and(Ok(content))
} else {
fs::read(filepath)
}
}
let from_content = match read_file_contents(&params.from) {
Ok(from_content) => from_content,
Err(e) => {
eprintln!("Failed to read from-file: {e}");
return ExitCode::from(2);
}
};
let to_content = match read_file_contents(&params.to) {
Ok(to_content) => to_content,
Err(e) => {
return Err(format!("Failed to read from-file: {e}"));
eprintln!("Failed to read to-file: {e}");
return ExitCode::from(2);
}
};
// run diff
let result: Vec<u8> = match format {
Format::Normal => normal_diff::diff(&from_content, &to_content),
Format::Unified => unified_diff::diff(
&from_content,
&from.to_string_lossy(),
&to_content,
&to.to_string_lossy(),
context_count,
),
Format::Context => context_diff::diff(
&from_content,
&from.to_string_lossy(),
&to_content,
&to.to_string_lossy(),
context_count,
),
Format::Ed => ed_diff::diff(&from_content, &to_content)?,
let result: Vec<u8> = match params.format {
Format::Normal => normal_diff::diff(&from_content, &to_content, &params),
Format::Unified => unified_diff::diff(&from_content, &to_content, &params),
Format::Context => context_diff::diff(&from_content, &to_content, &params),
Format::Ed => ed_diff::diff(&from_content, &to_content, &params).unwrap_or_else(|error| {
eprintln!("{error}");
exit(2);
}),
};
io::stdout().write_all(&result).unwrap();
Ok(())
if params.brief && !result.is_empty() {
println!(
"Files {} and {} differ",
params.from.to_string_lossy(),
params.to.to_string_lossy()
);
} else {
io::stdout().write_all(&result).unwrap();
}
if result.is_empty() {
maybe_report_identical_files();
ExitCode::SUCCESS
} else {
ExitCode::from(1)
}
}
+100 -13
View File
@@ -5,6 +5,9 @@
use std::io::Write;
use crate::params::Params;
use crate::utils::do_write_line;
#[derive(Debug, PartialEq)]
struct Mismatch {
pub line_number_expected: usize,
@@ -29,7 +32,7 @@ impl Mismatch {
}
// Produces a diff between the expected output and actual output.
fn make_diff(expected: &[u8], actual: &[u8]) -> Vec<Mismatch> {
fn make_diff(expected: &[u8], actual: &[u8], stop_early: bool) -> Vec<Mismatch> {
let mut line_number_expected = 1;
let mut line_number_actual = 1;
let mut results = Vec::new();
@@ -100,6 +103,10 @@ fn make_diff(expected: &[u8], actual: &[u8]) -> Vec<Mismatch> {
}
}
}
if stop_early && !results.is_empty() {
// Optimization: stop analyzing the files as soon as there are any differences
return results;
}
}
if !mismatch.actual.is_empty() || !mismatch.expected.is_empty() {
@@ -110,9 +117,15 @@ fn make_diff(expected: &[u8], actual: &[u8]) -> Vec<Mismatch> {
}
#[must_use]
pub fn diff(expected: &[u8], actual: &[u8]) -> Vec<u8> {
pub fn diff(expected: &[u8], actual: &[u8], params: &Params) -> Vec<u8> {
// See https://www.gnu.org/software/diffutils/manual/html_node/Detailed-Normal.html
// for details on the syntax of the normal format.
let mut output = Vec::new();
let diff_results = make_diff(expected, actual);
let diff_results = make_diff(expected, actual, params.brief);
if params.brief && !diff_results.is_empty() {
write!(&mut output, "\0").unwrap();
return output;
}
for result in diff_results {
let line_number_expected = result.line_number_expected;
let line_number_actual = result.line_number_actual;
@@ -121,6 +134,7 @@ pub fn diff(expected: &[u8], actual: &[u8]) -> Vec<u8> {
match (expected_count, actual_count) {
(0, 0) => unreachable!(),
(0, _) => writeln!(
// 'a' stands for "Add lines"
&mut output,
"{}a{},{}",
line_number_expected - 1,
@@ -129,6 +143,7 @@ pub fn diff(expected: &[u8], actual: &[u8]) -> Vec<u8> {
)
.unwrap(),
(_, 0) => writeln!(
// 'd' stands for "Delete lines"
&mut output,
"{},{}d{}",
line_number_expected,
@@ -136,7 +151,33 @@ pub fn diff(expected: &[u8], actual: &[u8]) -> Vec<u8> {
line_number_actual - 1
)
.unwrap(),
(1, 1) => writeln!(
// 'c' stands for "Change lines"
// exactly one line replaced by one line
&mut output,
"{line_number_expected}c{line_number_actual}"
)
.unwrap(),
(1, _) => writeln!(
// one line replaced by multiple lines
&mut output,
"{}c{},{}",
line_number_expected,
line_number_actual,
actual_count + line_number_actual - 1
)
.unwrap(),
(_, 1) => writeln!(
// multiple lines replaced by one line
&mut output,
"{},{}c{}",
line_number_expected,
expected_count + line_number_expected - 1,
line_number_actual
)
.unwrap(),
_ => writeln!(
// general case: multiple lines replaced by multiple lines
&mut output,
"{},{}c{},{}",
line_number_expected,
@@ -148,7 +189,7 @@ pub fn diff(expected: &[u8], actual: &[u8]) -> Vec<u8> {
}
for expected in &result.expected {
write!(&mut output, "< ").unwrap();
output.write_all(expected).unwrap();
do_write_line(&mut output, expected, params.expand_tabs, params.tabsize).unwrap();
writeln!(&mut output).unwrap();
}
if result.expected_missing_nl {
@@ -159,7 +200,7 @@ pub fn diff(expected: &[u8], actual: &[u8]) -> Vec<u8> {
}
for actual in &result.actual {
write!(&mut output, "> ").unwrap();
output.write_all(actual).unwrap();
do_write_line(&mut output, actual, params.expand_tabs, params.tabsize).unwrap();
writeln!(&mut output).unwrap();
}
if result.actual_missing_nl {
@@ -173,6 +214,18 @@ pub fn diff(expected: &[u8], actual: &[u8]) -> Vec<u8> {
mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn test_basic() {
let mut a = Vec::new();
a.write_all(b"a\n").unwrap();
let mut b = Vec::new();
b.write_all(b"b\n").unwrap();
let diff = diff(&a, &b, &Params::default());
let expected = b"1c1\n< a\n---\n> b\n".to_vec();
assert_eq!(diff, expected);
}
#[test]
fn test_permutations() {
let target = "target/normal-diff/";
@@ -221,7 +274,7 @@ mod tests {
}
// This test diff is intentionally reversed.
// We want it to turn the alef into bet.
let diff = diff(&alef, &bet);
let diff = diff(&alef, &bet, &Params::default());
File::create(&format!("{target}/ab.diff"))
.unwrap()
.write_all(&diff)
@@ -238,7 +291,7 @@ mod tests {
.stdin(File::open(&format!("{target}/ab.diff")).unwrap())
.output()
.unwrap();
assert!(output.status.success(), "{:?}", output);
assert!(output.status.success(), "{output:?}");
//println!("{}", String::from_utf8_lossy(&output.stdout));
//println!("{}", String::from_utf8_lossy(&output.stderr));
let alef = fs::read(&format!("{target}/alef")).unwrap();
@@ -313,7 +366,7 @@ mod tests {
}
// This test diff is intentionally reversed.
// We want it to turn the alef into bet.
let diff = diff(&alef, &bet);
let diff = diff(&alef, &bet, &Params::default());
File::create(&format!("{target}/abn.diff"))
.unwrap()
.write_all(&diff)
@@ -331,7 +384,7 @@ mod tests {
.stdin(File::open(&format!("{target}/abn.diff")).unwrap())
.output()
.unwrap();
assert!(output.status.success(), "{:?}", output);
assert!(output.status.success(), "{output:?}");
//println!("{}", String::from_utf8_lossy(&output.stdout));
//println!("{}", String::from_utf8_lossy(&output.stderr));
let alef = fs::read(&format!("{target}/alefn")).unwrap();
@@ -387,7 +440,7 @@ mod tests {
}
// This test diff is intentionally reversed.
// We want it to turn the alef into bet.
let diff = diff(&alef, &bet);
let diff = diff(&alef, &bet, &Params::default());
File::create(&format!("{target}/ab_.diff"))
.unwrap()
.write_all(&diff)
@@ -404,7 +457,7 @@ mod tests {
.stdin(File::open(&format!("{target}/ab_.diff")).unwrap())
.output()
.unwrap();
assert!(output.status.success(), "{:?}", output);
assert!(output.status.success(), "{output:?}");
//println!("{}", String::from_utf8_lossy(&output.stdout));
//println!("{}", String::from_utf8_lossy(&output.stderr));
let alef = fs::read(&format!("{target}/alef_")).unwrap();
@@ -465,7 +518,7 @@ mod tests {
}
// This test diff is intentionally reversed.
// We want it to turn the alef into bet.
let diff = diff(&alef, &bet);
let diff = diff(&alef, &bet, &Params::default());
File::create(&format!("{target}/abr.diff"))
.unwrap()
.write_all(&diff)
@@ -482,7 +535,7 @@ mod tests {
.stdin(File::open(&format!("{target}/abr.diff")).unwrap())
.output()
.unwrap();
assert!(output.status.success(), "{:?}", output);
assert!(output.status.success(), "{output:?}");
//println!("{}", String::from_utf8_lossy(&output.stdout));
//println!("{}", String::from_utf8_lossy(&output.stderr));
let alef = fs::read(&format!("{target}/alefr")).unwrap();
@@ -494,4 +547,38 @@ mod tests {
}
}
}
#[test]
fn test_stop_early() {
let from = ["a", "b", "c"].join("\n");
let to = ["a", "d", "c"].join("\n");
let diff_full = diff(from.as_bytes(), to.as_bytes(), &Params::default());
let expected_full = ["2c2", "< b", "---", "> d", ""].join("\n");
assert_eq!(diff_full, expected_full.as_bytes());
let diff_brief = diff(
from.as_bytes(),
to.as_bytes(),
&Params {
brief: true,
..Default::default()
},
);
let expected_brief = "\0".as_bytes();
assert_eq!(diff_brief, expected_brief);
let nodiff_full = diff(from.as_bytes(), from.as_bytes(), &Params::default());
assert!(nodiff_full.is_empty());
let nodiff_brief = diff(
from.as_bytes(),
from.as_bytes(),
&Params {
brief: true,
..Default::default()
},
);
assert!(nodiff_brief.is_empty());
}
}
+597 -84
View File
File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More