mirror of
https://github.com/uutils/diffutils.git
synced 2026-06-10 15:48:59 -07:00
Compare commits
163 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0d5719810f | |||
| 7480068e7d | |||
| 763074a804 | |||
| fac8dab182 | |||
| 2e681301b4 | |||
| 50057412bd | |||
| 68292b370d | |||
| 26bcc102c0 | |||
| 50198ef2c1 | |||
| bfdbf6d7b2 | |||
| f75c187971 | |||
| d07c0438b5 | |||
| 72c7802f06 | |||
| c1b66e4a47 | |||
| 9103365691 | |||
| 7574243de1 | |||
| 6f3834d69d | |||
| d8b91fd60e | |||
| 7c9c2a1ab2 | |||
| 63d51bcd69 | |||
| d5bce65a29 | |||
| 9db1eab1d0 | |||
| 2392acfad1 | |||
| 2a899a9fc7 | |||
| 6ec8370b4b | |||
| dbabf399d5 | |||
| b815162b80 | |||
| 12b205e655 | |||
| 8a6504dd83 | |||
| 67ef43083a | |||
| b1738538a8 | |||
| eea6b62b20 | |||
| f08a3bf512 | |||
| e55ee893dd | |||
| 24245ee098 | |||
| 11f815a7c2 | |||
| e9a8141618 | |||
| c9a756eb43 | |||
| 468c4bf934 | |||
| 1e8fdd58d9 | |||
| e98b5e179e | |||
| 1901982375 | |||
| eee6f49920 | |||
| 8a3a977d2c | |||
| fa4e0c6097 | |||
| d362046ae5 | |||
| 7964afa336 | |||
| 80b993141b | |||
| d922313c8c | |||
| 3e246ab36c | |||
| 4b70969ff1 | |||
| 767c6f6c4a | |||
| 1f896ca1ac | |||
| 713bd210ab | |||
| 61314eaf4e | |||
| bf9147733d | |||
| ce8457cbdb | |||
| df778c610b | |||
| d92132e721 | |||
| 99d4d02985 | |||
| e7dc6558c6 | |||
| 8c6a648aef | |||
| 0304391bc5 | |||
| 8de0ca60d1 | |||
| 43b9c524d9 | |||
| 3dc3fdf5cd | |||
| b7261a43f4 | |||
| 37fe1ae808 | |||
| 22d973fce6 | |||
| fe28610f21 | |||
| 3a8eddfe2c | |||
| 476e69ee20 | |||
| 65993d6a13 | |||
| 39d2ece187 | |||
| 46a26e896b | |||
| 14799eea89 | |||
| 831348d1fc | |||
| 00a5c0ba44 | |||
| bf104648c1 | |||
| 5669f164b3 | |||
| 11bf271666 | |||
| 674974d5e6 | |||
| 2ba35db431 | |||
| fcec7277c9 | |||
| b8efad6b90 | |||
| 68e2f51983 | |||
| 4edaee190f | |||
| 7f7821f558 | |||
| 1149a247dd | |||
| 1b311c6673 | |||
| aedd0684d1 | |||
| 54c02bdf0b | |||
| ba7cb0aef9 | |||
| 33783d094e | |||
| 900e1c3a68 | |||
| 0a77fe12b9 | |||
| 86bd05c739 | |||
| 00e18a6b0c | |||
| f6eb0835b0 | |||
| be66ff3299 | |||
| e1c319f96b | |||
| 84ad116845 | |||
| 6dc34fed44 | |||
| 9507ca28d7 | |||
| c325291696 | |||
| c08e0b6e1f | |||
| 72da7fca40 | |||
| 61fb0657c1 | |||
| 096aa1dad9 | |||
| 2d9e625a5b | |||
| d863fe443a | |||
| 6be94d8683 | |||
| 44ef772e4a | |||
| bbfca84e17 | |||
| 281098d751 | |||
| a3a372ff36 | |||
| 5b814f8530 | |||
| 34a5cc7340 | |||
| 6a152cdc7f | |||
| b8fada8faa | |||
| a213272d0c | |||
| 25e4a17421 | |||
| 589039ab4c | |||
| f83fccb542 | |||
| 76c4714f78 | |||
| 88a7568b52 | |||
| b135b6f218 | |||
| 314e3a7320 | |||
| 6a73657b3a | |||
| e9f0630aaf | |||
| e6a0ba28c5 | |||
| 80c9944bf7 | |||
| 043c5f9493 | |||
| 9ff8f89626 | |||
| 0ab824abda | |||
| f60fefaf6e | |||
| 14e77548fd | |||
| f2fd2127ed | |||
| cfc68d58bc | |||
| e0283083f2 | |||
| 8d65c2badd | |||
| 42eb15b87a | |||
| a304ac0a68 | |||
| f916f1ce86 | |||
| 4ed7ea1553 | |||
| 62e10c6d6c | |||
| c68d386170 | |||
| a89f30afa0 | |||
| 0a67bf9fb8 | |||
| 1241db4806 | |||
| 3bc8668f78 | |||
| c90eee442f | |||
| 6c29f02527 | |||
| 790ef1e633 | |||
| 4c1a752f11 | |||
| 54a5407bec | |||
| 02632e915c | |||
| 3f9556aa05 | |||
| a94c6a60cf | |||
| c28973c019 | |||
| a660f7440c | |||
| 6a69a39852 | |||
| b55cbf2ca2 |
@@ -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,13 +110,22 @@ jobs:
|
||||
|
||||
- name: rust toolchain ~ install
|
||||
uses: dtolnay/rust-toolchain@nightly
|
||||
- run: rustup component add llvm-tools-preview
|
||||
- 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:
|
||||
CARGO_INCREMENTAL: "0"
|
||||
RUSTC_WRAPPER: ""
|
||||
RUSTFLAGS: "-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort"
|
||||
RUSTFLAGS: "-Cinstrument-coverage -Zcoverage-options=branch -Ccodegen-units=1 -Copt-level=0 -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort"
|
||||
RUSTDOCFLAGS: "-Cpanic=abort"
|
||||
LLVM_PROFILE_FILE: "diffutils-%p-%m.profraw"
|
||||
- name: "`grcov` ~ install"
|
||||
id: build_grcov
|
||||
shell: bash
|
||||
@@ -117,15 +153,14 @@ jobs:
|
||||
COVERAGE_REPORT_FILE="${COVERAGE_REPORT_DIR}/lcov.info"
|
||||
mkdir -p "${COVERAGE_REPORT_DIR}"
|
||||
# display coverage files
|
||||
grcov . --output-type files --ignore build.rs --ignore "vendor/*" --ignore "/*" --ignore "[a-zA-Z]:/*" --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()" | sort --unique
|
||||
grcov . --output-type files --binary-path "${COVERAGE_REPORT_DIR}" | sort --unique
|
||||
# generate coverage report
|
||||
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\()"
|
||||
grcov . --output-type lcov --output-path "${COVERAGE_REPORT_FILE}" --binary-path "${COVERAGE_REPORT_DIR}" --branch
|
||||
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 }}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
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_cmp, should_pass: true }
|
||||
- { name: fuzz_cmp_args, should_pass: true }
|
||||
- { 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 }}
|
||||
@@ -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
+587
-6
File diff suppressed because it is too large
Load Diff
+29
-3
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "diffutils"
|
||||
version = "0.3.0"
|
||||
version = "0.4.2"
|
||||
edition = "2021"
|
||||
description = "A CLI app for generating diff files"
|
||||
license = "MIT OR Apache-2.0"
|
||||
@@ -15,7 +15,33 @@ name = "diffutils"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
diff = "0.1.10"
|
||||
chrono = "0.4.38"
|
||||
diff = "0.1.13"
|
||||
itoa = "1.0.11"
|
||||
regex = "1.10.4"
|
||||
same-file = "1.0.6"
|
||||
unicode-width = "0.2.0"
|
||||
|
||||
[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", "x86_64-apple-darwin", "x86_64-unknown-linux-gnu", "x86_64-pc-windows-msvc"]
|
||||
# Publish jobs to run in CI
|
||||
pr-run-mode = "plan"
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
Copyright (c) Michael Howell
|
||||
Copyright (c) uutils developers
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
[](https://crates.io/crates/diffutils)
|
||||
[](https://discord.gg/wQVJbvJ)
|
||||
[](https://github.com/uutils/diffutils/blob/main/LICENSE)
|
||||
[](https://deps.rs/repo/github/uutils/diffutils)
|
||||
|
||||
[](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/) (diff, cmp, diff3, sdiff) 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
|
||||
|
||||
+13
-1
@@ -9,13 +9,25 @@ edition = "2018"
|
||||
cargo-fuzz = true
|
||||
|
||||
[dependencies]
|
||||
libfuzzer-sys = "0.3"
|
||||
libfuzzer-sys = "0.4.7"
|
||||
diffutils = { path = "../" }
|
||||
|
||||
# Prevent this from interfering with workspaces
|
||||
[workspace]
|
||||
members = ["."]
|
||||
|
||||
[[bin]]
|
||||
name = "fuzz_cmp"
|
||||
path = "fuzz_targets/fuzz_cmp.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
[[bin]]
|
||||
name = "fuzz_cmp_args"
|
||||
path = "fuzz_targets/fuzz_cmp_args.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
[[bin]]
|
||||
name = "fuzz_patch"
|
||||
path = "fuzz_targets/fuzz_patch.rs"
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
"-l"
|
||||
"--verbose"
|
||||
"-b"
|
||||
"--print-bytes"
|
||||
"-lb"
|
||||
"-bl"
|
||||
"-n"
|
||||
"--bytes"
|
||||
"--bytes="
|
||||
"--bytes=1024"
|
||||
"--bytes=99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999"
|
||||
"-i"
|
||||
"--ignore-initial"
|
||||
"--ignore-initial="
|
||||
"--ignore-initial=1024"
|
||||
"--ignore-initial=99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999:9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999"
|
||||
"-s"
|
||||
"-q"
|
||||
"--quiet"
|
||||
"--silent"
|
||||
"-"
|
||||
"--"
|
||||
"1kB"
|
||||
"1G"
|
||||
"1GB"
|
||||
"1T"
|
||||
"1TB"
|
||||
"1P"
|
||||
"1PB"
|
||||
"1Z"
|
||||
"1ZB"
|
||||
"1Y"
|
||||
"1YB"
|
||||
"1Y"
|
||||
"0"
|
||||
"1:2"
|
||||
@@ -0,0 +1,51 @@
|
||||
#![no_main]
|
||||
#[macro_use]
|
||||
extern crate libfuzzer_sys;
|
||||
use diffutilslib::cmp::{self, Cmp};
|
||||
|
||||
use std::ffi::OsString;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
|
||||
fn os(s: &str) -> OsString {
|
||||
OsString::from(s)
|
||||
}
|
||||
|
||||
fuzz_target!(|x: (Vec<u8>, Vec<u8>)| {
|
||||
let args = vec!["cmp", "-l", "-b", "target/fuzz.cmp.a", "target/fuzz.cmp.b"]
|
||||
.into_iter()
|
||||
.map(|s| os(s))
|
||||
.peekable();
|
||||
|
||||
let (from, to) = x;
|
||||
|
||||
File::create("target/fuzz.cmp.a")
|
||||
.unwrap()
|
||||
.write_all(&from)
|
||||
.unwrap();
|
||||
|
||||
File::create("target/fuzz.cmp.b")
|
||||
.unwrap()
|
||||
.write_all(&to)
|
||||
.unwrap();
|
||||
|
||||
let params =
|
||||
cmp::parse_params(args).unwrap_or_else(|e| panic!("Failed to parse params: {}", e));
|
||||
let ret = cmp::cmp(¶ms);
|
||||
if from == to && !matches!(ret, Ok(Cmp::Equal)) {
|
||||
panic!(
|
||||
"target/fuzz.cmp.a and target/fuzz.cmp.b are equal, but cmp returned {:?}.",
|
||||
ret
|
||||
);
|
||||
} else if from != to && !matches!(ret, Ok(Cmp::Different)) {
|
||||
panic!(
|
||||
"target/fuzz.cmp.a and target/fuzz.cmp.b are different, but cmp returned {:?}.",
|
||||
ret
|
||||
);
|
||||
} else if ret.is_err() {
|
||||
panic!(
|
||||
"target/fuzz.cmp.a and target/fuzz.cmp.b caused cmp to error ({:?}).",
|
||||
ret
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,23 @@
|
||||
#![no_main]
|
||||
#[macro_use]
|
||||
extern crate libfuzzer_sys;
|
||||
use diffutilslib::cmp;
|
||||
|
||||
use libfuzzer_sys::Corpus;
|
||||
use std::ffi::OsString;
|
||||
|
||||
fn os(s: &str) -> OsString {
|
||||
OsString::from(s)
|
||||
}
|
||||
|
||||
fuzz_target!(|x: Vec<OsString>| -> Corpus {
|
||||
if x.len() > 6 {
|
||||
// Make sure we try to parse an option when we get longer args. x[0] will be
|
||||
// the executable name.
|
||||
if ![os("-l"), os("-b"), os("-s"), os("-n"), os("-i")].contains(&x[1]) {
|
||||
return Corpus::Reject;
|
||||
}
|
||||
}
|
||||
let _ = cmp::parse_params(x.into_iter().peekable());
|
||||
Corpus::Keep
|
||||
});
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:recommended"
|
||||
]
|
||||
}
|
||||
+1185
File diff suppressed because it is too large
Load Diff
+180
-49
@@ -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(¶ms.from.to_string_lossy());
|
||||
let to_modified_time = get_modification_time(¶ms.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,28 +429,36 @@ 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);
|
||||
File::create(&format!("{target}/ab.diff"))
|
||||
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)
|
||||
.unwrap();
|
||||
let mut fa = File::create(&format!("{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();
|
||||
let mut fb = File::create(format!("{target}/bet")).unwrap();
|
||||
fb.write_all(&bet[..]).unwrap();
|
||||
let _ = fa;
|
||||
let _ = fb;
|
||||
let output = Command::new("patch")
|
||||
.arg("-p0")
|
||||
.arg("--context")
|
||||
.stdin(File::open(&format!("{target}/ab.diff")).unwrap())
|
||||
.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();
|
||||
let alef = fs::read(format!("{target}/alef")).unwrap();
|
||||
assert_eq!(alef, bet);
|
||||
}
|
||||
}
|
||||
@@ -477,28 +510,36 @@ 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);
|
||||
File::create(&format!("{target}/ab_.diff"))
|
||||
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)
|
||||
.unwrap();
|
||||
let mut fa = File::create(&format!("{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();
|
||||
let mut fb = File::create(format!("{target}/bet_")).unwrap();
|
||||
fb.write_all(&bet[..]).unwrap();
|
||||
let _ = fa;
|
||||
let _ = fb;
|
||||
let output = Command::new("patch")
|
||||
.arg("-p0")
|
||||
.arg("--context")
|
||||
.stdin(File::open(&format!("{target}/ab_.diff")).unwrap())
|
||||
.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();
|
||||
let alef = fs::read(format!("{target}/alef_")).unwrap();
|
||||
assert_eq!(alef, bet);
|
||||
}
|
||||
}
|
||||
@@ -553,28 +594,36 @@ 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);
|
||||
File::create(&format!("{target}/abx.diff"))
|
||||
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)
|
||||
.unwrap();
|
||||
let mut fa = File::create(&format!("{target}/alefx")).unwrap();
|
||||
let mut fa = File::create(format!("{target}/alefx")).unwrap();
|
||||
fa.write_all(&alef[..]).unwrap();
|
||||
let mut fb = File::create(&format!("{target}/betx")).unwrap();
|
||||
let mut fb = File::create(format!("{target}/betx")).unwrap();
|
||||
fb.write_all(&bet[..]).unwrap();
|
||||
let _ = fa;
|
||||
let _ = fb;
|
||||
let output = Command::new("patch")
|
||||
.arg("-p0")
|
||||
.arg("--context")
|
||||
.stdin(File::open(&format!("{target}/abx.diff")).unwrap())
|
||||
.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();
|
||||
let alef = fs::read(format!("{target}/alefx")).unwrap();
|
||||
assert_eq!(alef, bet);
|
||||
}
|
||||
}
|
||||
@@ -632,28 +681,36 @@ 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);
|
||||
File::create(&format!("{target}/abr.diff"))
|
||||
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)
|
||||
.unwrap();
|
||||
let mut fa = File::create(&format!("{target}/alefr")).unwrap();
|
||||
let mut fa = File::create(format!("{target}/alefr")).unwrap();
|
||||
fa.write_all(&alef[..]).unwrap();
|
||||
let mut fb = File::create(&format!("{target}/betr")).unwrap();
|
||||
let mut fb = File::create(format!("{target}/betr")).unwrap();
|
||||
fb.write_all(&bet[..]).unwrap();
|
||||
let _ = fa;
|
||||
let _ = fb;
|
||||
let output = Command::new("patch")
|
||||
.arg("-p0")
|
||||
.arg("--context")
|
||||
.stdin(File::open(&format!("{target}/abr.diff")).unwrap())
|
||||
.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();
|
||||
let alef = fs::read(format!("{target}/alefr")).unwrap();
|
||||
assert_eq!(alef, bet);
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
// This file is part of the uutils diffutils package.
|
||||
//
|
||||
// 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};
|
||||
use crate::utils::report_failure_to_read_input_file;
|
||||
use crate::{context_diff, ed_diff, normal_diff, unified_diff};
|
||||
use std::env::ArgsOs;
|
||||
use std::ffi::OsString;
|
||||
use std::fs;
|
||||
use std::io::{self, Read, Write};
|
||||
use std::iter::Peekable;
|
||||
use std::process::{exit, ExitCode};
|
||||
|
||||
// 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.
|
||||
pub fn main(opts: Peekable<ArgsOs>) -> ExitCode {
|
||||
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(),
|
||||
);
|
||||
}
|
||||
};
|
||||
if params.from == "-" && params.to == "-"
|
||||
|| same_file::is_same_file(¶ms.from, ¶ms.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 mut io_error = false;
|
||||
let from_content = match read_file_contents(¶ms.from) {
|
||||
Ok(from_content) => from_content,
|
||||
Err(e) => {
|
||||
report_failure_to_read_input_file(¶ms.executable, ¶ms.from, &e);
|
||||
io_error = true;
|
||||
vec![]
|
||||
}
|
||||
};
|
||||
let to_content = match read_file_contents(¶ms.to) {
|
||||
Ok(to_content) => to_content,
|
||||
Err(e) => {
|
||||
report_failure_to_read_input_file(¶ms.executable, ¶ms.to, &e);
|
||||
io_error = true;
|
||||
vec![]
|
||||
}
|
||||
};
|
||||
if io_error {
|
||||
return ExitCode::from(2);
|
||||
}
|
||||
|
||||
// run diff
|
||||
let result: Vec<u8> = match params.format {
|
||||
Format::Normal => normal_diff::diff(&from_content, &to_content, ¶ms),
|
||||
Format::Unified => unified_diff::diff(&from_content, &to_content, ¶ms),
|
||||
Format::Context => context_diff::diff(&from_content, &to_content, ¶ms),
|
||||
Format::Ed => ed_diff::diff(&from_content, &to_content, ¶ms).unwrap_or_else(|error| {
|
||||
eprintln!("{error}");
|
||||
exit(2);
|
||||
}),
|
||||
};
|
||||
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)
|
||||
}
|
||||
}
|
||||
+117
-51
@@ -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,26 +225,30 @@ 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();
|
||||
let mut fa = File::create(&format!("{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();
|
||||
let mut fb = File::create(format!("{target}/bet")).unwrap();
|
||||
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();
|
||||
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,26 +379,30 @@ 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();
|
||||
let mut fa = File::create(&format!("{target}/alefr")).unwrap();
|
||||
let mut fa = File::create(format!("{target}/alefr")).unwrap();
|
||||
fa.write_all(&alef[..]).unwrap();
|
||||
let mut fb = File::create(&format!("{target}/betr")).unwrap();
|
||||
let mut fb = File::create(format!("{target}/betr")).unwrap();
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user