From b55cbf2ca2f53b480d9b51563aafab2e8bba0235 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 22 Jan 2024 15:43:25 +0000 Subject: [PATCH 01/61] Add renovate.json --- renovate.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 renovate.json diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..5db72dd --- /dev/null +++ b/renovate.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:recommended" + ] +} From 6a69a3985271092c0c15b09ed644da638bcf34c1 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 27 Jan 2024 09:50:58 +0100 Subject: [PATCH 02/61] add badges --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 009f66a..aef5bdd 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,10 @@ +[![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/master/graph/badge.svg)](https://codecov.io/gh/uutils/diffutils) + The goal of this package is to be a dropped in replacement for the [diffutils commands](https://www.gnu.org/software/diffutils/) in Rust. From c28973c019afe575b98828277c4a06574b3c0a6a Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Fri, 9 Feb 2024 10:28:27 +0100 Subject: [PATCH 03/61] coverage: remove the fail fast to continue for linux even if windows fails --- .github/workflows/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 03fc854..3ffce21 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,7 +53,6 @@ jobs: name: Code Coverage runs-on: ${{ matrix.job.os }} strategy: - fail-fast: true matrix: job: - { os: ubuntu-latest , features: unix } From a94c6a60cf537c3007d1f0329d87ba889409161f Mon Sep 17 00:00:00 2001 From: Olivier Tilloy Date: Wed, 7 Feb 2024 20:50:55 +0100 Subject: [PATCH 04/61] Normal diff: compact ranges of lines where possible (fixes #10) --- src/normal_diff.rs | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/normal_diff.rs b/src/normal_diff.rs index eda9d8c..d4c2a86 100644 --- a/src/normal_diff.rs +++ b/src/normal_diff.rs @@ -136,6 +136,28 @@ pub fn diff(expected: &[u8], actual: &[u8]) -> Vec { line_number_actual - 1 ) .unwrap(), + (1, 1) => writeln!( + &mut output, + "{}c{}", + line_number_expected, line_number_actual + ) + .unwrap(), + (1, _) => writeln!( + &mut output, + "{}c{},{}", + line_number_expected, + line_number_actual, + actual_count + line_number_actual - 1 + ) + .unwrap(), + (_, 1) => writeln!( + &mut output, + "{},{}c{}", + line_number_expected, + expected_count + line_number_expected - 1, + line_number_actual + ) + .unwrap(), _ => writeln!( &mut output, "{},{}c{},{}", @@ -173,6 +195,18 @@ pub fn diff(expected: &[u8], actual: &[u8]) -> Vec { 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); + 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/"; From 3f9556aa05f494ea27a4bbd6e90e584c5915d8f3 Mon Sep 17 00:00:00 2001 From: Olivier Tilloy Date: Thu, 8 Feb 2024 17:04:00 +0100 Subject: [PATCH 05/61] Add comments to ease readability and maintainability --- src/normal_diff.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/normal_diff.rs b/src/normal_diff.rs index d4c2a86..fa9c059 100644 --- a/src/normal_diff.rs +++ b/src/normal_diff.rs @@ -111,6 +111,8 @@ fn make_diff(expected: &[u8], actual: &[u8]) -> Vec { #[must_use] pub fn diff(expected: &[u8], actual: &[u8]) -> Vec { + // 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); for result in diff_results { @@ -121,6 +123,7 @@ pub fn diff(expected: &[u8], actual: &[u8]) -> Vec { match (expected_count, actual_count) { (0, 0) => unreachable!(), (0, _) => writeln!( + // 'a' stands for "Add lines" &mut output, "{}a{},{}", line_number_expected - 1, @@ -129,6 +132,7 @@ pub fn diff(expected: &[u8], actual: &[u8]) -> Vec { ) .unwrap(), (_, 0) => writeln!( + // 'd' stands for "Delete lines" &mut output, "{},{}d{}", line_number_expected, @@ -137,12 +141,16 @@ pub fn diff(expected: &[u8], actual: &[u8]) -> Vec { ) .unwrap(), (1, 1) => writeln!( + // 'c' stands for "Change lines" + // exactly one line replaced by one line &mut output, "{}c{}", - line_number_expected, line_number_actual + line_number_expected, + line_number_actual ) .unwrap(), (1, _) => writeln!( + // one line replaced by multiple lines &mut output, "{}c{},{}", line_number_expected, @@ -151,6 +159,7 @@ pub fn diff(expected: &[u8], actual: &[u8]) -> Vec { ) .unwrap(), (_, 1) => writeln!( + // multiple lines replaced by one line &mut output, "{},{}c{}", line_number_expected, @@ -159,6 +168,7 @@ pub fn diff(expected: &[u8], actual: &[u8]) -> Vec { ) .unwrap(), _ => writeln!( + // general case: multiple lines replaced by multiple lines &mut output, "{},{}c{},{}", line_number_expected, From 54a5407bec1e96f7526c0fb5e0cbd40e46496c1e Mon Sep 17 00:00:00 2001 From: Olivier Tilloy Date: Fri, 9 Feb 2024 17:51:50 +0100 Subject: [PATCH 06/61] Fix trivial typo in error message --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index eaa7d55..8a3e74e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -33,7 +33,7 @@ fn main() -> Result<(), String> { let to_content = match fs::read(&to) { Ok(to_content) => to_content, Err(e) => { - return Err(format!("Failed to read from-file: {e}")); + return Err(format!("Failed to read to-file: {e}")); } }; // run diff From 790ef1e633ecfda15e0f81b66777365576adf838 Mon Sep 17 00:00:00 2001 From: Olivier Tilloy Date: Sat, 17 Feb 2024 14:27:52 +0000 Subject: [PATCH 07/61] Run the GNU test suite in CI (fixes #8) (#13) --- .github/workflows/ci.yml | 17 ++++ tests/print-test-results.sh | 38 +++++++++ tests/run-upstream-testsuite.sh | 141 ++++++++++++++++++++++++++++++++ 3 files changed, 196 insertions(+) create mode 100755 tests/print-test-results.sh create mode 100755 tests/run-upstream-testsuite.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3ffce21..d416f74 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,6 +49,23 @@ 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 }} diff --git a/tests/print-test-results.sh b/tests/print-test-results.sh new file mode 100755 index 0000000..23136d6 --- /dev/null +++ b/tests/print-test-results.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +# Print the test results written to a JSON file by run-upstream-testsuite.sh +# in a markdown format. The printout includes the name of the test, the result, +# the URL to the test script and the contents of stdout and stderr. +# It can be used verbatim as the description when filing an issue for a test +# with an unexpected result. + +json="test-results.json" +[[ -n $1 ]] && json="$1" + +codeblock () { echo -e "\`\`\`\n$1\n\`\`\`"; } + +jq -c '.tests[]' "$json" | while read -r test +do + name=$(echo "$test" | jq -r '.test') + echo "# test: $name" + result=$(echo "$test" | jq -r '.result') + echo "result: $result" + url=$(echo "$test" | jq -r '.url') + echo "url: $url" + if [[ "$result" != "SKIP" ]] + then + stdout=$(echo "$test" | jq -r '.stdout' | base64 -d) + if [[ -n "$stdout" ]] + then + echo "## stdout" + codeblock "$stdout" + fi + stderr=$(echo "$test" | jq -r '.stderr' | base64 -d) + if [[ -n "$stderr" ]] + then + echo "## stderr" + codeblock "$stderr" + fi + fi + echo "" +done diff --git a/tests/run-upstream-testsuite.sh b/tests/run-upstream-testsuite.sh new file mode 100755 index 0000000..d85dfd6 --- /dev/null +++ b/tests/run-upstream-testsuite.sh @@ -0,0 +1,141 @@ +#!/bin/bash + +# Run the GNU upstream test suite for diffutils against a local build of the +# Rust implementation, print out a summary of the test results, and writes a +# JSON file ('test-results.json') containing detailed information about the +# test run. + +# The JSON file contains metadata about the test run, and for each test the +# result as well as the contents of stdout, stderr, and of all the files +# written by the test script, if any (excluding subdirectories). + +# The script takes a shortcut to fetch only the test suite from the upstream +# repository and carefully avoids running the autotools machinery which is +# time-consuming and resource-intensive, and doesn't offer the option to not +# build the upstream binaries. As a consequence, the environment in which the +# tests are run might not match exactly that used when the upstream tests are +# run through the autotools. + +# By default it expects a release build of the diffutils binary, but a +# different build profile can be specified as an argument +# (e.g. 'dev' or 'test'). +# Unless overriden by the $TESTS environment variable, all tests in the test +# suite will be run. Tests targeting a command that is not yet implemented +# (e.g. cmp, diff3 or sdiff) are skipped. + +scriptpath=$(dirname "$(readlink -f "$0")") +rev=$(git rev-parse HEAD) + +# Allow passing a specific profile as parameter (default to "release") +profile="release" +[[ -n $1 ]] && profile="$1" + +# Verify that the diffutils binary was built for the requested profile +binary="$scriptpath/../target/$profile/diffutils" +if [[ ! -x "$binary" ]] +then + echo "Missing build for profile $profile" + exit 1 +fi + +# Work in a temporary directory +tempdir=$(mktemp -d) +cd "$tempdir" + +# Check out the upstream test suite +gitserver="https://git.savannah.gnu.org" +testsuite="$gitserver/git/diffutils.git" +echo "Fetching upstream test suite from $testsuite" +git clone -n --depth=1 --filter=tree:0 "$testsuite" &> /dev/null +cd diffutils +git sparse-checkout set --no-cone tests &> /dev/null +git checkout &> /dev/null +upstreamrev=$(git rev-parse HEAD) + +# Ensure that calling `diff` invokes the built `diffutils` binary instead of +# the upstream `diff` binary that is most likely installed on the system +mkdir src +cd src +ln -s "$binary" diff +cd ../tests + +if [[ -n "$TESTS" ]] +then + tests="$TESTS" +else + # Get a list of all upstream tests (default if $TESTS isn't set) + echo -e '\n\nprinttests:\n\t@echo "${TESTS}"' >> Makefile.am + tests=$(make -f Makefile.am printtests) +fi +total=$(echo "$tests" | wc -w) +echo "Running $total tests" +export LC_ALL=C +export KEEP=yes +exitcode=0 +timestamp=$(date -Iseconds) +urlroot="$gitserver/cgit/diffutils.git/tree/tests/" +passed=0 +failed=0 +skipped=0 +normal="$(tput sgr0)" +for test in $tests +do + result="FAIL" + url="$urlroot$test?id=$upstreamrev" + # Run only the tests that invoke `diff`, + # because other binaries aren't implemented yet + if ! grep -E -s -q "(cmp|diff3|sdiff)" "$test" + then + sh "$test" 1> stdout.txt 2> stderr.txt && result="PASS" || exitcode=1 + json+="{\"test\":\"$test\",\"result\":\"$result\"," + json+="\"url\":\"$url\"," + json+="\"stdout\":\"$(base64 -w0 < stdout.txt)\"," + json+="\"stderr\":\"$(base64 -w0 < stderr.txt)\"," + json+="\"files\":{" + cd gt-$test.* + # Note: this doesn't include the contents of subdirectories, + # but there isn't much value added in doing so + for file in * + do + [[ -f "$file" ]] && json+="\"$file\":\"$(base64 -w0 < "$file")\"," + done + json="${json%,}}}," + cd - > /dev/null + [[ "$result" = "PASS" ]] && (( passed++ )) + [[ "$result" = "FAIL" ]] && (( failed++ )) + else + result="SKIP" + (( skipped++ )) + json+="{\"test\":\"$test\",\"url\":\"$url\",\"result\":\"$result\"}," + fi + color=2 # green + [[ "$result" = "FAIL" ]] && color=1 # red + [[ "$result" = "SKIP" ]] && color=3 # yellow + printf " %-40s $(tput setaf $color)$result$(tput sgr0)\n" "$test" +done +echo "" +echo -n "Summary: TOTAL: $total / " +echo -n "$(tput setaf 2)PASS$normal: $passed / " +echo -n "$(tput setaf 1)FAIL$normal: $failed / " +echo "$(tput setaf 3)SKIP$normal: $skipped" +echo "" + +json="\"tests\":[${json%,}]" +metadata="\"timestamp\":\"$timestamp\"," +metadata+="\"revision\":\"$rev\"," +metadata+="\"upstream-revision\":\"$upstreamrev\"," +if [[ -n "$GITHUB_ACTIONS" ]] +then + metadata+="\"branch\":\"$GITHUB_REF\"," +fi +json="{$metadata $json}" + +# Clean up +cd "$scriptpath" +rm -rf "$tempdir" + +resultsfile="test-results.json" +echo "$json" | jq > "$resultsfile" +echo "Results written to $scriptpath/$resultsfile" + +exit $exitcode From 6c29f02527358a81737b1086dc490f288edfb799 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 17 Feb 2024 17:05:51 +0100 Subject: [PATCH 08/61] improve the readme and example --- README.md | 91 ++++++++++++++++++++++++------------------------------- 1 file changed, 40 insertions(+), 51 deletions(-) diff --git a/README.md b/README.md index aef5bdd..c99770f 100644 --- a/README.md +++ b/README.md @@ -7,59 +7,48 @@ The goal of this package is to be a dropped 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 + +```bash + +cat <fruits_old.txt +Apple +Banana +Cherry +EOF + +cat <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 From c90eee442ffe294b110e6fb452797cb55307524e Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sat, 17 Feb 2024 17:07:28 +0100 Subject: [PATCH 09/61] Add the example section --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index c99770f..1308648 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,8 @@ cd diffutils cargo build --release ``` +## Example + ```bash cat <fruits_old.txt From 3bc8668f78ffdacad2f8ab62abb931c6aaa665db Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 17 Feb 2024 18:19:02 +0100 Subject: [PATCH 10/61] fix(deps): update rust crate libfuzzer-sys to 0.4 (#5) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- fuzz/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index c1bebc9..650e1d4 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -9,7 +9,7 @@ edition = "2018" cargo-fuzz = true [dependencies] -libfuzzer-sys = "0.3" +libfuzzer-sys = "0.4" diffutils = { path = "../" } # Prevent this from interfering with workspaces From 1241db48069906973e6ef1c200d3eb1199fc3fe9 Mon Sep 17 00:00:00 2001 From: Olivier Tilloy Date: Wed, 21 Feb 2024 22:43:17 +0100 Subject: [PATCH 11/61] Match GNU diff's implementation for exit codes (fixes #17) --- src/main.rs | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/src/main.rs b/src/main.rs index 8a3e74e..a684ff4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,6 +8,7 @@ use std::env; use std::fs; use std::io::{self, Write}; +use std::process::{exit, ExitCode}; mod context_diff; mod ed_diff; @@ -15,25 +16,35 @@ mod normal_diff; mod params; mod unified_diff; -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)?; + } = parse_params(opts).unwrap_or_else(|error| { + eprintln!("{error}"); + exit(2); + }); // 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}")); + eprintln!("Failed to read from-file: {e}"); + return ExitCode::from(2); } }; let to_content = match fs::read(&to) { Ok(to_content) => to_content, Err(e) => { - return Err(format!("Failed to read to-file: {e}")); + eprintln!("Failed to read to-file: {e}"); + return ExitCode::from(2); } }; // run diff @@ -53,8 +64,15 @@ fn main() -> Result<(), String> { &to.to_string_lossy(), context_count, ), - Format::Ed => ed_diff::diff(&from_content, &to_content)?, + Format::Ed => ed_diff::diff(&from_content, &to_content).unwrap_or_else(|error| { + eprintln!("{error}"); + exit(2); + }), }; io::stdout().write_all(&result).unwrap(); - Ok(()) + if result.is_empty() { + ExitCode::SUCCESS + } else { + ExitCode::from(1) + } } From 0a67bf9fb827853596bc1ec54d4822763e119170 Mon Sep 17 00:00:00 2001 From: Olivier Tilloy Date: Thu, 22 Feb 2024 17:15:03 +0100 Subject: [PATCH 12/61] Add integration tests that check the exit codes and stdout/stderr --- Cargo.lock | 352 +++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 3 + tests/integration.rs | 98 ++++++++++++ 3 files changed, 453 insertions(+) create mode 100644 tests/integration.rs diff --git a/Cargo.lock b/Cargo.lock index 2ae28d9..cf8f04f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,18 +2,180 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstyle" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" + +[[package]] +name = "assert_cmd" +version = "2.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed72493ac66d5804837f480ab3766c72bdfab91a65e565fc54fa9e42db0073a8" +dependencies = [ + "anstyle", + "bstr", + "doc-comment", + "predicates", + "predicates-core", + "predicates-tree", + "wait-timeout", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" + +[[package]] +name = "bstr" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c48f0051a4b4c5e0b6d365cd04af53aeaa209e3cc15ec2cdb69e73cc87fbd0dc" +dependencies = [ + "memchr", + "regex-automata", + "serde", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + [[package]] name = "diff" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" +[[package]] +name = "difflib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" + [[package]] name = "diffutils" version = "0.3.0" dependencies = [ + "assert_cmd", "diff", + "predicates", "pretty_assertions", + "tempfile", +] + +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +dependencies = [ + "num-traits", +] + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "linux-raw-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "normalize-line-endings" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" + +[[package]] +name = "num-traits" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "predicates" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b87bfd4605926cdfefc1c3b5f8fe560e3feca9d5552cf68c466d3d8236c7e8" +dependencies = [ + "anstyle", + "difflib", + "float-cmp", + "normalize-line-endings", + "predicates-core", + "regex", +] + +[[package]] +name = "predicates-core" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174" + +[[package]] +name = "predicates-tree" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf" +dependencies = [ + "predicates-core", + "termtree", ] [[package]] @@ -26,6 +188,196 @@ dependencies = [ "yansi", ] +[[package]] +name = "proc-macro2" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "rustix" +version = "0.38.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "serde" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "2.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74f1bdc9872430ce9b75da68329d1c1746faf50ffac5f19e02b71e37ff881ffb" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a365e8cd18e44762ef95d87f284f4b5cd04107fec2ff3052bd6a3e6069669e67" +dependencies = [ + "cfg-if", + "fastrand", + "rustix", + "windows-sys", +] + +[[package]] +name = "termtree" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98532992affa02e52709d5b4d145a3668ae10d9081eea4a7f26f719a8476f71" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7269c1442e75af9fa59290383f7665b828efc76c429cc0b7f2ecb33cf51ebae" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f70ab2cebf332b7ecbdd98900c2da5298a8c862472fb35c75fc297eabb9d89b8" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "679f235acf6b1639408c0f6db295697a19d103b0cdc88146aa1b992c580c647d" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3480ac194b55ae274a7e135c21645656825da4a7f5b6e9286291b2113c94a78b" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42c46bab241c121402d1cb47d028ea3680ee2f359dcc287482dcf7fdddc73363" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc885a4332ee1afb9a1bacf11514801011725570d35675abc229ce7e3afe4d20" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e440c60457f84b0bee09208e62acc7ade264b38c4453f6312b8c9ab1613e73c" + [[package]] name = "yansi" version = "0.5.1" diff --git a/Cargo.toml b/Cargo.toml index 984e4d9..f1c7b71 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,3 +19,6 @@ diff = "0.1.10" [dev-dependencies] pretty_assertions = "1" +assert_cmd = "2.0.14" +predicates = "3.1.0" +tempfile = "3.10.0" diff --git a/tests/integration.rs b/tests/integration.rs new file mode 100644 index 0000000..f5768d0 --- /dev/null +++ b/tests/integration.rs @@ -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 assert_cmd::prelude::*; +use predicates::prelude::*; +use std::io::Write; +use std::process::Command; +use tempfile::NamedTempFile; + +// Integration tests for the diffutils command + +#[test] +fn unknown_param() -> Result<(), Box> { + let mut cmd = Command::cargo_bin("diffutils")?; + cmd.arg("--foobar"); + cmd.assert() + .code(predicate::eq(2)) + .failure() + .stderr(predicate::str::starts_with("Usage: ")); + Ok(()) +} + +#[test] +fn cannot_read_from_file() -> Result<(), Box> { + let mut cmd = Command::cargo_bin("diffutils")?; + cmd.arg("foo.txt").arg("bar.txt"); + cmd.assert() + .code(predicate::eq(2)) + .failure() + .stderr(predicate::str::starts_with("Failed to read from-file")); + Ok(()) +} + +#[test] +fn cannot_read_to_file() -> Result<(), Box> { + let file = NamedTempFile::new()?; + let mut cmd = Command::cargo_bin("diffutils")?; + cmd.arg(file.path()).arg("bar.txt"); + cmd.assert() + .code(predicate::eq(2)) + .failure() + .stderr(predicate::str::starts_with("Failed to read to-file")); + Ok(()) +} + +#[test] +fn no_differences() -> Result<(), Box> { + let file = NamedTempFile::new()?; + for option in ["", "-u", "-c", "-e"] { + let mut cmd = Command::cargo_bin("diffutils")?; + if !option.is_empty() { + cmd.arg(option); + } + cmd.arg(file.path()).arg(file.path()); + cmd.assert() + .code(predicate::eq(0)) + .success() + .stdout(predicate::str::is_empty()); + } + Ok(()) +} + +#[test] +fn differences() -> Result<(), Box> { + let mut file1 = NamedTempFile::new()?; + file1.write_all("foo\n".as_bytes())?; + let mut file2 = NamedTempFile::new()?; + file2.write_all("bar\n".as_bytes())?; + for option in ["", "-u", "-c", "-e"] { + let mut cmd = Command::cargo_bin("diffutils")?; + if !option.is_empty() { + cmd.arg(option); + } + cmd.arg(file1.path()).arg(file2.path()); + cmd.assert() + .code(predicate::eq(1)) + .failure() + .stdout(predicate::str::is_empty().not()); + } + Ok(()) +} + +#[test] +fn missing_newline() -> Result<(), Box> { + let mut file1 = NamedTempFile::new()?; + file1.write_all("foo".as_bytes())?; + let mut file2 = NamedTempFile::new()?; + file2.write_all("bar".as_bytes())?; + let mut cmd = Command::cargo_bin("diffutils")?; + cmd.arg("-e").arg(file1.path()).arg(file2.path()); + cmd.assert() + .code(predicate::eq(2)) + .failure() + .stderr(predicate::str::starts_with("No newline at end of file")); + Ok(()) +} From a89f30afa0cdd597806ee0cb1c7f461f96c08c43 Mon Sep 17 00:00:00 2001 From: Olivier Tilloy Date: Fri, 23 Feb 2024 22:52:54 +0100 Subject: [PATCH 13/61] Ed diff: compact ranges of lines where possible (fixes #21) --- src/ed_diff.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/ed_diff.rs b/src/ed_diff.rs index 2f8c0dd..41a968a 100644 --- a/src/ed_diff.rs +++ b/src/ed_diff.rs @@ -122,6 +122,7 @@ pub fn diff(expected: &[u8], actual: &[u8]) -> Result, DiffError> { expected_count + line_number_expected - 1 ) .unwrap(), + (1, _) => writeln!(&mut output, "{}c", line_number_expected).unwrap(), _ => writeln!( &mut output, "{},{}c", @@ -156,6 +157,15 @@ mod tests { Ok(output) } + #[test] + fn test_basic() { + let from = b"a\n"; + let to = b"b\n"; + let diff = diff(from, to).unwrap(); + let expected = vec!["1c", "b", ".", ""].join("\n"); + assert_eq!(diff, expected.as_bytes()); + } + #[test] fn test_permutations() { let target = "target/ed-diff/"; From c68d3861700b5059518f86aa58c75906a0159fee Mon Sep 17 00:00:00 2001 From: Olivier Tilloy Date: Fri, 23 Feb 2024 23:45:38 +0100 Subject: [PATCH 14/61] Implement -s/--report-identical-files option (fixes #23) --- Cargo.lock | 41 ++++++++++++++++++++++++++++++++ Cargo.toml | 1 + src/main.rs | 16 +++++++++++++ src/params.rs | 56 ++++++++++++++++++++++++++++++++++++++++++++ tests/integration.rs | 41 ++++++++++++++++++++++++++++++++ 5 files changed, 155 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index cf8f04f..92ad218 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -81,6 +81,7 @@ dependencies = [ "diff", "predicates", "pretty_assertions", + "same-file", "tempfile", ] @@ -248,6 +249,15 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "serde" version = "1.0.197" @@ -312,6 +322,37 @@ dependencies = [ "libc", ] +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-sys" version = "0.52.0" diff --git a/Cargo.toml b/Cargo.toml index f1c7b71..3804a81 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ path = "src/main.rs" [dependencies] diff = "0.1.10" +same-file = "1.0.6" [dev-dependencies] pretty_assertions = "1" diff --git a/src/main.rs b/src/main.rs index a684ff4..ef14096 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,10 +28,25 @@ fn main() -> ExitCode { to, context_count, format, + report_identical_files, } = 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 report_identical_files { + println!( + "Files {} and {} are identical", + from.to_string_lossy(), + to.to_string_lossy(), + ) + } + }; + if same_file::is_same_file(&from, &to).unwrap_or(false) { + maybe_report_identical_files(); + return ExitCode::SUCCESS; + } // read files let from_content = match fs::read(&from) { Ok(from_content) => from_content, @@ -71,6 +86,7 @@ fn main() -> ExitCode { }; io::stdout().write_all(&result).unwrap(); if result.is_empty() { + maybe_report_identical_files(); ExitCode::SUCCESS } else { ExitCode::from(1) diff --git a/src/params.rs b/src/params.rs index b118be7..66d3d56 100644 --- a/src/params.rs +++ b/src/params.rs @@ -25,6 +25,7 @@ pub struct Params { pub to: OsString, pub format: Format, pub context_count: usize, + pub report_identical_files: bool, } pub fn parse_params>(opts: I) -> Result { @@ -38,6 +39,7 @@ pub fn parse_params>(opts: I) -> Result>(opts: I) -> Result>(opts: I) -> Result Result<(), Box> { Ok(()) } +#[test] +fn no_differences_report_identical_files() -> Result<(), Box> { + // same file + let mut file1 = NamedTempFile::new()?; + file1.write_all("foo\n".as_bytes())?; + for option in ["", "-u", "-c", "-e"] { + let mut cmd = Command::cargo_bin("diffutils")?; + if !option.is_empty() { + cmd.arg(option); + } + cmd.arg("-s").arg(file1.path()).arg(file1.path()); + cmd.assert() + .code(predicate::eq(0)) + .success() + .stdout(predicate::eq(format!( + "Files {} and {} are identical\n", + file1.path().to_string_lossy(), + file1.path().to_string_lossy(), + ))); + } + // two files with the same content + let mut file2 = NamedTempFile::new()?; + file2.write_all("foo\n".as_bytes())?; + for option in ["", "-u", "-c", "-e"] { + let mut cmd = Command::cargo_bin("diffutils")?; + if !option.is_empty() { + cmd.arg(option); + } + cmd.arg("-s").arg(file1.path()).arg(file2.path()); + cmd.assert() + .code(predicate::eq(0)) + .success() + .stdout(predicate::eq(format!( + "Files {} and {} are identical\n", + file1.path().to_string_lossy(), + file2.path().to_string_lossy(), + ))); + } + Ok(()) +} + #[test] fn differences() -> Result<(), Box> { let mut file1 = NamedTempFile::new()?; From 62e10c6d6ca7984e4f10ecca2ea1ed06cc7f0324 Mon Sep 17 00:00:00 2001 From: hanbings Date: Tue, 19 Mar 2024 04:50:33 +0800 Subject: [PATCH 15/61] ci: fuzzers in the Github Actions CI. (#29) * Add fuzzing CI. * fuzz dependency change: diffutils -> diffutilslib * fix fuzz build. --- .github/workflows/fuzzing.yml | 72 ++++++++++++++++++++++++++++++++ fuzz/fuzz_targets/fuzz_ed.rs | 11 ++++- fuzz/fuzz_targets/fuzz_normal.rs | 2 +- fuzz/fuzz_targets/fuzz_patch.rs | 2 +- 4 files changed, 83 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/fuzzing.yml diff --git a/.github/workflows/fuzzing.yml b/.github/workflows/fuzzing.yml new file mode 100644 index 0000000..589b952 --- /dev/null +++ b/.github/workflows/fuzzing.yml @@ -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 }} diff --git a/fuzz/fuzz_targets/fuzz_ed.rs b/fuzz/fuzz_targets/fuzz_ed.rs index e46908d..5c5132e 100644 --- a/fuzz/fuzz_targets/fuzz_ed.rs +++ b/fuzz/fuzz_targets/fuzz_ed.rs @@ -1,11 +1,18 @@ #![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 std::fs::{self, File}; use std::io::Write; use std::process::Command; +fn diff_w(expected: &[u8], actual: &[u8], filename: &str) -> Result, DiffError> { + let mut output = ed_diff::diff(expected, actual)?; + writeln!(&mut output, "w {filename}").unwrap(); + Ok(output) +} + fuzz_target!(|x: (Vec, Vec)| { let (mut from, mut to) = x; from.push(b'\n'); @@ -30,7 +37,7 @@ fuzz_target!(|x: (Vec, Vec)| { } 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) diff --git a/fuzz/fuzz_targets/fuzz_normal.rs b/fuzz/fuzz_targets/fuzz_normal.rs index 4e114d2..a44ece3 100644 --- a/fuzz/fuzz_targets/fuzz_normal.rs +++ b/fuzz/fuzz_targets/fuzz_normal.rs @@ -1,7 +1,7 @@ #![no_main] #[macro_use] extern crate libfuzzer_sys; -use diffutils::{normal_diff, unified_diff}; +use diffutilslib::normal_diff; use std::fs::{self, File}; use std::io::Write; diff --git a/fuzz/fuzz_targets/fuzz_patch.rs b/fuzz/fuzz_targets/fuzz_patch.rs index c190d76..d353523 100644 --- a/fuzz/fuzz_targets/fuzz_patch.rs +++ b/fuzz/fuzz_targets/fuzz_patch.rs @@ -1,7 +1,7 @@ #![no_main] #[macro_use] extern crate libfuzzer_sys; -use diffutils::{normal_diff, unified_diff}; +use diffutilslib::unified_diff; use std::fs::{self, File}; use std::io::Write; use std::process::Command; From 4ed7ea1553c927845e39831a111bf76c081f0589 Mon Sep 17 00:00:00 2001 From: Olivier Tilloy Date: Tue, 19 Mar 2024 11:45:06 +0100 Subject: [PATCH 16/61] 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 --- src/context_diff.rs | 124 +++++++++++++++++++++++++++++++++++++---- src/ed_diff.rs | 38 +++++++++++-- src/main.rs | 17 +++++- src/normal_diff.rs | 44 ++++++++++++--- src/params.rs | 57 +++++++++++++++++++ src/unified_diff.rs | 130 ++++++++++++++++++++++++++++++++++++++----- tests/integration.rs | 24 ++++++++ 7 files changed, 394 insertions(+), 40 deletions(-) diff --git a/src/context_diff.rs b/src/context_diff.rs index 1c9d44f..abc0e9e 100644 --- a/src/context_diff.rs +++ b/src/context_diff.rs @@ -41,7 +41,12 @@ impl Mismatch { } // Produces a diff between the expected output and actual output. -fn make_diff(expected: &[u8], actual: &[u8], context_size: usize) -> Vec { +fn make_diff( + expected: &[u8], + actual: &[u8], + context_size: usize, + stop_early: bool, +) -> Vec { 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 +196,10 @@ fn make_diff(expected: &[u8], actual: &[u8], context_size: usize) -> Vec Vec { let mut output = format!("*** {expected_filename}\t\n--- {actual_filename}\t\n").into_bytes(); - let diff_results = make_diff(expected, actual, context_size); + let diff_results = make_diff(expected, actual, context_size, stop_early); if diff_results.is_empty() { return Vec::new(); - }; + } + if stop_early { + 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; @@ -404,8 +417,14 @@ 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, + "a/alef", + &bet, + &format!("{target}/alef"), + 2, + false, + ); File::create(&format!("{target}/ab.diff")) .unwrap() .write_all(&diff) @@ -477,8 +496,14 @@ 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, + "a/alef_", + &bet, + &format!("{target}/alef_"), + 2, + false, + ); File::create(&format!("{target}/ab_.diff")) .unwrap() .write_all(&diff) @@ -553,8 +578,14 @@ 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, + "a/alefx", + &bet, + &format!("{target}/alefx"), + 2, + false, + ); File::create(&format!("{target}/abx.diff")) .unwrap() .write_all(&diff) @@ -632,8 +663,14 @@ 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, + "a/alefr", + &bet, + &format!("{target}/alefr"), + 2, + false, + ); File::create(&format!("{target}/abr.diff")) .unwrap() .write_all(&diff) @@ -662,4 +699,69 @@ mod tests { } } } + + #[test] + fn test_stop_early() { + let from_filename = "foo"; + let from = vec!["a", "b", "c", ""].join("\n"); + let to_filename = "bar"; + let to = vec!["a", "d", "c", ""].join("\n"); + let context_size: usize = 3; + + let diff_full = diff( + from.as_bytes(), + from_filename, + to.as_bytes(), + to_filename, + context_size, + false, + ); + let expected_full = vec![ + "*** foo\t", + "--- bar\t", + "***************", + "*** 1,3 ****", + " a", + "! b", + " c", + "--- 1,3 ----", + " a", + "! d", + " c", + "", + ] + .join("\n"); + assert_eq!(diff_full, expected_full.as_bytes()); + + let diff_brief = diff( + from.as_bytes(), + from_filename, + to.as_bytes(), + to_filename, + context_size, + true, + ); + let expected_brief = vec!["*** foo\t", "--- bar\t", ""].join("\n"); + assert_eq!(diff_brief, expected_brief.as_bytes()); + + let nodiff_full = diff( + from.as_bytes(), + from_filename, + from.as_bytes(), + to_filename, + context_size, + false, + ); + assert!(nodiff_full.is_empty()); + + let nodiff_brief = diff( + from.as_bytes(), + from_filename, + from.as_bytes(), + to_filename, + context_size, + true, + ); + assert!(nodiff_brief.is_empty()); + } } diff --git a/src/ed_diff.rs b/src/ed_diff.rs index 41a968a..eec1fb4 100644 --- a/src/ed_diff.rs +++ b/src/ed_diff.rs @@ -42,7 +42,7 @@ impl Mismatch { } // Produces a diff between the expected output and actual output. -fn make_diff(expected: &[u8], actual: &[u8]) -> Result, DiffError> { +fn make_diff(expected: &[u8], actual: &[u8], stop_early: bool) -> Result, DiffError> { let mut line_number_expected = 1; let mut line_number_actual = 1; let mut results = Vec::new(); @@ -94,6 +94,10 @@ fn make_diff(expected: &[u8], actual: &[u8]) -> Result, 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 +107,13 @@ fn make_diff(expected: &[u8], actual: &[u8]) -> Result, DiffError> Ok(results) } -pub fn diff(expected: &[u8], actual: &[u8]) -> Result, DiffError> { +pub fn diff(expected: &[u8], actual: &[u8], stop_early: bool) -> Result, DiffError> { let mut output = Vec::new(); - let diff_results = make_diff(expected, actual)?; + let diff_results = make_diff(expected, actual, stop_early)?; + if stop_early && !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; @@ -152,7 +160,7 @@ mod tests { use super::*; use pretty_assertions::assert_eq; pub fn diff_w(expected: &[u8], actual: &[u8], filename: &str) -> Result, DiffError> { - let mut output = diff(expected, actual)?; + let mut output = diff(expected, actual, false)?; writeln!(&mut output, "w {filename}").unwrap(); Ok(output) } @@ -161,7 +169,7 @@ mod tests { fn test_basic() { let from = b"a\n"; let to = b"b\n"; - let diff = diff(from, to).unwrap(); + let diff = diff(from, to, false).unwrap(); let expected = vec!["1c", "b", ".", ""].join("\n"); assert_eq!(diff, expected.as_bytes()); } @@ -390,4 +398,24 @@ mod tests { } } } + + #[test] + fn test_stop_early() { + let from = vec!["a", "b", "c", ""].join("\n"); + let to = vec!["a", "d", "c", ""].join("\n"); + + let diff_full = diff(from.as_bytes(), to.as_bytes(), false).unwrap(); + let expected_full = vec!["2c", "d", ".", ""].join("\n"); + assert_eq!(diff_full, expected_full.as_bytes()); + + let diff_brief = diff(from.as_bytes(), to.as_bytes(), true).unwrap(); + let expected_brief = "\0".as_bytes(); + assert_eq!(diff_brief, expected_brief); + + let nodiff_full = diff(from.as_bytes(), from.as_bytes(), false).unwrap(); + assert!(nodiff_full.is_empty()); + + let nodiff_brief = diff(from.as_bytes(), from.as_bytes(), true).unwrap(); + assert!(nodiff_brief.is_empty()); + } } diff --git a/src/main.rs b/src/main.rs index ef14096..6ff2a0f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -29,6 +29,7 @@ fn main() -> ExitCode { context_count, format, report_identical_files, + brief, } = parse_params(opts).unwrap_or_else(|error| { eprintln!("{error}"); exit(2); @@ -64,13 +65,14 @@ fn main() -> ExitCode { }; // run diff let result: Vec = match format { - Format::Normal => normal_diff::diff(&from_content, &to_content), + Format::Normal => normal_diff::diff(&from_content, &to_content, brief), Format::Unified => unified_diff::diff( &from_content, &from.to_string_lossy(), &to_content, &to.to_string_lossy(), context_count, + brief, ), Format::Context => context_diff::diff( &from_content, @@ -78,13 +80,22 @@ fn main() -> ExitCode { &to_content, &to.to_string_lossy(), context_count, + brief, ), - Format::Ed => ed_diff::diff(&from_content, &to_content).unwrap_or_else(|error| { + Format::Ed => ed_diff::diff(&from_content, &to_content, brief).unwrap_or_else(|error| { eprintln!("{error}"); exit(2); }), }; - io::stdout().write_all(&result).unwrap(); + if brief && !result.is_empty() { + println!( + "Files {} and {} differ", + from.to_string_lossy(), + to.to_string_lossy() + ); + } else { + io::stdout().write_all(&result).unwrap(); + } if result.is_empty() { maybe_report_identical_files(); ExitCode::SUCCESS diff --git a/src/normal_diff.rs b/src/normal_diff.rs index fa9c059..e324ada 100644 --- a/src/normal_diff.rs +++ b/src/normal_diff.rs @@ -29,7 +29,7 @@ impl Mismatch { } // Produces a diff between the expected output and actual output. -fn make_diff(expected: &[u8], actual: &[u8]) -> Vec { +fn make_diff(expected: &[u8], actual: &[u8], stop_early: bool) -> Vec { let mut line_number_expected = 1; let mut line_number_actual = 1; let mut results = Vec::new(); @@ -100,6 +100,10 @@ fn make_diff(expected: &[u8], actual: &[u8]) -> Vec { } } } + 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,11 +114,15 @@ fn make_diff(expected: &[u8], actual: &[u8]) -> Vec { } #[must_use] -pub fn diff(expected: &[u8], actual: &[u8]) -> Vec { +pub fn diff(expected: &[u8], actual: &[u8], stop_early: bool) -> Vec { // 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, stop_early); + if stop_early && !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; @@ -212,7 +220,7 @@ mod tests { a.write_all(b"a\n").unwrap(); let mut b = Vec::new(); b.write_all(b"b\n").unwrap(); - let diff = diff(&a, &b); + let diff = diff(&a, &b, false); let expected = b"1c1\n< a\n---\n> b\n".to_vec(); assert_eq!(diff, expected); } @@ -265,7 +273,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, false); File::create(&format!("{target}/ab.diff")) .unwrap() .write_all(&diff) @@ -357,7 +365,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, false); File::create(&format!("{target}/abn.diff")) .unwrap() .write_all(&diff) @@ -431,7 +439,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, false); File::create(&format!("{target}/ab_.diff")) .unwrap() .write_all(&diff) @@ -509,7 +517,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, false); File::create(&format!("{target}/abr.diff")) .unwrap() .write_all(&diff) @@ -538,4 +546,24 @@ mod tests { } } } + + #[test] + fn test_stop_early() { + let from = vec!["a", "b", "c"].join("\n"); + let to = vec!["a", "d", "c"].join("\n"); + + let diff_full = diff(from.as_bytes(), to.as_bytes(), false); + let expected_full = vec!["2c2", "< b", "---", "> d", ""].join("\n"); + assert_eq!(diff_full, expected_full.as_bytes()); + + let diff_brief = diff(from.as_bytes(), to.as_bytes(), true); + let expected_brief = "\0".as_bytes(); + assert_eq!(diff_brief, expected_brief); + + let nodiff_full = diff(from.as_bytes(), from.as_bytes(), false); + assert!(nodiff_full.is_empty()); + + let nodiff_brief = diff(from.as_bytes(), from.as_bytes(), true); + assert!(nodiff_brief.is_empty()); + } } diff --git a/src/params.rs b/src/params.rs index 66d3d56..661cd37 100644 --- a/src/params.rs +++ b/src/params.rs @@ -26,6 +26,7 @@ pub struct Params { pub format: Format, pub context_count: usize, pub report_identical_files: bool, + pub brief: bool, } pub fn parse_params>(opts: I) -> Result { @@ -40,6 +41,7 @@ pub fn parse_params>(opts: I) -> Result>(opts: I) -> Result>(opts: I) -> Result Vec { +fn make_diff( + expected: &[u8], + actual: &[u8], + context_size: usize, + stop_early: bool, +) -> Vec { let mut line_number_expected = 1; let mut line_number_actual = 1; let mut context_queue: VecDeque<&[u8]> = VecDeque::with_capacity(context_size); @@ -180,6 +185,10 @@ fn make_diff(expected: &[u8], actual: &[u8], context_size: usize) -> Vec Vec { let mut output = format!("--- {expected_filename}\t\n+++ {actual_filename}\t\n").into_bytes(); - let diff_results = make_diff(expected, actual, context_size); + let diff_results = make_diff(expected, actual, context_size, stop_early); if diff_results.is_empty() { return Vec::new(); - }; + } + if stop_early { + 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; @@ -434,8 +447,14 @@ 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, + "a/alef", + &bet, + &format!("{target}/alef"), + 2, + false, + ); File::create(&format!("{target}/ab.diff")) .unwrap() .write_all(&diff) @@ -542,8 +561,14 @@ mod tests { } // This test diff is intentionally reversed. // We want it to turn the alef into bet. - let diff = - diff(&alef, "a/alefn", &bet, &format!("{target}/alefn"), 2); + let diff = diff( + &alef, + "a/alefn", + &bet, + &format!("{target}/alefn"), + 2, + false, + ); File::create(&format!("{target}/abn.diff")) .unwrap() .write_all(&diff) @@ -630,8 +655,14 @@ 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, + "a/alef_", + &bet, + &format!("{target}/alef_"), + 2, + false, + ); File::create(&format!("{target}/ab_.diff")) .unwrap() .write_all(&diff) @@ -703,8 +734,14 @@ 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, + "a/alefx", + &bet, + &format!("{target}/alefx"), + 2, + false, + ); File::create(&format!("{target}/abx.diff")) .unwrap() .write_all(&diff) @@ -781,8 +818,14 @@ 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, + "a/alefr", + &bet, + &format!("{target}/alefr"), + 2, + false, + ); File::create(&format!("{target}/abr.diff")) .unwrap() .write_all(&diff) @@ -810,4 +853,65 @@ mod tests { } } } + + #[test] + fn test_stop_early() { + let from_filename = "foo"; + let from = vec!["a", "b", "c", ""].join("\n"); + let to_filename = "bar"; + let to = vec!["a", "d", "c", ""].join("\n"); + let context_size: usize = 3; + + let diff_full = diff( + from.as_bytes(), + from_filename, + to.as_bytes(), + to_filename, + context_size, + false, + ); + let expected_full = vec![ + "--- foo\t", + "+++ bar\t", + "@@ -1,3 +1,3 @@", + " a", + "-b", + "+d", + " c", + "", + ] + .join("\n"); + assert_eq!(diff_full, expected_full.as_bytes()); + + let diff_brief = diff( + from.as_bytes(), + from_filename, + to.as_bytes(), + to_filename, + context_size, + true, + ); + let expected_brief = vec!["--- foo\t", "+++ bar\t", ""].join("\n"); + assert_eq!(diff_brief, expected_brief.as_bytes()); + + let nodiff_full = diff( + from.as_bytes(), + from_filename, + from.as_bytes(), + to_filename, + context_size, + false, + ); + assert!(nodiff_full.is_empty()); + + let nodiff_brief = diff( + from.as_bytes(), + from_filename, + from.as_bytes(), + to_filename, + context_size, + true, + ); + assert!(nodiff_brief.is_empty()); + } } diff --git a/tests/integration.rs b/tests/integration.rs index 50bf559..be1320e 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -123,6 +123,30 @@ fn differences() -> Result<(), Box> { Ok(()) } +#[test] +fn differences_brief() -> Result<(), Box> { + let mut file1 = NamedTempFile::new()?; + file1.write_all("foo\n".as_bytes())?; + let mut file2 = NamedTempFile::new()?; + file2.write_all("bar\n".as_bytes())?; + for option in ["", "-u", "-c", "-e"] { + let mut cmd = Command::cargo_bin("diffutils")?; + if !option.is_empty() { + cmd.arg(option); + } + cmd.arg("-q").arg(file1.path()).arg(file2.path()); + cmd.assert() + .code(predicate::eq(1)) + .failure() + .stdout(predicate::eq(format!( + "Files {} and {} differ\n", + file1.path().to_string_lossy(), + file2.path().to_string_lossy() + ))); + } + Ok(()) +} + #[test] fn missing_newline() -> Result<(), Box> { let mut file1 = NamedTempFile::new()?; From f916f1ce86bed29228e0bd02176516651a6003eb Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Sun, 24 Mar 2024 14:05:44 +0100 Subject: [PATCH 17/61] clippy: fix warnings from useless_vec lint --- src/context_diff.rs | 8 ++++---- src/ed_diff.rs | 8 ++++---- src/normal_diff.rs | 6 +++--- src/unified_diff.rs | 8 ++++---- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/context_diff.rs b/src/context_diff.rs index abc0e9e..af262a3 100644 --- a/src/context_diff.rs +++ b/src/context_diff.rs @@ -703,9 +703,9 @@ mod tests { #[test] fn test_stop_early() { let from_filename = "foo"; - let from = vec!["a", "b", "c", ""].join("\n"); + let from = ["a", "b", "c", ""].join("\n"); let to_filename = "bar"; - let to = vec!["a", "d", "c", ""].join("\n"); + let to = ["a", "d", "c", ""].join("\n"); let context_size: usize = 3; let diff_full = diff( @@ -716,7 +716,7 @@ mod tests { context_size, false, ); - let expected_full = vec![ + let expected_full = [ "*** foo\t", "--- bar\t", "***************", @@ -741,7 +741,7 @@ mod tests { context_size, true, ); - let expected_brief = vec!["*** foo\t", "--- bar\t", ""].join("\n"); + let expected_brief = ["*** foo\t", "--- bar\t", ""].join("\n"); assert_eq!(diff_brief, expected_brief.as_bytes()); let nodiff_full = diff( diff --git a/src/ed_diff.rs b/src/ed_diff.rs index eec1fb4..7613b22 100644 --- a/src/ed_diff.rs +++ b/src/ed_diff.rs @@ -170,7 +170,7 @@ mod tests { let from = b"a\n"; let to = b"b\n"; let diff = diff(from, to, false).unwrap(); - let expected = vec!["1c", "b", ".", ""].join("\n"); + let expected = ["1c", "b", ".", ""].join("\n"); assert_eq!(diff, expected.as_bytes()); } @@ -401,11 +401,11 @@ mod tests { #[test] fn test_stop_early() { - let from = vec!["a", "b", "c", ""].join("\n"); - let to = vec!["a", "d", "c", ""].join("\n"); + let from = ["a", "b", "c", ""].join("\n"); + let to = ["a", "d", "c", ""].join("\n"); let diff_full = diff(from.as_bytes(), to.as_bytes(), false).unwrap(); - let expected_full = vec!["2c", "d", ".", ""].join("\n"); + 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(), true).unwrap(); diff --git a/src/normal_diff.rs b/src/normal_diff.rs index e324ada..aeef145 100644 --- a/src/normal_diff.rs +++ b/src/normal_diff.rs @@ -549,11 +549,11 @@ mod tests { #[test] fn test_stop_early() { - let from = vec!["a", "b", "c"].join("\n"); - let to = vec!["a", "d", "c"].join("\n"); + let from = ["a", "b", "c"].join("\n"); + let to = ["a", "d", "c"].join("\n"); let diff_full = diff(from.as_bytes(), to.as_bytes(), false); - let expected_full = vec!["2c2", "< b", "---", "> d", ""].join("\n"); + 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(), true); diff --git a/src/unified_diff.rs b/src/unified_diff.rs index becf721..f9c256e 100644 --- a/src/unified_diff.rs +++ b/src/unified_diff.rs @@ -857,9 +857,9 @@ mod tests { #[test] fn test_stop_early() { let from_filename = "foo"; - let from = vec!["a", "b", "c", ""].join("\n"); + let from = ["a", "b", "c", ""].join("\n"); let to_filename = "bar"; - let to = vec!["a", "d", "c", ""].join("\n"); + let to = ["a", "d", "c", ""].join("\n"); let context_size: usize = 3; let diff_full = diff( @@ -870,7 +870,7 @@ mod tests { context_size, false, ); - let expected_full = vec![ + let expected_full = [ "--- foo\t", "+++ bar\t", "@@ -1,3 +1,3 @@", @@ -891,7 +891,7 @@ mod tests { context_size, true, ); - let expected_brief = vec!["--- foo\t", "+++ bar\t", ""].join("\n"); + let expected_brief = ["--- foo\t", "+++ bar\t", ""].join("\n"); assert_eq!(diff_brief, expected_brief.as_bytes()); let nodiff_full = diff( From 42eb15b87ad39f97bf016e23631eab9550d559c7 Mon Sep 17 00:00:00 2001 From: Tanmay Patil Date: Wed, 27 Mar 2024 22:46:23 +0530 Subject: [PATCH 18/61] Display modification times of input files in context diff Fixes #31 --- Cargo.lock | 155 ++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + src/context_diff.rs | 20 +++++- 3 files changed, 175 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 92ad218..6235103 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anstyle" version = "1.0.6" @@ -55,12 +70,44 @@ dependencies = [ "serde", ] +[[package]] +name = "bumpalo" +version = "3.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" + +[[package]] +name = "cc" +version = "1.0.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + [[package]] name = "diff" version = "0.1.13" @@ -78,6 +125,7 @@ name = "diffutils" version = "0.3.0" dependencies = [ "assert_cmd", + "chrono", "diff", "predicates", "pretty_assertions", @@ -116,6 +164,38 @@ dependencies = [ "num-traits", ] +[[package]] +name = "iana-time-zone" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "js-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "libc" version = "0.2.153" @@ -128,6 +208,12 @@ version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + [[package]] name = "memchr" version = "2.7.1" @@ -149,6 +235,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + [[package]] name = "predicates" version = "3.1.0" @@ -322,6 +414,60 @@ dependencies = [ "libc", ] +[[package]] +name = "wasm-bindgen" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + [[package]] name = "winapi" version = "0.3.9" @@ -353,6 +499,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.52.0" diff --git a/Cargo.toml b/Cargo.toml index 3804a81..940e3d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ name = "diffutils" path = "src/main.rs" [dependencies] +chrono = "0.4.35" diff = "0.1.10" same-file = "1.0.6" diff --git a/src/context_diff.rs b/src/context_diff.rs index abc0e9e..fa09f7c 100644 --- a/src/context_diff.rs +++ b/src/context_diff.rs @@ -262,6 +262,22 @@ fn make_diff( results } +fn get_modification_time(file_path: &str) -> String { + use chrono::{DateTime, Local}; + use std::fs; + + let metadata = fs::metadata(file_path).expect("Failed to get metadata"); + let modification_time = metadata + .modified() + .expect("Failed to get modification time"); + let modification_time: DateTime = modification_time.into(); + let modification_time: String = modification_time + .format("%Y-%m-%d %H:%M:%S%.9f %z") + .to_string(); + + modification_time +} + #[must_use] pub fn diff( expected: &[u8], @@ -271,7 +287,9 @@ pub fn diff( context_size: usize, stop_early: bool, ) -> Vec { - let mut output = format!("*** {expected_filename}\t\n--- {actual_filename}\t\n").into_bytes(); + let expected_file_modified_time = get_modification_time(expected_filename); + let actual_file_modified_time = get_modification_time(actual_filename); + let mut output = format!("*** {expected_filename}\t{expected_file_modified_time}\n--- {actual_filename}\t{actual_file_modified_time}\n").into_bytes(); let diff_results = make_diff(expected, actual, context_size, stop_early); if diff_results.is_empty() { return Vec::new(); From 8d65c2baddac1226cc6d172acf3998b24d47e722 Mon Sep 17 00:00:00 2001 From: Olivier Tilloy Date: Mon, 4 Mar 2024 21:41:27 +0100 Subject: [PATCH 19/61] Implement -t/--expand-tabs option --- Cargo.lock | 7 +++ Cargo.toml | 1 + src/context_diff.rs | 29 +++++++++--- src/ed_diff.rs | 23 +++++---- src/lib.rs | 1 + src/main.rs | 16 +++++-- src/normal_diff.rs | 26 +++++----- src/params.rs | 53 +++++++++++++++++++++ src/unified_diff.rs | 21 +++++++-- src/utils.rs | 112 ++++++++++++++++++++++++++++++++++++++++++++ 10 files changed, 255 insertions(+), 34 deletions(-) create mode 100644 src/utils.rs diff --git a/Cargo.lock b/Cargo.lock index 92ad218..9dc8153 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -83,6 +83,7 @@ dependencies = [ "pretty_assertions", "same-file", "tempfile", + "unicode-width", ] [[package]] @@ -313,6 +314,12 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-width" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" + [[package]] name = "wait-timeout" version = "0.2.0" diff --git a/Cargo.toml b/Cargo.toml index 3804a81..7eeb35b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ path = "src/main.rs" [dependencies] diff = "0.1.10" same-file = "1.0.6" +unicode-width = "0.1.11" [dev-dependencies] pretty_assertions = "1" diff --git a/src/context_diff.rs b/src/context_diff.rs index af262a3..408821f 100644 --- a/src/context_diff.rs +++ b/src/context_diff.rs @@ -6,6 +6,8 @@ use std::collections::VecDeque; use std::io::Write; +use crate::utils::do_write_line; + #[derive(Debug, PartialEq)] pub enum DiffLine { Context(Vec), @@ -270,6 +272,7 @@ pub fn diff( actual_filename: &str, context_size: usize, stop_early: bool, + expand_tabs: bool, ) -> Vec { let mut output = format!("*** {expected_filename}\t\n--- {actual_filename}\t\n").into_bytes(); let diff_results = make_diff(expected, actual, context_size, stop_early); @@ -314,17 +317,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, expand_tabs) + .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, expand_tabs) + .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, expand_tabs) + .expect("write to Vec is infallible"); writeln!(output).unwrap(); } } @@ -341,17 +347,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, expand_tabs) + .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, expand_tabs) + .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, expand_tabs) + .expect("write to Vec is infallible"); writeln!(output).unwrap(); } } @@ -424,6 +433,7 @@ mod tests { &format!("{target}/alef"), 2, false, + false, ); File::create(&format!("{target}/ab.diff")) .unwrap() @@ -503,6 +513,7 @@ mod tests { &format!("{target}/alef_"), 2, false, + false, ); File::create(&format!("{target}/ab_.diff")) .unwrap() @@ -585,6 +596,7 @@ mod tests { &format!("{target}/alefx"), 2, false, + false, ); File::create(&format!("{target}/abx.diff")) .unwrap() @@ -670,6 +682,7 @@ mod tests { &format!("{target}/alefr"), 2, false, + false, ); File::create(&format!("{target}/abr.diff")) .unwrap() @@ -715,6 +728,7 @@ mod tests { to_filename, context_size, false, + false, ); let expected_full = [ "*** foo\t", @@ -740,6 +754,7 @@ mod tests { to_filename, context_size, true, + false, ); let expected_brief = ["*** foo\t", "--- bar\t", ""].join("\n"); assert_eq!(diff_brief, expected_brief.as_bytes()); @@ -751,6 +766,7 @@ mod tests { to_filename, context_size, false, + false, ); assert!(nodiff_full.is_empty()); @@ -761,6 +777,7 @@ mod tests { to_filename, context_size, true, + false, ); assert!(nodiff_brief.is_empty()); } diff --git a/src/ed_diff.rs b/src/ed_diff.rs index 7613b22..6d47b9f 100644 --- a/src/ed_diff.rs +++ b/src/ed_diff.rs @@ -5,6 +5,8 @@ use std::io::Write; +use crate::utils::do_write_line; + #[derive(Debug, PartialEq)] struct Mismatch { pub line_number_expected: usize, @@ -107,7 +109,12 @@ fn make_diff(expected: &[u8], actual: &[u8], stop_early: bool) -> Result Result, DiffError> { +pub fn diff( + expected: &[u8], + actual: &[u8], + stop_early: bool, + expand_tabs: bool, +) -> Result, DiffError> { let mut output = Vec::new(); let diff_results = make_diff(expected, actual, stop_early)?; if stop_early && !diff_results.is_empty() { @@ -145,7 +152,7 @@ pub fn diff(expected: &[u8], actual: &[u8], stop_early: bool) -> Result, if actual == b"." { writeln!(&mut output, "..\n.\ns/.//\na").unwrap(); } else { - output.write_all(actual).unwrap(); + do_write_line(&mut output, actual, expand_tabs).unwrap(); writeln!(&mut output).unwrap(); } } @@ -160,7 +167,7 @@ mod tests { use super::*; use pretty_assertions::assert_eq; pub fn diff_w(expected: &[u8], actual: &[u8], filename: &str) -> Result, DiffError> { - let mut output = diff(expected, actual, false)?; + let mut output = diff(expected, actual, false, false)?; writeln!(&mut output, "w {filename}").unwrap(); Ok(output) } @@ -169,7 +176,7 @@ mod tests { fn test_basic() { let from = b"a\n"; let to = b"b\n"; - let diff = diff(from, to, false).unwrap(); + let diff = diff(from, to, false, false).unwrap(); let expected = ["1c", "b", ".", ""].join("\n"); assert_eq!(diff, expected.as_bytes()); } @@ -404,18 +411,18 @@ mod tests { let from = ["a", "b", "c", ""].join("\n"); let to = ["a", "d", "c", ""].join("\n"); - let diff_full = diff(from.as_bytes(), to.as_bytes(), false).unwrap(); + let diff_full = diff(from.as_bytes(), to.as_bytes(), false, false).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(), true).unwrap(); + let diff_brief = diff(from.as_bytes(), to.as_bytes(), true, false).unwrap(); let expected_brief = "\0".as_bytes(); assert_eq!(diff_brief, expected_brief); - let nodiff_full = diff(from.as_bytes(), from.as_bytes(), false).unwrap(); + let nodiff_full = diff(from.as_bytes(), from.as_bytes(), false, false).unwrap(); assert!(nodiff_full.is_empty()); - let nodiff_brief = diff(from.as_bytes(), from.as_bytes(), true).unwrap(); + let nodiff_brief = diff(from.as_bytes(), from.as_bytes(), true, false).unwrap(); assert!(nodiff_brief.is_empty()); } } diff --git a/src/lib.rs b/src/lib.rs index a78b64d..faf5df2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,6 +2,7 @@ pub mod context_diff; pub mod ed_diff; pub mod normal_diff; pub mod unified_diff; +pub mod utils; // Re-export the public functions/types you need pub use context_diff::diff as context_diff; diff --git a/src/main.rs b/src/main.rs index 6ff2a0f..f074cb1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,6 +15,7 @@ mod ed_diff; mod normal_diff; mod params; mod unified_diff; +mod utils; // Exit codes are documented at // https://www.gnu.org/software/diffutils/manual/html_node/Invoking-diff.html. @@ -30,6 +31,7 @@ fn main() -> ExitCode { format, report_identical_files, brief, + expand_tabs, } = parse_params(opts).unwrap_or_else(|error| { eprintln!("{error}"); exit(2); @@ -65,7 +67,7 @@ fn main() -> ExitCode { }; // run diff let result: Vec = match format { - Format::Normal => normal_diff::diff(&from_content, &to_content, brief), + Format::Normal => normal_diff::diff(&from_content, &to_content, brief, expand_tabs), Format::Unified => unified_diff::diff( &from_content, &from.to_string_lossy(), @@ -73,6 +75,7 @@ fn main() -> ExitCode { &to.to_string_lossy(), context_count, brief, + expand_tabs, ), Format::Context => context_diff::diff( &from_content, @@ -81,11 +84,14 @@ fn main() -> ExitCode { &to.to_string_lossy(), context_count, brief, + expand_tabs, ), - Format::Ed => ed_diff::diff(&from_content, &to_content, brief).unwrap_or_else(|error| { - eprintln!("{error}"); - exit(2); - }), + Format::Ed => { + ed_diff::diff(&from_content, &to_content, brief, expand_tabs).unwrap_or_else(|error| { + eprintln!("{error}"); + exit(2); + }) + } }; if brief && !result.is_empty() { println!( diff --git a/src/normal_diff.rs b/src/normal_diff.rs index aeef145..e25a6c6 100644 --- a/src/normal_diff.rs +++ b/src/normal_diff.rs @@ -5,6 +5,8 @@ use std::io::Write; +use crate::utils::do_write_line; + #[derive(Debug, PartialEq)] struct Mismatch { pub line_number_expected: usize, @@ -114,7 +116,7 @@ fn make_diff(expected: &[u8], actual: &[u8], stop_early: bool) -> Vec } #[must_use] -pub fn diff(expected: &[u8], actual: &[u8], stop_early: bool) -> Vec { +pub fn diff(expected: &[u8], actual: &[u8], stop_early: bool, expand_tabs: bool) -> Vec { // 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(); @@ -188,7 +190,7 @@ pub fn diff(expected: &[u8], actual: &[u8], stop_early: bool) -> Vec { } for expected in &result.expected { write!(&mut output, "< ").unwrap(); - output.write_all(expected).unwrap(); + do_write_line(&mut output, expected, expand_tabs).unwrap(); writeln!(&mut output).unwrap(); } if result.expected_missing_nl { @@ -199,7 +201,7 @@ pub fn diff(expected: &[u8], actual: &[u8], stop_early: bool) -> Vec { } for actual in &result.actual { write!(&mut output, "> ").unwrap(); - output.write_all(actual).unwrap(); + do_write_line(&mut output, actual, expand_tabs).unwrap(); writeln!(&mut output).unwrap(); } if result.actual_missing_nl { @@ -220,7 +222,7 @@ mod tests { a.write_all(b"a\n").unwrap(); let mut b = Vec::new(); b.write_all(b"b\n").unwrap(); - let diff = diff(&a, &b, false); + let diff = diff(&a, &b, false, false); let expected = b"1c1\n< a\n---\n> b\n".to_vec(); assert_eq!(diff, expected); } @@ -273,7 +275,7 @@ mod tests { } // This test diff is intentionally reversed. // We want it to turn the alef into bet. - let diff = diff(&alef, &bet, false); + let diff = diff(&alef, &bet, false, false); File::create(&format!("{target}/ab.diff")) .unwrap() .write_all(&diff) @@ -365,7 +367,7 @@ mod tests { } // This test diff is intentionally reversed. // We want it to turn the alef into bet. - let diff = diff(&alef, &bet, false); + let diff = diff(&alef, &bet, false, false); File::create(&format!("{target}/abn.diff")) .unwrap() .write_all(&diff) @@ -439,7 +441,7 @@ mod tests { } // This test diff is intentionally reversed. // We want it to turn the alef into bet. - let diff = diff(&alef, &bet, false); + let diff = diff(&alef, &bet, false, false); File::create(&format!("{target}/ab_.diff")) .unwrap() .write_all(&diff) @@ -517,7 +519,7 @@ mod tests { } // This test diff is intentionally reversed. // We want it to turn the alef into bet. - let diff = diff(&alef, &bet, false); + let diff = diff(&alef, &bet, false, false); File::create(&format!("{target}/abr.diff")) .unwrap() .write_all(&diff) @@ -552,18 +554,18 @@ mod tests { let from = ["a", "b", "c"].join("\n"); let to = ["a", "d", "c"].join("\n"); - let diff_full = diff(from.as_bytes(), to.as_bytes(), false); + let diff_full = diff(from.as_bytes(), to.as_bytes(), false, false); 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(), true); + let diff_brief = diff(from.as_bytes(), to.as_bytes(), true, false); let expected_brief = "\0".as_bytes(); assert_eq!(diff_brief, expected_brief); - let nodiff_full = diff(from.as_bytes(), from.as_bytes(), false); + let nodiff_full = diff(from.as_bytes(), from.as_bytes(), false, false); assert!(nodiff_full.is_empty()); - let nodiff_brief = diff(from.as_bytes(), from.as_bytes(), true); + let nodiff_brief = diff(from.as_bytes(), from.as_bytes(), true, false); assert!(nodiff_brief.is_empty()); } } diff --git a/src/params.rs b/src/params.rs index 661cd37..a576f3d 100644 --- a/src/params.rs +++ b/src/params.rs @@ -27,6 +27,7 @@ pub struct Params { pub context_count: usize, pub report_identical_files: bool, pub brief: bool, + pub expand_tabs: bool, } pub fn parse_params>(opts: I) -> Result { @@ -42,6 +43,7 @@ pub fn parse_params>(opts: I) -> Result>(opts: I) -> Result>(opts: I) -> Result), @@ -241,6 +243,7 @@ pub fn diff( actual_filename: &str, context_size: usize, stop_early: bool, + expand_tabs: bool, ) -> Vec { let mut output = format!("--- {expected_filename}\t\n+++ {actual_filename}\t\n").into_bytes(); let diff_results = make_diff(expected, actual, context_size, stop_early); @@ -371,17 +374,20 @@ pub fn diff( match line { DiffLine::Expected(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, expand_tabs) + .expect("write to Vec is infallible"); writeln!(output).unwrap(); } DiffLine::Context(c) => { write!(output, " ").expect("write to Vec is infallible"); - output.write_all(&c).expect("write to Vec is infallible"); + do_write_line(&mut output, &c, expand_tabs) + .expect("write to Vec is infallible"); writeln!(output).unwrap(); } DiffLine::Actual(r) => { write!(output, "+",).expect("write to Vec is infallible"); - output.write_all(&r).expect("write to Vec is infallible"); + do_write_line(&mut output, &r, expand_tabs) + .expect("write to Vec is infallible"); writeln!(output).unwrap(); } DiffLine::MissingNL => { @@ -454,6 +460,7 @@ mod tests { &format!("{target}/alef"), 2, false, + false, ); File::create(&format!("{target}/ab.diff")) .unwrap() @@ -568,6 +575,7 @@ mod tests { &format!("{target}/alefn"), 2, false, + false, ); File::create(&format!("{target}/abn.diff")) .unwrap() @@ -662,6 +670,7 @@ mod tests { &format!("{target}/alef_"), 2, false, + false, ); File::create(&format!("{target}/ab_.diff")) .unwrap() @@ -741,6 +750,7 @@ mod tests { &format!("{target}/alefx"), 2, false, + false, ); File::create(&format!("{target}/abx.diff")) .unwrap() @@ -825,6 +835,7 @@ mod tests { &format!("{target}/alefr"), 2, false, + false, ); File::create(&format!("{target}/abr.diff")) .unwrap() @@ -869,6 +880,7 @@ mod tests { to_filename, context_size, false, + false, ); let expected_full = [ "--- foo\t", @@ -890,6 +902,7 @@ mod tests { to_filename, context_size, true, + false, ); let expected_brief = ["--- foo\t", "+++ bar\t", ""].join("\n"); assert_eq!(diff_brief, expected_brief.as_bytes()); @@ -901,6 +914,7 @@ mod tests { to_filename, context_size, false, + false, ); assert!(nodiff_full.is_empty()); @@ -911,6 +925,7 @@ mod tests { to_filename, context_size, true, + false, ); assert!(nodiff_brief.is_empty()); } diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..1d13682 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,112 @@ +// 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 std::io::Write; + +use unicode_width::UnicodeWidthStr; + +/// Replace tabs by spaces in the input line. +/// Correctly handle multi-bytes characters. +/// This assumes that line does not contain any line breaks (if it does, the result is undefined). +pub fn do_expand_tabs(line: &[u8], tabsize: usize) -> Vec { + let tab = b'\t'; + let ntabs = line.iter().filter(|c| **c == tab).count(); + if ntabs == 0 { + return line.to_vec(); + } + let mut result = Vec::with_capacity(line.len() + ntabs * (tabsize - 1)); + let mut offset = 0; + + let mut iter = line.split(|c| *c == tab).peekable(); + while let Some(chunk) = iter.next() { + match String::from_utf8(chunk.to_vec()) { + Ok(s) => offset += UnicodeWidthStr::width(s.as_str()), + Err(_) => offset += chunk.len(), + } + result.extend_from_slice(chunk); + if iter.peek().is_some() { + result.resize(result.len() + tabsize - offset % tabsize, b' '); + offset = 0; + } + } + + result +} + +/// Write a single line to an output stream, expanding tabs to space if necessary. +/// This assumes that line does not contain any line breaks +/// (if it does and tabs are to be expanded to spaces, the result is undefined). +pub fn do_write_line(output: &mut Vec, line: &[u8], expand_tabs: bool) -> std::io::Result<()> { + if expand_tabs { + output.write_all(do_expand_tabs(line, 8).as_slice()) + } else { + output.write_all(line) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + mod expand_tabs { + use super::*; + use pretty_assertions::assert_eq; + + fn assert_tab_expansion(line: &str, tabsize: usize, expected: &str) { + assert_eq!( + do_expand_tabs(line.as_bytes(), tabsize), + expected.as_bytes() + ); + } + + #[test] + fn basics() { + assert_tab_expansion("foo barr baz", 8, "foo barr baz"); + assert_tab_expansion("foo\tbarr\tbaz", 8, "foo barr baz"); + assert_tab_expansion("foo\tbarr\tbaz", 5, "foo barr baz"); + assert_tab_expansion("foo\tbarr\tbaz", 2, "foo barr baz"); + } + + #[test] + fn multibyte_chars() { + assert_tab_expansion("foo\tépée\tbaz", 8, "foo épée baz"); + assert_tab_expansion("foo\t😉\tbaz", 5, "foo 😉 baz"); + + // Note: The Woman Scientist emoji (👩‍🔬) is a ZWJ sequence combining + // the Woman emoji (👩) and the Microscope emoji (🔬). On supported platforms + // it is displayed as a single emoji and should have a print size of 2 columns, + // but terminal emulators tend to not support this, and display the two emojis + // side by side, thus accounting for a print size of 4 columns. + assert_tab_expansion("foo\t👩‍🔬\tbaz", 6, "foo 👩‍🔬 baz"); + } + + #[test] + fn invalid_utf8() { + // [240, 240, 152, 137] is an invalid UTF-8 sequence, so it is handled as 4 bytes + assert_eq!( + do_expand_tabs(&[240, 240, 152, 137, 9, 102, 111, 111], 8), + &[240, 240, 152, 137, 32, 32, 32, 32, 102, 111, 111] + ); + } + } + + mod write_line { + use super::*; + use pretty_assertions::assert_eq; + + fn assert_line_written(line: &str, expand_tabs: bool, expected: &str) { + let mut output: Vec = Vec::new(); + assert!(do_write_line(&mut output, line.as_bytes(), expand_tabs).is_ok()); + assert_eq!(output, expected.as_bytes()); + } + + #[test] + fn basics() { + assert_line_written("foo bar baz", false, "foo bar baz"); + assert_line_written("foo bar\tbaz", false, "foo bar\tbaz"); + assert_line_written("foo bar\tbaz", true, "foo bar baz"); + } + } +} From e0283083f297cd8f9f6e39e6f24cd18ab18e8b02 Mon Sep 17 00:00:00 2001 From: Olivier Tilloy Date: Tue, 5 Mar 2024 18:52:04 +0100 Subject: [PATCH 20/61] Implement --tabsize option --- Cargo.lock | 1 + Cargo.toml | 1 + src/context_diff.rs | 21 +++++-- src/ed_diff.rs | 15 ++--- src/main.rs | 14 +++-- src/normal_diff.rs | 30 ++++++---- src/params.rs | 130 ++++++++++++++++++++++++++++++++++++++++++++ src/unified_diff.rs | 16 +++++- src/utils.rs | 19 ++++--- 9 files changed, 207 insertions(+), 40 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9dc8153..15c81e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -81,6 +81,7 @@ dependencies = [ "diff", "predicates", "pretty_assertions", + "regex", "same-file", "tempfile", "unicode-width", diff --git a/Cargo.toml b/Cargo.toml index 7eeb35b..4ddf5fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ path = "src/main.rs" [dependencies] diff = "0.1.10" +regex = "1.10.3" same-file = "1.0.6" unicode-width = "0.1.11" diff --git a/src/context_diff.rs b/src/context_diff.rs index 408821f..9f1db55 100644 --- a/src/context_diff.rs +++ b/src/context_diff.rs @@ -273,6 +273,7 @@ pub fn diff( context_size: usize, stop_early: bool, expand_tabs: bool, + tabsize: usize, ) -> Vec { let mut output = format!("*** {expected_filename}\t\n--- {actual_filename}\t\n").into_bytes(); let diff_results = make_diff(expected, actual, context_size, stop_early); @@ -317,19 +318,19 @@ pub fn diff( match line { DiffLine::Context(e) => { write!(output, " ").expect("write to Vec is infallible"); - do_write_line(&mut output, &e, expand_tabs) + do_write_line(&mut output, &e, expand_tabs, tabsize) .expect("write to Vec is infallible"); writeln!(output).unwrap(); } DiffLine::Change(e) => { write!(output, "! ").expect("write to Vec is infallible"); - do_write_line(&mut output, &e, expand_tabs) + do_write_line(&mut output, &e, expand_tabs, tabsize) .expect("write to Vec is infallible"); writeln!(output).unwrap(); } DiffLine::Add(e) => { write!(output, "- ").expect("write to Vec is infallible"); - do_write_line(&mut output, &e, expand_tabs) + do_write_line(&mut output, &e, expand_tabs, tabsize) .expect("write to Vec is infallible"); writeln!(output).unwrap(); } @@ -347,19 +348,19 @@ pub fn diff( match line { DiffLine::Context(e) => { write!(output, " ").expect("write to Vec is infallible"); - do_write_line(&mut output, &e, expand_tabs) + do_write_line(&mut output, &e, expand_tabs, tabsize) .expect("write to Vec is infallible"); writeln!(output).unwrap(); } DiffLine::Change(e) => { write!(output, "! ").expect("write to Vec is infallible"); - do_write_line(&mut output, &e, expand_tabs) + do_write_line(&mut output, &e, expand_tabs, tabsize) .expect("write to Vec is infallible"); writeln!(output).unwrap(); } DiffLine::Add(e) => { write!(output, "+ ").expect("write to Vec is infallible"); - do_write_line(&mut output, &e, expand_tabs) + do_write_line(&mut output, &e, expand_tabs, tabsize) .expect("write to Vec is infallible"); writeln!(output).unwrap(); } @@ -434,6 +435,7 @@ mod tests { 2, false, false, + 8, ); File::create(&format!("{target}/ab.diff")) .unwrap() @@ -514,6 +516,7 @@ mod tests { 2, false, false, + 8, ); File::create(&format!("{target}/ab_.diff")) .unwrap() @@ -597,6 +600,7 @@ mod tests { 2, false, false, + 8, ); File::create(&format!("{target}/abx.diff")) .unwrap() @@ -683,6 +687,7 @@ mod tests { 2, false, false, + 8, ); File::create(&format!("{target}/abr.diff")) .unwrap() @@ -729,6 +734,7 @@ mod tests { context_size, false, false, + 8, ); let expected_full = [ "*** foo\t", @@ -755,6 +761,7 @@ mod tests { context_size, true, false, + 8, ); let expected_brief = ["*** foo\t", "--- bar\t", ""].join("\n"); assert_eq!(diff_brief, expected_brief.as_bytes()); @@ -767,6 +774,7 @@ mod tests { context_size, false, false, + 8, ); assert!(nodiff_full.is_empty()); @@ -778,6 +786,7 @@ mod tests { context_size, true, false, + 8, ); assert!(nodiff_brief.is_empty()); } diff --git a/src/ed_diff.rs b/src/ed_diff.rs index 6d47b9f..c02289c 100644 --- a/src/ed_diff.rs +++ b/src/ed_diff.rs @@ -114,6 +114,7 @@ pub fn diff( actual: &[u8], stop_early: bool, expand_tabs: bool, + tabsize: usize, ) -> Result, DiffError> { let mut output = Vec::new(); let diff_results = make_diff(expected, actual, stop_early)?; @@ -152,7 +153,7 @@ pub fn diff( if actual == b"." { writeln!(&mut output, "..\n.\ns/.//\na").unwrap(); } else { - do_write_line(&mut output, actual, expand_tabs).unwrap(); + do_write_line(&mut output, actual, expand_tabs, tabsize).unwrap(); writeln!(&mut output).unwrap(); } } @@ -167,7 +168,7 @@ mod tests { use super::*; use pretty_assertions::assert_eq; pub fn diff_w(expected: &[u8], actual: &[u8], filename: &str) -> Result, DiffError> { - let mut output = diff(expected, actual, false, false)?; + let mut output = diff(expected, actual, false, false, 8)?; writeln!(&mut output, "w {filename}").unwrap(); Ok(output) } @@ -176,7 +177,7 @@ mod tests { fn test_basic() { let from = b"a\n"; let to = b"b\n"; - let diff = diff(from, to, false, false).unwrap(); + let diff = diff(from, to, false, false, 8).unwrap(); let expected = ["1c", "b", ".", ""].join("\n"); assert_eq!(diff, expected.as_bytes()); } @@ -411,18 +412,18 @@ mod tests { let from = ["a", "b", "c", ""].join("\n"); let to = ["a", "d", "c", ""].join("\n"); - let diff_full = diff(from.as_bytes(), to.as_bytes(), false, false).unwrap(); + let diff_full = diff(from.as_bytes(), to.as_bytes(), false, false, 8).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(), true, false).unwrap(); + let diff_brief = diff(from.as_bytes(), to.as_bytes(), true, false, 8).unwrap(); let expected_brief = "\0".as_bytes(); assert_eq!(diff_brief, expected_brief); - let nodiff_full = diff(from.as_bytes(), from.as_bytes(), false, false).unwrap(); + let nodiff_full = diff(from.as_bytes(), from.as_bytes(), false, false, 8).unwrap(); assert!(nodiff_full.is_empty()); - let nodiff_brief = diff(from.as_bytes(), from.as_bytes(), true, false).unwrap(); + let nodiff_brief = diff(from.as_bytes(), from.as_bytes(), true, false, 8).unwrap(); assert!(nodiff_brief.is_empty()); } } diff --git a/src/main.rs b/src/main.rs index f074cb1..2a6d4ca 100644 --- a/src/main.rs +++ b/src/main.rs @@ -32,6 +32,7 @@ fn main() -> ExitCode { report_identical_files, brief, expand_tabs, + tabsize, } = parse_params(opts).unwrap_or_else(|error| { eprintln!("{error}"); exit(2); @@ -67,7 +68,9 @@ fn main() -> ExitCode { }; // run diff let result: Vec = match format { - Format::Normal => normal_diff::diff(&from_content, &to_content, brief, expand_tabs), + Format::Normal => { + normal_diff::diff(&from_content, &to_content, brief, expand_tabs, tabsize) + } Format::Unified => unified_diff::diff( &from_content, &from.to_string_lossy(), @@ -76,6 +79,7 @@ fn main() -> ExitCode { context_count, brief, expand_tabs, + tabsize, ), Format::Context => context_diff::diff( &from_content, @@ -85,13 +89,13 @@ fn main() -> ExitCode { context_count, brief, expand_tabs, + tabsize, ), - Format::Ed => { - ed_diff::diff(&from_content, &to_content, brief, expand_tabs).unwrap_or_else(|error| { + Format::Ed => ed_diff::diff(&from_content, &to_content, brief, expand_tabs, tabsize) + .unwrap_or_else(|error| { eprintln!("{error}"); exit(2); - }) - } + }), }; if brief && !result.is_empty() { println!( diff --git a/src/normal_diff.rs b/src/normal_diff.rs index e25a6c6..b26de77 100644 --- a/src/normal_diff.rs +++ b/src/normal_diff.rs @@ -116,7 +116,13 @@ fn make_diff(expected: &[u8], actual: &[u8], stop_early: bool) -> Vec } #[must_use] -pub fn diff(expected: &[u8], actual: &[u8], stop_early: bool, expand_tabs: bool) -> Vec { +pub fn diff( + expected: &[u8], + actual: &[u8], + stop_early: bool, + expand_tabs: bool, + tabsize: usize, +) -> Vec { // 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(); @@ -190,7 +196,7 @@ pub fn diff(expected: &[u8], actual: &[u8], stop_early: bool, expand_tabs: bool) } for expected in &result.expected { write!(&mut output, "< ").unwrap(); - do_write_line(&mut output, expected, expand_tabs).unwrap(); + do_write_line(&mut output, expected, expand_tabs, tabsize).unwrap(); writeln!(&mut output).unwrap(); } if result.expected_missing_nl { @@ -201,7 +207,7 @@ pub fn diff(expected: &[u8], actual: &[u8], stop_early: bool, expand_tabs: bool) } for actual in &result.actual { write!(&mut output, "> ").unwrap(); - do_write_line(&mut output, actual, expand_tabs).unwrap(); + do_write_line(&mut output, actual, expand_tabs, tabsize).unwrap(); writeln!(&mut output).unwrap(); } if result.actual_missing_nl { @@ -222,7 +228,7 @@ mod tests { a.write_all(b"a\n").unwrap(); let mut b = Vec::new(); b.write_all(b"b\n").unwrap(); - let diff = diff(&a, &b, false, false); + let diff = diff(&a, &b, false, false, 8); let expected = b"1c1\n< a\n---\n> b\n".to_vec(); assert_eq!(diff, expected); } @@ -275,7 +281,7 @@ mod tests { } // This test diff is intentionally reversed. // We want it to turn the alef into bet. - let diff = diff(&alef, &bet, false, false); + let diff = diff(&alef, &bet, false, false, 8); File::create(&format!("{target}/ab.diff")) .unwrap() .write_all(&diff) @@ -367,7 +373,7 @@ mod tests { } // This test diff is intentionally reversed. // We want it to turn the alef into bet. - let diff = diff(&alef, &bet, false, false); + let diff = diff(&alef, &bet, false, false, 8); File::create(&format!("{target}/abn.diff")) .unwrap() .write_all(&diff) @@ -441,7 +447,7 @@ mod tests { } // This test diff is intentionally reversed. // We want it to turn the alef into bet. - let diff = diff(&alef, &bet, false, false); + let diff = diff(&alef, &bet, false, false, 8); File::create(&format!("{target}/ab_.diff")) .unwrap() .write_all(&diff) @@ -519,7 +525,7 @@ mod tests { } // This test diff is intentionally reversed. // We want it to turn the alef into bet. - let diff = diff(&alef, &bet, false, false); + let diff = diff(&alef, &bet, false, false, 8); File::create(&format!("{target}/abr.diff")) .unwrap() .write_all(&diff) @@ -554,18 +560,18 @@ mod tests { let from = ["a", "b", "c"].join("\n"); let to = ["a", "d", "c"].join("\n"); - let diff_full = diff(from.as_bytes(), to.as_bytes(), false, false); + let diff_full = diff(from.as_bytes(), to.as_bytes(), false, false, 8); 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(), true, false); + let diff_brief = diff(from.as_bytes(), to.as_bytes(), true, false, 8); let expected_brief = "\0".as_bytes(); assert_eq!(diff_brief, expected_brief); - let nodiff_full = diff(from.as_bytes(), from.as_bytes(), false, false); + let nodiff_full = diff(from.as_bytes(), from.as_bytes(), false, false, 8); assert!(nodiff_full.is_empty()); - let nodiff_brief = diff(from.as_bytes(), from.as_bytes(), true, false); + let nodiff_brief = diff(from.as_bytes(), from.as_bytes(), true, false, 8); assert!(nodiff_brief.is_empty()); } } diff --git a/src/params.rs b/src/params.rs index a576f3d..f511e7c 100644 --- a/src/params.rs +++ b/src/params.rs @@ -1,5 +1,7 @@ use std::ffi::{OsStr, OsString}; +use regex::Regex; + #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Format { Normal, @@ -8,6 +10,8 @@ pub enum Format { Ed, } +const DEFAULT_TABSIZE: usize = 8; + #[cfg(unix)] fn osstr_bytes(osstr: &OsStr) -> &[u8] { use std::os::unix::ffi::OsStrExt; @@ -28,6 +32,7 @@ pub struct Params { pub report_identical_files: bool, pub brief: bool, pub expand_tabs: bool, + pub tabsize: usize, } pub fn parse_params>(opts: I) -> Result { @@ -44,6 +49,8 @@ pub fn parse_params>(opts: I) -> Result\d+)$").unwrap(); + let mut tabsize = DEFAULT_TABSIZE; while let Some(param) = opts.next() { if param == "--" { break; @@ -70,6 +77,22 @@ pub fn parse_params>(opts: I) -> Result() { + Ok(num) => num, + Err(_) => return Err(format!("invalid tabsize «{}»", tabsize_str)), + }; + continue; + } let p = osstr_bytes(¶m); if p.first() == Some(&b'-') && p.get(1) != Some(&b'-') { let mut bit = p[1..].iter().copied().peekable(); @@ -154,6 +177,7 @@ pub fn parse_params>(opts: I) -> Result Vec { let mut output = format!("--- {expected_filename}\t\n+++ {actual_filename}\t\n").into_bytes(); let diff_results = make_diff(expected, actual, context_size, stop_early); @@ -374,19 +375,19 @@ pub fn diff( match line { DiffLine::Expected(e) => { write!(output, "-").expect("write to Vec is infallible"); - do_write_line(&mut output, &e, expand_tabs) + do_write_line(&mut output, &e, expand_tabs, tabsize) .expect("write to Vec is infallible"); writeln!(output).unwrap(); } DiffLine::Context(c) => { write!(output, " ").expect("write to Vec is infallible"); - do_write_line(&mut output, &c, expand_tabs) + do_write_line(&mut output, &c, expand_tabs, tabsize) .expect("write to Vec is infallible"); writeln!(output).unwrap(); } DiffLine::Actual(r) => { write!(output, "+",).expect("write to Vec is infallible"); - do_write_line(&mut output, &r, expand_tabs) + do_write_line(&mut output, &r, expand_tabs, tabsize) .expect("write to Vec is infallible"); writeln!(output).unwrap(); } @@ -461,6 +462,7 @@ mod tests { 2, false, false, + 8, ); File::create(&format!("{target}/ab.diff")) .unwrap() @@ -576,6 +578,7 @@ mod tests { 2, false, false, + 8, ); File::create(&format!("{target}/abn.diff")) .unwrap() @@ -671,6 +674,7 @@ mod tests { 2, false, false, + 8, ); File::create(&format!("{target}/ab_.diff")) .unwrap() @@ -751,6 +755,7 @@ mod tests { 2, false, false, + 8, ); File::create(&format!("{target}/abx.diff")) .unwrap() @@ -836,6 +841,7 @@ mod tests { 2, false, false, + 8, ); File::create(&format!("{target}/abr.diff")) .unwrap() @@ -881,6 +887,7 @@ mod tests { context_size, false, false, + 8, ); let expected_full = [ "--- foo\t", @@ -903,6 +910,7 @@ mod tests { context_size, true, false, + 8, ); let expected_brief = ["--- foo\t", "+++ bar\t", ""].join("\n"); assert_eq!(diff_brief, expected_brief.as_bytes()); @@ -915,6 +923,7 @@ mod tests { context_size, false, false, + 8, ); assert!(nodiff_full.is_empty()); @@ -926,6 +935,7 @@ mod tests { context_size, true, false, + 8, ); assert!(nodiff_brief.is_empty()); } diff --git a/src/utils.rs b/src/utils.rs index 1d13682..94d950f 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -38,9 +38,14 @@ pub fn do_expand_tabs(line: &[u8], tabsize: usize) -> Vec { /// Write a single line to an output stream, expanding tabs to space if necessary. /// This assumes that line does not contain any line breaks /// (if it does and tabs are to be expanded to spaces, the result is undefined). -pub fn do_write_line(output: &mut Vec, line: &[u8], expand_tabs: bool) -> std::io::Result<()> { +pub fn do_write_line( + output: &mut Vec, + line: &[u8], + expand_tabs: bool, + tabsize: usize, +) -> std::io::Result<()> { if expand_tabs { - output.write_all(do_expand_tabs(line, 8).as_slice()) + output.write_all(do_expand_tabs(line, tabsize).as_slice()) } else { output.write_all(line) } @@ -96,17 +101,17 @@ mod tests { use super::*; use pretty_assertions::assert_eq; - fn assert_line_written(line: &str, expand_tabs: bool, expected: &str) { + fn assert_line_written(line: &str, expand_tabs: bool, tabsize: usize, expected: &str) { let mut output: Vec = Vec::new(); - assert!(do_write_line(&mut output, line.as_bytes(), expand_tabs).is_ok()); + assert!(do_write_line(&mut output, line.as_bytes(), expand_tabs, tabsize).is_ok()); assert_eq!(output, expected.as_bytes()); } #[test] fn basics() { - assert_line_written("foo bar baz", false, "foo bar baz"); - assert_line_written("foo bar\tbaz", false, "foo bar\tbaz"); - assert_line_written("foo bar\tbaz", true, "foo bar baz"); + assert_line_written("foo bar baz", false, 8, "foo bar baz"); + assert_line_written("foo bar\tbaz", false, 8, "foo bar\tbaz"); + assert_line_written("foo bar\tbaz", true, 8, "foo bar baz"); } } } From cfc68d58bcd0bfd1b339a84cb70950fbca875569 Mon Sep 17 00:00:00 2001 From: Olivier Tilloy Date: Tue, 19 Mar 2024 19:00:39 +0100 Subject: [PATCH 21/61] Fix fuzzers' invokations --- fuzz/fuzz_targets/fuzz_ed.rs | 2 +- fuzz/fuzz_targets/fuzz_normal.rs | 2 +- fuzz/fuzz_targets/fuzz_patch.rs | 3 +++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/fuzz/fuzz_targets/fuzz_ed.rs b/fuzz/fuzz_targets/fuzz_ed.rs index 5c5132e..69461d1 100644 --- a/fuzz/fuzz_targets/fuzz_ed.rs +++ b/fuzz/fuzz_targets/fuzz_ed.rs @@ -8,7 +8,7 @@ use std::io::Write; use std::process::Command; fn diff_w(expected: &[u8], actual: &[u8], filename: &str) -> Result, DiffError> { - let mut output = ed_diff::diff(expected, actual)?; + let mut output = ed_diff::diff(expected, actual, false, false, 8)?; writeln!(&mut output, "w {filename}").unwrap(); Ok(output) } diff --git a/fuzz/fuzz_targets/fuzz_normal.rs b/fuzz/fuzz_targets/fuzz_normal.rs index a44ece3..2d38641 100644 --- a/fuzz/fuzz_targets/fuzz_normal.rs +++ b/fuzz/fuzz_targets/fuzz_normal.rs @@ -21,7 +21,7 @@ fuzz_target!(|x: (Vec, Vec)| { } else { return }*/ - let diff = normal_diff::diff(&from, &to); + let diff = normal_diff::diff(&from, &to, false, false, 8); File::create("target/fuzz.file.original") .unwrap() .write_all(&from) diff --git a/fuzz/fuzz_targets/fuzz_patch.rs b/fuzz/fuzz_targets/fuzz_patch.rs index d353523..15e4967 100644 --- a/fuzz/fuzz_targets/fuzz_patch.rs +++ b/fuzz/fuzz_targets/fuzz_patch.rs @@ -26,6 +26,9 @@ fuzz_target!(|x: (Vec, Vec, u8)| { &to, "target/fuzz.file", context as usize, + false, + false, + 8, ); File::create("target/fuzz.file.original") .unwrap() From f2fd2127ed866222639019ac61298c843f778187 Mon Sep 17 00:00:00 2001 From: Olivier Tilloy Date: Tue, 19 Mar 2024 19:02:26 +0100 Subject: [PATCH 22/61] Politely ask clippy to not complain about too many arguments --- src/context_diff.rs | 1 + src/unified_diff.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/context_diff.rs b/src/context_diff.rs index 9f1db55..d9c61b8 100644 --- a/src/context_diff.rs +++ b/src/context_diff.rs @@ -265,6 +265,7 @@ fn make_diff( } #[must_use] +#[allow(clippy::too_many_arguments)] pub fn diff( expected: &[u8], expected_filename: &str, diff --git a/src/unified_diff.rs b/src/unified_diff.rs index 5af52e9..0d3ec38 100644 --- a/src/unified_diff.rs +++ b/src/unified_diff.rs @@ -236,6 +236,7 @@ fn make_diff( } #[must_use] +#[allow(clippy::too_many_arguments)] pub fn diff( expected: &[u8], expected_filename: &str, From f60fefaf6e5d7f92e8eb58ef07d3107ea9b37e8c Mon Sep 17 00:00:00 2001 From: Olivier Tilloy Date: Sat, 30 Mar 2024 23:50:58 +0100 Subject: [PATCH 23/61] Implement the Default trait for Params --- src/params.rs | 169 ++++++++++++++------------------------------------ 1 file changed, 48 insertions(+), 121 deletions(-) diff --git a/src/params.rs b/src/params.rs index f511e7c..6a6d261 100644 --- a/src/params.rs +++ b/src/params.rs @@ -2,16 +2,15 @@ use std::ffi::{OsStr, OsString}; use regex::Regex; -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub enum Format { + #[default] Normal, Unified, Context, Ed, } -const DEFAULT_TABSIZE: usize = 8; - #[cfg(unix)] fn osstr_bytes(osstr: &OsStr) -> &[u8] { use std::os::unix::ffi::OsStrExt; @@ -35,6 +34,21 @@ pub struct Params { pub tabsize: usize, } +impl Default for Params { + fn default() -> Self { + Self { + from: OsString::default(), + to: OsString::default(), + format: Format::default(), + context_count: 3, + report_identical_files: false, + brief: false, + expand_tabs: false, + tabsize: 8, + } + } +} + pub fn parse_params>(opts: I) -> Result { let mut opts = opts.into_iter(); // parse CLI @@ -42,15 +56,11 @@ pub fn parse_params>(opts: I) -> Result ".to_string()); }; + let mut params = Params::default(); let mut from = None; let mut to = None; let mut format = None; - let mut context_count = 3; - let mut report_identical_files = false; - let mut brief = false; - let mut expand_tabs = false; let tabsize_re = Regex::new(r"^--tabsize=(?\d+)$").unwrap(); - let mut tabsize = DEFAULT_TABSIZE; while let Some(param) = opts.next() { if param == "--" { break; @@ -66,15 +76,15 @@ pub fn parse_params>(opts: I) -> Result>(opts: I) -> Result() { + params.tabsize = match tabsize_str.parse::() { Ok(num) => num, Err(_) => return Err(format!("invalid tabsize «{}»", tabsize_str)), }; @@ -101,10 +111,10 @@ pub fn parse_params>(opts: I) -> Result { - context_count = (b - b'0') as usize; + params.context_count = (b - b'0') as usize; while let Some(b'0'..=b'9') = bit.peek() { - context_count *= 10; - context_count += (bit.next().unwrap() - b'0') as usize; + params.context_count *= 10; + params.context_count += (bit.next().unwrap() - b'0') as usize; } } b'c' => { @@ -138,7 +148,7 @@ pub fn parse_params>(opts: I) -> Result>(opts: I) -> Result ", exe.to_string_lossy())); } } - let from = if let Some(from) = from { + params.from = if let Some(from) = from { from } else if let Some(param) = opts.next() { param } else { return Err(format!("Usage: {} ", exe.to_string_lossy())); }; - let to = if let Some(to) = to { + params.to = if let Some(to) = to { to } else if let Some(param) = opts.next() { param } else { return Err(format!("Usage: {} ", exe.to_string_lossy())); }; - let format = format.unwrap_or(Format::Normal); - Ok(Params { - from, - to, - format, - context_count, - report_identical_files, - brief, - expand_tabs, - tabsize, - }) + params.format = format.unwrap_or(Format::default()); + Ok(params) } #[cfg(test)] @@ -193,12 +194,7 @@ mod tests { Ok(Params { from: os("foo"), to: os("bar"), - format: Format::Normal, - context_count: 3, - report_identical_files: false, - brief: false, - expand_tabs: false, - tabsize: DEFAULT_TABSIZE, + ..Default::default() }), parse_params([os("diff"), os("foo"), os("bar")].iter().cloned()) ); @@ -210,11 +206,7 @@ mod tests { from: os("foo"), to: os("bar"), format: Format::Ed, - context_count: 3, - report_identical_files: false, - brief: false, - expand_tabs: false, - tabsize: DEFAULT_TABSIZE, + ..Default::default() }), parse_params([os("diff"), os("-e"), os("foo"), os("bar")].iter().cloned()) ); @@ -227,10 +219,7 @@ mod tests { to: os("bar"), format: Format::Unified, context_count: 54, - report_identical_files: false, - brief: false, - expand_tabs: false, - tabsize: DEFAULT_TABSIZE, + ..Default::default() }), parse_params( [os("diff"), os("-u54"), os("foo"), os("bar")] @@ -244,10 +233,7 @@ mod tests { to: os("bar"), format: Format::Unified, context_count: 54, - report_identical_files: false, - brief: false, - expand_tabs: false, - tabsize: DEFAULT_TABSIZE, + ..Default::default() }), parse_params( [os("diff"), os("-U54"), os("foo"), os("bar")] @@ -261,10 +247,7 @@ mod tests { to: os("bar"), format: Format::Unified, context_count: 54, - report_identical_files: false, - brief: false, - expand_tabs: false, - tabsize: DEFAULT_TABSIZE, + ..Default::default() }), parse_params( [os("diff"), os("-U"), os("54"), os("foo"), os("bar")] @@ -278,10 +261,7 @@ mod tests { to: os("bar"), format: Format::Context, context_count: 54, - report_identical_files: false, - brief: false, - expand_tabs: false, - tabsize: DEFAULT_TABSIZE, + ..Default::default() }), parse_params( [os("diff"), os("-c54"), os("foo"), os("bar")] @@ -296,12 +276,7 @@ mod tests { Ok(Params { from: os("foo"), to: os("bar"), - format: Format::Normal, - context_count: 3, - report_identical_files: false, - brief: false, - expand_tabs: false, - tabsize: DEFAULT_TABSIZE, + ..Default::default() }), parse_params([os("diff"), os("foo"), os("bar")].iter().cloned()) ); @@ -309,12 +284,8 @@ mod tests { Ok(Params { from: os("foo"), to: os("bar"), - format: Format::Normal, - context_count: 3, report_identical_files: true, - brief: false, - expand_tabs: false, - tabsize: DEFAULT_TABSIZE, + ..Default::default() }), parse_params([os("diff"), os("-s"), os("foo"), os("bar")].iter().cloned()) ); @@ -322,12 +293,8 @@ mod tests { Ok(Params { from: os("foo"), to: os("bar"), - format: Format::Normal, - context_count: 3, report_identical_files: true, - brief: false, - expand_tabs: false, - tabsize: DEFAULT_TABSIZE, + ..Default::default() }), parse_params( [ @@ -347,12 +314,7 @@ mod tests { Ok(Params { from: os("foo"), to: os("bar"), - format: Format::Normal, - context_count: 3, - report_identical_files: false, - brief: false, - expand_tabs: false, - tabsize: 8, + ..Default::default() }), parse_params([os("diff"), os("foo"), os("bar")].iter().cloned()) ); @@ -360,12 +322,8 @@ mod tests { Ok(Params { from: os("foo"), to: os("bar"), - format: Format::Normal, - context_count: 3, - report_identical_files: false, brief: true, - expand_tabs: false, - tabsize: 8, + ..Default::default() }), parse_params([os("diff"), os("-q"), os("foo"), os("bar")].iter().cloned()) ); @@ -373,12 +331,8 @@ mod tests { Ok(Params { from: os("foo"), to: os("bar"), - format: Format::Normal, - context_count: 3, - report_identical_files: false, brief: true, - expand_tabs: false, - tabsize: 8, + ..Default::default() }), parse_params( [os("diff"), os("--brief"), os("foo"), os("bar"),] @@ -393,12 +347,7 @@ mod tests { Ok(Params { from: os("foo"), to: os("bar"), - format: Format::Normal, - context_count: 3, - report_identical_files: false, - brief: false, - expand_tabs: false, - tabsize: DEFAULT_TABSIZE, + ..Default::default() }), parse_params([os("diff"), os("foo"), os("bar")].iter().cloned()) ); @@ -407,12 +356,8 @@ mod tests { Ok(Params { from: os("foo"), to: os("bar"), - format: Format::Normal, - context_count: 3, - report_identical_files: false, - brief: false, expand_tabs: true, - tabsize: DEFAULT_TABSIZE, + ..Default::default() }), parse_params( [os("diff"), os(option), os("foo"), os("bar")] @@ -428,12 +373,7 @@ mod tests { Ok(Params { from: os("foo"), to: os("bar"), - format: Format::Normal, - context_count: 3, - report_identical_files: false, - brief: false, - expand_tabs: false, - tabsize: DEFAULT_TABSIZE, + ..Default::default() }), parse_params([os("diff"), os("foo"), os("bar")].iter().cloned()) ); @@ -441,12 +381,8 @@ mod tests { Ok(Params { from: os("foo"), to: os("bar"), - format: Format::Normal, - context_count: 3, - report_identical_files: false, - brief: false, - expand_tabs: false, tabsize: 0, + ..Default::default() }), parse_params( [os("diff"), os("--tabsize=0"), os("foo"), os("bar")] @@ -458,12 +394,8 @@ mod tests { Ok(Params { from: os("foo"), to: os("bar"), - format: Format::Normal, - context_count: 3, - report_identical_files: false, - brief: false, - expand_tabs: false, tabsize: 42, + ..Default::default() }), parse_params( [os("diff"), os("--tabsize=42"), os("foo"), os("bar")] @@ -519,12 +451,7 @@ mod tests { Ok(Params { from: os("-g"), to: os("-h"), - format: Format::Normal, - context_count: 3, - report_identical_files: false, - brief: false, - expand_tabs: false, - tabsize: DEFAULT_TABSIZE, + ..Default::default() }), parse_params([os("diff"), os("--"), os("-g"), os("-h")].iter().cloned()) ); From 9ff8f89626ee8a144fc25e21a9dab3e7af9c28cb Mon Sep 17 00:00:00 2001 From: Tanmay Patil Date: Thu, 28 Mar 2024 23:32:46 +0530 Subject: [PATCH 24/61] Fix tests --- Cargo.lock | 5 +++-- Cargo.toml | 1 + src/context_diff.rs | 31 +++++++++++++++++++++++++------ 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6235103..4453f09 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -129,6 +129,7 @@ dependencies = [ "diff", "predicates", "pretty_assertions", + "regex", "same-file", "tempfile", ] @@ -301,9 +302,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.3" +version = "1.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", diff --git a/Cargo.toml b/Cargo.toml index 940e3d1..32f0cda 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ path = "src/main.rs" [dependencies] chrono = "0.4.35" diff = "0.1.10" +regex = "1.10.4" same-file = "1.0.6" [dev-dependencies] diff --git a/src/context_diff.rs b/src/context_diff.rs index fa09f7c..bb1dfe1 100644 --- a/src/context_diff.rs +++ b/src/context_diff.rs @@ -433,11 +433,12 @@ mod tests { if f != 2 { bet.write_all(b"l\n").unwrap(); } + let _ = File::create(&format!("{target}/aalef")).unwrap(); // This test diff is intentionally reversed. // We want it to turn the alef into bet. let diff = diff( &alef, - "a/alef", + &format!("{target}/aalef"), &bet, &format!("{target}/alef"), 2, @@ -512,11 +513,12 @@ mod tests { if f != 2 { bet.write_all(b"l\n").unwrap(); } + let _ = File::create(&format!("{target}/aalef_")).unwrap(); // This test diff is intentionally reversed. // We want it to turn the alef into bet. let diff = diff( &alef, - "a/alef_", + &format!("{target}/aalef_"), &bet, &format!("{target}/alef_"), 2, @@ -594,11 +596,12 @@ mod tests { if alef.is_empty() && bet.is_empty() { continue; }; + let _ = File::create(&format!("{target}/aalefx")).unwrap(); // This test diff is intentionally reversed. // We want it to turn the alef into bet. let diff = diff( &alef, - "a/alefx", + &format!("{target}/aalefx"), &bet, &format!("{target}/alefx"), 2, @@ -679,11 +682,12 @@ mod tests { if f != 2 { bet.write_all(b"f\n").unwrap(); } + let _ = File::create(&format!("{target}/aalefr")).unwrap(); // This test diff is intentionally reversed. // We want it to turn the alef into bet. let diff = diff( &alef, - "a/alefr", + &format!("{target}/aalefr"), &bet, &format!("{target}/alefr"), 2, @@ -720,9 +724,15 @@ mod tests { #[test] fn test_stop_early() { + use regex::Regex; + use std::fs::File; + use std::str; + let from_filename = "foo"; + let _ = File::create(&format!("foo")).unwrap(); let from = vec!["a", "b", "c", ""].join("\n"); let to_filename = "bar"; + let _ = File::create(&format!("bar")).unwrap(); let to = vec!["a", "d", "c", ""].join("\n"); let context_size: usize = 3; @@ -734,6 +744,11 @@ mod tests { context_size, false, ); + + let diff_full_text = str::from_utf8(&diff_full).unwrap(); + let re = Regex::new(r"\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d+ [+-]\d{4}").unwrap(); + let diff_full = re.replace_all(diff_full_text, ""); + let expected_full = vec![ "*** foo\t", "--- bar\t", @@ -749,7 +764,7 @@ mod tests { "", ] .join("\n"); - assert_eq!(diff_full, expected_full.as_bytes()); + assert_eq!(diff_full, expected_full); let diff_brief = diff( from.as_bytes(), @@ -759,8 +774,12 @@ mod tests { context_size, true, ); + + let diff_brief_text = str::from_utf8(&diff_brief).unwrap(); + let re = Regex::new(r"\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d+ [+-]\d{4}").unwrap(); + let diff_brief = re.replace_all(diff_brief_text, ""); let expected_brief = vec!["*** foo\t", "--- bar\t", ""].join("\n"); - assert_eq!(diff_brief, expected_brief.as_bytes()); + assert_eq!(diff_brief, expected_brief); let nodiff_full = diff( from.as_bytes(), From 80c9944bf79ddf71c73324b58faabd4408bd5f69 Mon Sep 17 00:00:00 2001 From: Tanmay Patil Date: Sun, 31 Mar 2024 22:57:51 +0530 Subject: [PATCH 25/61] Create foo/bar in target/context-diff --- src/context_diff.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/context_diff.rs b/src/context_diff.rs index 9df10d0..cad851c 100644 --- a/src/context_diff.rs +++ b/src/context_diff.rs @@ -747,11 +747,14 @@ mod tests { use std::fs::File; use std::str; - let from_filename = "foo"; - let _ = File::create(&format!("foo")).unwrap(); + let target = "target/context-diff"; + // test all possible six-line files. + let _ = std::fs::create_dir(target); + let from_filename = &format!("{target}/foo"); + let _ = File::create(from_filename).unwrap(); let from = ["a", "b", "c", ""].join("\n"); - let to_filename = "bar"; - let _ = File::create(&format!("bar")).unwrap(); + let to_filename = &format!("{target}/bar"); + let _ = File::create(to_filename).unwrap(); let to = ["a", "d", "c", ""].join("\n"); let context_size: usize = 3; @@ -771,8 +774,8 @@ mod tests { let diff_full = re.replace_all(diff_full_text, ""); let expected_full = [ - "*** foo\t", - "--- bar\t", + "*** target/context-diff/foo\t", + "--- target/context-diff/bar\t", "***************", "*** 1,3 ****", " a", @@ -801,7 +804,7 @@ mod tests { let diff_brief_text = str::from_utf8(&diff_brief).unwrap(); let re = Regex::new(r"\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d+ [+-]\d{4}").unwrap(); let diff_brief = re.replace_all(diff_brief_text, ""); - let expected_brief = ["*** foo\t", "--- bar\t", ""].join("\n"); + let expected_brief = ["*** target/context-diff/foo\t", "--- target/context-diff/bar\t", ""].join("\n"); assert_eq!(diff_brief, expected_brief); let nodiff_full = diff( From e6a0ba28c5900498824d9bdcc2c979dafdd0d2ed Mon Sep 17 00:00:00 2001 From: Olivier Tilloy Date: Sun, 31 Mar 2024 21:27:28 +0200 Subject: [PATCH 26/61] Pass a Params reference to the various diff() functions, instead of a long list of arguments --- src/context_diff.rs | 131 ++++++++++++++++++++---------------------- src/ed_diff.rs | 43 ++++++++------ src/lib.rs | 1 + src/main.rs | 68 ++++++---------------- src/normal_diff.rs | 49 +++++++++------- src/unified_diff.rs | 137 +++++++++++++++++++++----------------------- 6 files changed, 203 insertions(+), 226 deletions(-) diff --git a/src/context_diff.rs b/src/context_diff.rs index d9c61b8..9c567d8 100644 --- a/src/context_diff.rs +++ b/src/context_diff.rs @@ -6,6 +6,7 @@ use std::collections::VecDeque; use std::io::Write; +use crate::params::Params; use crate::utils::do_write_line; #[derive(Debug, PartialEq)] @@ -265,23 +266,18 @@ fn make_diff( } #[must_use] -#[allow(clippy::too_many_arguments)] -pub fn diff( - expected: &[u8], - expected_filename: &str, - actual: &[u8], - actual_filename: &str, - context_size: usize, - stop_early: bool, - expand_tabs: bool, - tabsize: usize, -) -> Vec { - let mut output = format!("*** {expected_filename}\t\n--- {actual_filename}\t\n").into_bytes(); - let diff_results = make_diff(expected, actual, context_size, stop_early); +pub fn diff(expected: &[u8], actual: &[u8], params: &Params) -> Vec { + let mut output = format!( + "*** {0}\t\n--- {1}\t\n", + params.from.to_string_lossy(), + params.to.to_string_lossy() + ) + .into_bytes(); + let diff_results = make_diff(expected, actual, params.context_count, params.brief); if diff_results.is_empty() { return Vec::new(); } - if stop_early { + if params.brief { return output; } for result in diff_results { @@ -319,19 +315,19 @@ pub fn diff( match line { DiffLine::Context(e) => { write!(output, " ").expect("write to Vec is infallible"); - do_write_line(&mut output, &e, expand_tabs, tabsize) + 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"); - do_write_line(&mut output, &e, expand_tabs, tabsize) + 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"); - do_write_line(&mut output, &e, expand_tabs, tabsize) + do_write_line(&mut output, &e, params.expand_tabs, params.tabsize) .expect("write to Vec is infallible"); writeln!(output).unwrap(); } @@ -349,19 +345,19 @@ pub fn diff( match line { DiffLine::Context(e) => { write!(output, " ").expect("write to Vec is infallible"); - do_write_line(&mut output, &e, expand_tabs, tabsize) + 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"); - do_write_line(&mut output, &e, expand_tabs, tabsize) + 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"); - do_write_line(&mut output, &e, expand_tabs, tabsize) + do_write_line(&mut output, &e, params.expand_tabs, params.tabsize) .expect("write to Vec is infallible"); writeln!(output).unwrap(); } @@ -430,13 +426,13 @@ mod tests { // We want it to turn the alef into bet. let diff = diff( &alef, - "a/alef", &bet, - &format!("{target}/alef"), - 2, - false, - false, - 8, + &Params { + from: "a/alef".into(), + to: (&format!("{target}/alef")).into(), + context_count: 2, + ..Default::default() + }, ); File::create(&format!("{target}/ab.diff")) .unwrap() @@ -511,13 +507,13 @@ mod tests { // We want it to turn the alef into bet. let diff = diff( &alef, - "a/alef_", &bet, - &format!("{target}/alef_"), - 2, - false, - false, - 8, + &Params { + from: "a/alef_".into(), + to: (&format!("{target}/alef_")).into(), + context_count: 2, + ..Default::default() + }, ); File::create(&format!("{target}/ab_.diff")) .unwrap() @@ -595,13 +591,13 @@ mod tests { // We want it to turn the alef into bet. let diff = diff( &alef, - "a/alefx", &bet, - &format!("{target}/alefx"), - 2, - false, - false, - 8, + &Params { + from: "a/alefx".into(), + to: (&format!("{target}/alefx")).into(), + context_count: 2, + ..Default::default() + }, ); File::create(&format!("{target}/abx.diff")) .unwrap() @@ -682,13 +678,13 @@ mod tests { // We want it to turn the alef into bet. let diff = diff( &alef, - "a/alefr", &bet, - &format!("{target}/alefr"), - 2, - false, - false, - 8, + &Params { + from: "a/alefr".into(), + to: (&format!("{target}/alefr")).into(), + context_count: 2, + ..Default::default() + }, ); File::create(&format!("{target}/abr.diff")) .unwrap() @@ -725,17 +721,15 @@ mod tests { let from = ["a", "b", "c", ""].join("\n"); let to_filename = "bar"; let to = ["a", "d", "c", ""].join("\n"); - let context_size: usize = 3; let diff_full = diff( from.as_bytes(), - from_filename, to.as_bytes(), - to_filename, - context_size, - false, - false, - 8, + &Params { + from: from_filename.into(), + to: to_filename.into(), + ..Default::default() + }, ); let expected_full = [ "*** foo\t", @@ -756,38 +750,37 @@ mod tests { let diff_brief = diff( from.as_bytes(), - from_filename, to.as_bytes(), - to_filename, - context_size, - true, - false, - 8, + &Params { + from: from_filename.into(), + to: to_filename.into(), + brief: true, + ..Default::default() + }, ); let expected_brief = ["*** foo\t", "--- bar\t", ""].join("\n"); assert_eq!(diff_brief, expected_brief.as_bytes()); let nodiff_full = diff( from.as_bytes(), - from_filename, from.as_bytes(), - to_filename, - context_size, - false, - false, - 8, + &Params { + from: from_filename.into(), + to: to_filename.into(), + ..Default::default() + }, ); assert!(nodiff_full.is_empty()); let nodiff_brief = diff( from.as_bytes(), - from_filename, from.as_bytes(), - to_filename, - context_size, - true, - false, - 8, + &Params { + from: from_filename.into(), + to: to_filename.into(), + brief: true, + ..Default::default() + }, ); assert!(nodiff_brief.is_empty()); } diff --git a/src/ed_diff.rs b/src/ed_diff.rs index c02289c..1be37a5 100644 --- a/src/ed_diff.rs +++ b/src/ed_diff.rs @@ -5,6 +5,7 @@ use std::io::Write; +use crate::params::Params; use crate::utils::do_write_line; #[derive(Debug, PartialEq)] @@ -109,16 +110,10 @@ fn make_diff(expected: &[u8], actual: &[u8], stop_early: bool) -> Result Result, DiffError> { +pub fn diff(expected: &[u8], actual: &[u8], params: &Params) -> Result, DiffError> { let mut output = Vec::new(); - let diff_results = make_diff(expected, actual, stop_early)?; - if stop_early && !diff_results.is_empty() { + let diff_results = make_diff(expected, actual, params.brief)?; + if params.brief && !diff_results.is_empty() { write!(&mut output, "\0").unwrap(); return Ok(output); } @@ -153,7 +148,7 @@ pub fn diff( if actual == b"." { writeln!(&mut output, "..\n.\ns/.//\na").unwrap(); } else { - do_write_line(&mut output, actual, expand_tabs, tabsize).unwrap(); + do_write_line(&mut output, actual, params.expand_tabs, params.tabsize).unwrap(); writeln!(&mut output).unwrap(); } } @@ -168,7 +163,7 @@ mod tests { use super::*; use pretty_assertions::assert_eq; pub fn diff_w(expected: &[u8], actual: &[u8], filename: &str) -> Result, DiffError> { - let mut output = diff(expected, actual, false, false, 8)?; + let mut output = diff(expected, actual, &Params::default())?; writeln!(&mut output, "w {filename}").unwrap(); Ok(output) } @@ -177,7 +172,7 @@ mod tests { fn test_basic() { let from = b"a\n"; let to = b"b\n"; - let diff = diff(from, to, false, false, 8).unwrap(); + let diff = diff(from, to, &Params::default()).unwrap(); let expected = ["1c", "b", ".", ""].join("\n"); assert_eq!(diff, expected.as_bytes()); } @@ -412,18 +407,34 @@ mod tests { let from = ["a", "b", "c", ""].join("\n"); let to = ["a", "d", "c", ""].join("\n"); - let diff_full = diff(from.as_bytes(), to.as_bytes(), false, false, 8).unwrap(); + 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(), true, false, 8).unwrap(); + 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(), false, false, 8).unwrap(); + 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(), true, false, 8).unwrap(); + let nodiff_brief = diff( + from.as_bytes(), + from.as_bytes(), + &Params { + brief: true, + ..Default::default() + }, + ) + .unwrap(); assert!(nodiff_brief.is_empty()); } } diff --git a/src/lib.rs b/src/lib.rs index faf5df2..7ed36a6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ pub mod context_diff; pub mod ed_diff; pub mod normal_diff; +pub mod params; pub mod unified_diff; pub mod utils; diff --git a/src/main.rs b/src/main.rs index 2a6d4ca..dab0eff 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,7 @@ // 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::fs; @@ -24,42 +24,33 @@ mod utils; // and 2 means trouble. fn main() -> ExitCode { let opts = env::args_os(); - let Params { - from, - to, - context_count, - format, - report_identical_files, - brief, - expand_tabs, - tabsize, - } = parse_params(opts).unwrap_or_else(|error| { + 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 report_identical_files { + if params.report_identical_files { println!( "Files {} and {} are identical", - from.to_string_lossy(), - to.to_string_lossy(), + params.from.to_string_lossy(), + params.to.to_string_lossy(), ) } }; - if same_file::is_same_file(&from, &to).unwrap_or(false) { + if same_file::is_same_file(¶ms.from, ¶ms.to).unwrap_or(false) { maybe_report_identical_files(); return ExitCode::SUCCESS; } // read files - let from_content = match fs::read(&from) { + let from_content = match fs::read(¶ms.from) { Ok(from_content) => from_content, Err(e) => { eprintln!("Failed to read from-file: {e}"); return ExitCode::from(2); } }; - let to_content = match fs::read(&to) { + let to_content = match fs::read(¶ms.to) { Ok(to_content) => to_content, Err(e) => { eprintln!("Failed to read to-file: {e}"); @@ -67,41 +58,20 @@ fn main() -> ExitCode { } }; // run diff - let result: Vec = match format { - Format::Normal => { - normal_diff::diff(&from_content, &to_content, brief, expand_tabs, tabsize) - } - Format::Unified => unified_diff::diff( - &from_content, - &from.to_string_lossy(), - &to_content, - &to.to_string_lossy(), - context_count, - brief, - expand_tabs, - tabsize, - ), - Format::Context => context_diff::diff( - &from_content, - &from.to_string_lossy(), - &to_content, - &to.to_string_lossy(), - context_count, - brief, - expand_tabs, - tabsize, - ), - Format::Ed => ed_diff::diff(&from_content, &to_content, brief, expand_tabs, tabsize) - .unwrap_or_else(|error| { - eprintln!("{error}"); - exit(2); - }), + let result: Vec = 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 brief && !result.is_empty() { + if params.brief && !result.is_empty() { println!( "Files {} and {} differ", - from.to_string_lossy(), - to.to_string_lossy() + params.from.to_string_lossy(), + params.to.to_string_lossy() ); } else { io::stdout().write_all(&result).unwrap(); diff --git a/src/normal_diff.rs b/src/normal_diff.rs index b26de77..cfa389c 100644 --- a/src/normal_diff.rs +++ b/src/normal_diff.rs @@ -5,6 +5,7 @@ use std::io::Write; +use crate::params::Params; use crate::utils::do_write_line; #[derive(Debug, PartialEq)] @@ -116,18 +117,12 @@ fn make_diff(expected: &[u8], actual: &[u8], stop_early: bool) -> Vec } #[must_use] -pub fn diff( - expected: &[u8], - actual: &[u8], - stop_early: bool, - expand_tabs: bool, - tabsize: usize, -) -> Vec { +pub fn diff(expected: &[u8], actual: &[u8], params: &Params) -> Vec { // 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, stop_early); - if stop_early && !diff_results.is_empty() { + let diff_results = make_diff(expected, actual, params.brief); + if params.brief && !diff_results.is_empty() { write!(&mut output, "\0").unwrap(); return output; } @@ -196,7 +191,7 @@ pub fn diff( } for expected in &result.expected { write!(&mut output, "< ").unwrap(); - do_write_line(&mut output, expected, expand_tabs, tabsize).unwrap(); + do_write_line(&mut output, expected, params.expand_tabs, params.tabsize).unwrap(); writeln!(&mut output).unwrap(); } if result.expected_missing_nl { @@ -207,7 +202,7 @@ pub fn diff( } for actual in &result.actual { write!(&mut output, "> ").unwrap(); - do_write_line(&mut output, actual, expand_tabs, tabsize).unwrap(); + do_write_line(&mut output, actual, params.expand_tabs, params.tabsize).unwrap(); writeln!(&mut output).unwrap(); } if result.actual_missing_nl { @@ -228,7 +223,7 @@ mod tests { a.write_all(b"a\n").unwrap(); let mut b = Vec::new(); b.write_all(b"b\n").unwrap(); - let diff = diff(&a, &b, false, false, 8); + let diff = diff(&a, &b, &Params::default()); let expected = b"1c1\n< a\n---\n> b\n".to_vec(); assert_eq!(diff, expected); } @@ -281,7 +276,7 @@ mod tests { } // This test diff is intentionally reversed. // We want it to turn the alef into bet. - let diff = diff(&alef, &bet, false, false, 8); + let diff = diff(&alef, &bet, &Params::default()); File::create(&format!("{target}/ab.diff")) .unwrap() .write_all(&diff) @@ -373,7 +368,7 @@ mod tests { } // This test diff is intentionally reversed. // We want it to turn the alef into bet. - let diff = diff(&alef, &bet, false, false, 8); + let diff = diff(&alef, &bet, &Params::default()); File::create(&format!("{target}/abn.diff")) .unwrap() .write_all(&diff) @@ -447,7 +442,7 @@ mod tests { } // This test diff is intentionally reversed. // We want it to turn the alef into bet. - let diff = diff(&alef, &bet, false, false, 8); + let diff = diff(&alef, &bet, &Params::default()); File::create(&format!("{target}/ab_.diff")) .unwrap() .write_all(&diff) @@ -525,7 +520,7 @@ mod tests { } // This test diff is intentionally reversed. // We want it to turn the alef into bet. - let diff = diff(&alef, &bet, false, false, 8); + let diff = diff(&alef, &bet, &Params::default()); File::create(&format!("{target}/abr.diff")) .unwrap() .write_all(&diff) @@ -560,18 +555,32 @@ mod tests { let from = ["a", "b", "c"].join("\n"); let to = ["a", "d", "c"].join("\n"); - let diff_full = diff(from.as_bytes(), to.as_bytes(), false, false, 8); + 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(), true, false, 8); + 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(), false, false, 8); + 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(), true, false, 8); + let nodiff_brief = diff( + from.as_bytes(), + from.as_bytes(), + &Params { + brief: true, + ..Default::default() + }, + ); assert!(nodiff_brief.is_empty()); } } diff --git a/src/unified_diff.rs b/src/unified_diff.rs index 0d3ec38..3af51c2 100644 --- a/src/unified_diff.rs +++ b/src/unified_diff.rs @@ -6,6 +6,7 @@ use std::collections::VecDeque; use std::io::Write; +use crate::params::Params; use crate::utils::do_write_line; #[derive(Debug, PartialEq)] @@ -236,23 +237,18 @@ fn make_diff( } #[must_use] -#[allow(clippy::too_many_arguments)] -pub fn diff( - expected: &[u8], - expected_filename: &str, - actual: &[u8], - actual_filename: &str, - context_size: usize, - stop_early: bool, - expand_tabs: bool, - tabsize: usize, -) -> Vec { - let mut output = format!("--- {expected_filename}\t\n+++ {actual_filename}\t\n").into_bytes(); - let diff_results = make_diff(expected, actual, context_size, stop_early); +pub fn diff(expected: &[u8], actual: &[u8], params: &Params) -> Vec { + let mut output = format!( + "--- {0}\t\n+++ {1}\t\n", + params.from.to_string_lossy(), + params.to.to_string_lossy() + ) + .into_bytes(); + let diff_results = make_diff(expected, actual, params.context_count, params.brief); if diff_results.is_empty() { return Vec::new(); } - if stop_early { + if params.brief { return output; } for result in diff_results { @@ -376,19 +372,19 @@ pub fn diff( match line { DiffLine::Expected(e) => { write!(output, "-").expect("write to Vec is infallible"); - do_write_line(&mut output, &e, expand_tabs, tabsize) + do_write_line(&mut output, &e, params.expand_tabs, params.tabsize) .expect("write to Vec is infallible"); writeln!(output).unwrap(); } DiffLine::Context(c) => { write!(output, " ").expect("write to Vec is infallible"); - do_write_line(&mut output, &c, expand_tabs, tabsize) + do_write_line(&mut output, &c, params.expand_tabs, params.tabsize) .expect("write to Vec is infallible"); writeln!(output).unwrap(); } DiffLine::Actual(r) => { write!(output, "+",).expect("write to Vec is infallible"); - do_write_line(&mut output, &r, expand_tabs, tabsize) + do_write_line(&mut output, &r, params.expand_tabs, params.tabsize) .expect("write to Vec is infallible"); writeln!(output).unwrap(); } @@ -457,13 +453,13 @@ mod tests { // We want it to turn the alef into bet. let diff = diff( &alef, - "a/alef", &bet, - &format!("{target}/alef"), - 2, - false, - false, - 8, + &Params { + from: "a/alef".into(), + to: (&format!("{target}/alef")).into(), + context_count: 2, + ..Default::default() + }, ); File::create(&format!("{target}/ab.diff")) .unwrap() @@ -573,13 +569,13 @@ mod tests { // We want it to turn the alef into bet. let diff = diff( &alef, - "a/alefn", &bet, - &format!("{target}/alefn"), - 2, - false, - false, - 8, + &Params { + from: "a/alefn".into(), + to: (&format!("{target}/alefn")).into(), + context_count: 2, + ..Default::default() + }, ); File::create(&format!("{target}/abn.diff")) .unwrap() @@ -669,13 +665,13 @@ mod tests { // We want it to turn the alef into bet. let diff = diff( &alef, - "a/alef_", &bet, - &format!("{target}/alef_"), - 2, - false, - false, - 8, + &Params { + from: "a/alef_".into(), + to: (&format!("{target}/alef_")).into(), + context_count: 2, + ..Default::default() + }, ); File::create(&format!("{target}/ab_.diff")) .unwrap() @@ -750,13 +746,13 @@ mod tests { // We want it to turn the alef into bet. let diff = diff( &alef, - "a/alefx", &bet, - &format!("{target}/alefx"), - 2, - false, - false, - 8, + &Params { + from: "a/alefx".into(), + to: (&format!("{target}/alefx")).into(), + context_count: 2, + ..Default::default() + }, ); File::create(&format!("{target}/abx.diff")) .unwrap() @@ -836,13 +832,13 @@ mod tests { // We want it to turn the alef into bet. let diff = diff( &alef, - "a/alefr", &bet, - &format!("{target}/alefr"), - 2, - false, - false, - 8, + &Params { + from: "a/alefr".into(), + to: (&format!("{target}/alefr")).into(), + context_count: 2, + ..Default::default() + }, ); File::create(&format!("{target}/abr.diff")) .unwrap() @@ -878,17 +874,15 @@ mod tests { let from = ["a", "b", "c", ""].join("\n"); let to_filename = "bar"; let to = ["a", "d", "c", ""].join("\n"); - let context_size: usize = 3; let diff_full = diff( from.as_bytes(), - from_filename, to.as_bytes(), - to_filename, - context_size, - false, - false, - 8, + &Params { + from: from_filename.into(), + to: to_filename.into(), + ..Default::default() + }, ); let expected_full = [ "--- foo\t", @@ -905,38 +899,37 @@ mod tests { let diff_brief = diff( from.as_bytes(), - from_filename, to.as_bytes(), - to_filename, - context_size, - true, - false, - 8, + &Params { + from: from_filename.into(), + to: to_filename.into(), + brief: true, + ..Default::default() + }, ); let expected_brief = ["--- foo\t", "+++ bar\t", ""].join("\n"); assert_eq!(diff_brief, expected_brief.as_bytes()); let nodiff_full = diff( from.as_bytes(), - from_filename, from.as_bytes(), - to_filename, - context_size, - false, - false, - 8, + &Params { + from: from_filename.into(), + to: to_filename.into(), + ..Default::default() + }, ); assert!(nodiff_full.is_empty()); let nodiff_brief = diff( from.as_bytes(), - from_filename, from.as_bytes(), - to_filename, - context_size, - true, - false, - 8, + &Params { + from: from_filename.into(), + to: to_filename.into(), + brief: true, + ..Default::default() + }, ); assert!(nodiff_brief.is_empty()); } From e9f0630aafbfd8ac33271b59cba8f9cf2be73e7e Mon Sep 17 00:00:00 2001 From: Olivier Tilloy Date: Sun, 31 Mar 2024 23:39:43 +0200 Subject: [PATCH 27/61] Update fuzzers --- fuzz/fuzz_targets/fuzz_ed.rs | 3 ++- fuzz/fuzz_targets/fuzz_normal.rs | 3 ++- fuzz/fuzz_targets/fuzz_patch.rs | 13 +++++++------ 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/fuzz/fuzz_targets/fuzz_ed.rs b/fuzz/fuzz_targets/fuzz_ed.rs index 69461d1..7c38fda 100644 --- a/fuzz/fuzz_targets/fuzz_ed.rs +++ b/fuzz/fuzz_targets/fuzz_ed.rs @@ -3,12 +3,13 @@ extern crate libfuzzer_sys; 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, DiffError> { - let mut output = ed_diff::diff(expected, actual, false, false, 8)?; + let mut output = ed_diff::diff(expected, actual, &Params::default())?; writeln!(&mut output, "w {filename}").unwrap(); Ok(output) } diff --git a/fuzz/fuzz_targets/fuzz_normal.rs b/fuzz/fuzz_targets/fuzz_normal.rs index 2d38641..6b1e6b9 100644 --- a/fuzz/fuzz_targets/fuzz_normal.rs +++ b/fuzz/fuzz_targets/fuzz_normal.rs @@ -2,6 +2,7 @@ #[macro_use] extern crate libfuzzer_sys; 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, Vec)| { } else { return }*/ - let diff = normal_diff::diff(&from, &to, false, false, 8); + let diff = normal_diff::diff(&from, &to, &Params::default()); File::create("target/fuzz.file.original") .unwrap() .write_all(&from) diff --git a/fuzz/fuzz_targets/fuzz_patch.rs b/fuzz/fuzz_targets/fuzz_patch.rs index 15e4967..4dea4b5 100644 --- a/fuzz/fuzz_targets/fuzz_patch.rs +++ b/fuzz/fuzz_targets/fuzz_patch.rs @@ -1,6 +1,7 @@ #![no_main] #[macro_use] extern crate libfuzzer_sys; +use diffutilslib::params::Params; use diffutilslib::unified_diff; use std::fs::{self, File}; use std::io::Write; @@ -22,13 +23,13 @@ fuzz_target!(|x: (Vec, Vec, u8)| { }*/ let diff = unified_diff::diff( &from, - "a/fuzz.file", &to, - "target/fuzz.file", - context as usize, - false, - false, - 8, + &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() From 6a73657b3a1014ffa23b23a5238fb7b8980994ca Mon Sep 17 00:00:00 2001 From: Olivier Tilloy Date: Mon, 1 Apr 2024 00:26:30 +0200 Subject: [PATCH 28/61] README: minor grammar correction --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1308648..62be861 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![CodeCov](https://codecov.io/gh/uutils/diffutils/branch/master/graph/badge.svg)](https://codecov.io/gh/uutils/diffutils) -The goal of this package is to be a dropped in replacement for the [diffutils commands](https://www.gnu.org/software/diffutils/) in Rust. +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. From 76c4714f78ffbe09554b6ced8a302a7e1274f980 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Mon, 1 Apr 2024 23:15:23 +0200 Subject: [PATCH 29/61] Disable the fail fast (closes: #40) --- .github/workflows/ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d416f74..8b8e474 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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,6 +22,7 @@ jobs: name: cargo test runs-on: ${{ matrix.os }} strategy: + fail-fast: false matrix: os: [ubuntu-latest, macOS-latest, windows-latest] steps: @@ -41,6 +43,7 @@ jobs: name: cargo clippy -- -D warnings runs-on: ${{ matrix.os }} strategy: + fail-fast: false matrix: os: [ubuntu-latest, macOS-latest, windows-latest] steps: @@ -70,6 +73,7 @@ jobs: name: Code Coverage runs-on: ${{ matrix.job.os }} strategy: + fail-fast: false matrix: job: - { os: ubuntu-latest , features: unix } From 589039ab4c6f61406e7f23094edbe2bbfb2557b2 Mon Sep 17 00:00:00 2001 From: Olivier Tilloy Date: Tue, 2 Apr 2024 18:22:40 +0200 Subject: [PATCH 30/61] Fix the link to the Codecov badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 62be861..552df09 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![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/master/graph/badge.svg)](https://codecov.io/gh/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. From a213272d0c8a8490258e8e983c31963a129b962b Mon Sep 17 00:00:00 2001 From: Olivier Tilloy Date: Tue, 2 Apr 2024 20:17:02 +0200 Subject: [PATCH 31/61] Add tests for when '-' is used to signify to use standard input --- src/params.rs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/params.rs b/src/params.rs index 6a6d261..b913a40 100644 --- a/src/params.rs +++ b/src/params.rs @@ -457,6 +457,35 @@ mod tests { ); } #[test] + fn default_to_stdin() { + assert_eq!( + Ok(Params { + from: os("foo"), + to: os("/dev/stdin"), + ..Default::default() + }), + parse_params([os("diff"), os("foo"), os("-")].iter().cloned()) + ); + assert_eq!( + Ok(Params { + from: os("/dev/stdin"), + to: os("bar"), + ..Default::default() + }), + parse_params([os("diff"), os("-"), os("bar")].iter().cloned()) + ); + assert_eq!( + Ok(Params { + from: os("/dev/stdin"), + to: os("/dev/stdin"), + ..Default::default() + }), + parse_params([os("diff"), os("-"), os("-")].iter().cloned()) + ); + assert!(parse_params([os("diff"), os("foo"), os("bar"), os("-")].iter().cloned()).is_err()); + assert!(parse_params([os("diff"), os("-"), os("-"), os("-")].iter().cloned()).is_err()); + } + #[test] fn unknown_argument() { assert!( parse_params([os("diff"), os("-g"), os("foo"), os("bar")].iter().cloned()).is_err() From b8fada8faa568e8e8024632bf8184a8194274732 Mon Sep 17 00:00:00 2001 From: Olivier Tilloy Date: Tue, 2 Apr 2024 21:10:41 +0200 Subject: [PATCH 32/61] Add unit tests for missing arguments --- src/params.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/params.rs b/src/params.rs index b913a40..7017646 100644 --- a/src/params.rs +++ b/src/params.rs @@ -486,6 +486,11 @@ mod tests { assert!(parse_params([os("diff"), os("-"), os("-"), os("-")].iter().cloned()).is_err()); } #[test] + fn missing_arguments() { + assert!(parse_params([os("diff")].iter().cloned()).is_err()); + assert!(parse_params([os("diff"), os("foo")].iter().cloned()).is_err()); + } + #[test] fn unknown_argument() { assert!( parse_params([os("diff"), os("-g"), os("foo"), os("bar")].iter().cloned()).is_err() From 6a152cdc7f203408331c3e337a86b2200303e1a3 Mon Sep 17 00:00:00 2001 From: Olivier Tilloy Date: Tue, 2 Apr 2024 22:34:42 +0200 Subject: [PATCH 33/61] Add an integration test for reading data from stdin --- tests/integration.rs | 46 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/tests/integration.rs b/tests/integration.rs index be1320e..5ad624e 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -3,10 +3,9 @@ // For the full copyright and license information, please view the LICENSE-* // files that was distributed with this source code. -use assert_cmd::prelude::*; +use assert_cmd::cmd::Command; use predicates::prelude::*; use std::io::Write; -use std::process::Command; use tempfile::NamedTempFile; // Integration tests for the diffutils command @@ -161,3 +160,46 @@ fn missing_newline() -> Result<(), Box> { .stderr(predicate::str::starts_with("No newline at end of file")); Ok(()) } + +#[test] +fn read_from_stdin() -> Result<(), Box> { + let mut file1 = NamedTempFile::new()?; + file1.write_all("foo\n".as_bytes())?; + let mut file2 = NamedTempFile::new()?; + file2.write_all("bar\n".as_bytes())?; + + let mut cmd = Command::cargo_bin("diffutils")?; + cmd.arg("-u") + .arg(file1.path()) + .arg("-") + .write_stdin("bar\n"); + cmd.assert() + .code(predicate::eq(1)) + .failure() + .stdout(predicate::eq(format!( + "--- {}\t\n+++ /dev/stdin\t\n@@ -1 +1 @@\n-foo\n+bar\n", + file1.path().to_string_lossy() + ))); + + let mut cmd = Command::cargo_bin("diffutils")?; + cmd.arg("-u") + .arg("-") + .arg(file2.path()) + .write_stdin("foo\n"); + cmd.assert() + .code(predicate::eq(1)) + .failure() + .stdout(predicate::eq(format!( + "--- /dev/stdin\t\n+++ {}\t\n@@ -1 +1 @@\n-foo\n+bar\n", + file2.path().to_string_lossy() + ))); + + let mut cmd = Command::cargo_bin("diffutils")?; + cmd.arg("-u").arg("-").arg("-").write_stdin("foo\n"); + cmd.assert() + .code(predicate::eq(0)) + .success() + .stdout(predicate::str::is_empty()); + + Ok(()) +} From 5b814f853040a0d2e6204b65e8e19e2354fe72f1 Mon Sep 17 00:00:00 2001 From: Tanmay Patil Date: Wed, 3 Apr 2024 10:50:52 +0530 Subject: [PATCH 34/61] Fix tests --- src/context_diff.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/context_diff.rs b/src/context_diff.rs index 9254e9b..d60b6ef 100644 --- a/src/context_diff.rs +++ b/src/context_diff.rs @@ -444,6 +444,7 @@ mod tests { bet.write_all(b"l\n").unwrap(); } let _ = File::create(&format!("{target}/aalef")).unwrap(); + let mut fa = File::create(&format!("{target}/alef")).unwrap(); // This test diff is intentionally reversed. // We want it to turn the alef into bet. let diff = diff( @@ -460,7 +461,6 @@ mod tests { .unwrap() .write_all(&diff) .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(); @@ -526,6 +526,7 @@ mod tests { bet.write_all(b"l\n").unwrap(); } let _ = File::create(&format!("{target}/aalef_")).unwrap(); + let mut fa = File::create(&format!("{target}/alef_")).unwrap(); // This test diff is intentionally reversed. // We want it to turn the alef into bet. let diff = diff( @@ -542,7 +543,6 @@ mod tests { .unwrap() .write_all(&diff) .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(); @@ -611,6 +611,7 @@ mod tests { continue; }; let _ = File::create(&format!("{target}/aalefx")).unwrap(); + let mut fa = File::create(&format!("{target}/alefx")).unwrap(); // This test diff is intentionally reversed. // We want it to turn the alef into bet. let diff = diff( @@ -627,7 +628,6 @@ mod tests { .unwrap() .write_all(&diff) .unwrap(); - let mut fa = File::create(&format!("{target}/alefx")).unwrap(); fa.write_all(&alef[..]).unwrap(); let mut fb = File::create(&format!("{target}/betx")).unwrap(); fb.write_all(&bet[..]).unwrap(); @@ -699,6 +699,7 @@ mod tests { bet.write_all(b"f\n").unwrap(); } let _ = File::create(&format!("{target}/aalefr")).unwrap(); + let mut fa = File::create(&format!("{target}/alefr")).unwrap(); // This test diff is intentionally reversed. // We want it to turn the alef into bet. let diff = diff( @@ -715,7 +716,6 @@ mod tests { .unwrap() .write_all(&diff) .unwrap(); - let mut fa = File::create(&format!("{target}/alefr")).unwrap(); fa.write_all(&alef[..]).unwrap(); let mut fb = File::create(&format!("{target}/betr")).unwrap(); fb.write_all(&bet[..]).unwrap(); From a3a372ff362992df34193c2b8d4c9026f9eb576e Mon Sep 17 00:00:00 2001 From: Tanmay Patil Date: Thu, 4 Apr 2024 00:09:57 +0530 Subject: [PATCH 35/61] Display modification times of input files in unified diff --- src/context_diff.rs | 19 +----------- src/unified_diff.rs | 70 +++++++++++++++++++++++++++++++++------------ src/utils.rs | 16 +++++++++++ 3 files changed, 68 insertions(+), 37 deletions(-) diff --git a/src/context_diff.rs b/src/context_diff.rs index d60b6ef..b34295f 100644 --- a/src/context_diff.rs +++ b/src/context_diff.rs @@ -8,6 +8,7 @@ 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 { @@ -265,24 +266,7 @@ fn make_diff( results } -fn get_modification_time(file_path: &str) -> String { - use chrono::{DateTime, Local}; - use std::fs; - - let metadata = fs::metadata(file_path).expect("Failed to get metadata"); - let modification_time = metadata - .modified() - .expect("Failed to get modification time"); - let modification_time: DateTime = modification_time.into(); - let modification_time: String = modification_time - .format("%Y-%m-%d %H:%M:%S%.9f %z") - .to_string(); - - modification_time -} - #[must_use] -#[allow(clippy::too_many_arguments)] pub fn diff(expected: &[u8], actual: &[u8], params: &Params) -> Vec { let from_modified_time = get_modification_time(¶ms.from.to_string_lossy()); let to_modified_time = get_modification_time(¶ms.to.to_string_lossy()); @@ -747,7 +731,6 @@ mod tests { use std::str; let target = "target/context-diff"; - // test all possible six-line files. let _ = std::fs::create_dir(target); let from_filename = &format!("{target}/foo"); let _ = File::create(from_filename).unwrap(); diff --git a/src/unified_diff.rs b/src/unified_diff.rs index 3af51c2..c5eab49 100644 --- a/src/unified_diff.rs +++ b/src/unified_diff.rs @@ -8,6 +8,7 @@ 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 { @@ -238,10 +239,14 @@ fn make_diff( #[must_use] pub fn diff(expected: &[u8], actual: &[u8], params: &Params) -> Vec { + 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\n+++ {1}\t\n", + "--- {0}\t{1}\n+++ {2}\t{3}\n", params.from.to_string_lossy(), - params.to.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); @@ -449,13 +454,15 @@ mod tests { if f != 2 { bet.write_all(b"l\n").unwrap(); } + let _ = File::create(&format!("{target}/aalef")).unwrap(); + let mut fa = File::create(&format!("{target}/alef")).unwrap(); // This test diff is intentionally reversed. // We want it to turn the alef into bet. let diff = diff( &alef, &bet, &Params { - from: "a/alef".into(), + from: (&format!("{target}/aalef")).into(), to: (&format!("{target}/alef")).into(), context_count: 2, ..Default::default() @@ -465,7 +472,6 @@ mod tests { .unwrap() .write_all(&diff) .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(); @@ -565,13 +571,15 @@ mod tests { } _ => unreachable!(), } + let _ = File::create(&format!("{target}/aalefn")).unwrap(); + let mut fa = File::create(&format!("{target}/alefn")).unwrap(); // This test diff is intentionally reversed. // We want it to turn the alef into bet. let diff = diff( &alef, &bet, &Params { - from: "a/alefn".into(), + from: (&format!("{target}/aalefn")).into(), to: (&format!("{target}/alefn")).into(), context_count: 2, ..Default::default() @@ -581,7 +589,6 @@ mod tests { .unwrap() .write_all(&diff) .unwrap(); - let mut fa = File::create(&format!("{target}/alefn")).unwrap(); fa.write_all(&alef[..]).unwrap(); let mut fb = File::create(&format!("{target}/betn")).unwrap(); fb.write_all(&bet[..]).unwrap(); @@ -661,13 +668,15 @@ mod tests { 3 => {} _ => unreachable!(), } + let _ = File::create(&format!("{target}/aalef_")).unwrap(); + let mut fa = File::create(&format!("{target}/alef_")).unwrap(); // This test diff is intentionally reversed. // We want it to turn the alef into bet. let diff = diff( &alef, &bet, &Params { - from: "a/alef_".into(), + from: (&format!("{target}/aalef_")).into(), to: (&format!("{target}/alef_")).into(), context_count: 2, ..Default::default() @@ -677,7 +686,6 @@ mod tests { .unwrap() .write_all(&diff) .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(); @@ -742,13 +750,15 @@ mod tests { if f != 2 { bet.write_all(b"l\n").unwrap(); } + let _ = File::create(&format!("{target}/aalefx")).unwrap(); + let mut fa = File::create(&format!("{target}/alefx")).unwrap(); // This test diff is intentionally reversed. // We want it to turn the alef into bet. let diff = diff( &alef, &bet, &Params { - from: "a/alefx".into(), + from: (&format!("{target}/aalefx")).into(), to: (&format!("{target}/alefx")).into(), context_count: 2, ..Default::default() @@ -758,7 +768,6 @@ mod tests { .unwrap() .write_all(&diff) .unwrap(); - let mut fa = File::create(&format!("{target}/alefx")).unwrap(); fa.write_all(&alef[..]).unwrap(); let mut fb = File::create(&format!("{target}/betx")).unwrap(); fb.write_all(&bet[..]).unwrap(); @@ -828,13 +837,15 @@ mod tests { if f != 2 { bet.write_all(b"f\n").unwrap(); } + let _ = File::create(&format!("{target}/aalefr")).unwrap(); + let mut fa = File::create(&format!("{target}/alefr")).unwrap(); // This test diff is intentionally reversed. // We want it to turn the alef into bet. let diff = diff( &alef, &bet, &Params { - from: "a/alefr".into(), + from: (&format!("{target}/aalefr")).into(), to: (&format!("{target}/alefr")).into(), context_count: 2, ..Default::default() @@ -844,7 +855,6 @@ mod tests { .unwrap() .write_all(&diff) .unwrap(); - let mut fa = File::create(&format!("{target}/alefr")).unwrap(); fa.write_all(&alef[..]).unwrap(); let mut fb = File::create(&format!("{target}/betr")).unwrap(); fb.write_all(&bet[..]).unwrap(); @@ -870,9 +880,17 @@ mod tests { #[test] fn test_stop_early() { - let from_filename = "foo"; + use regex::Regex; + use std::fs::File; + use std::str; + + let target = "target/context-diff"; + let _ = std::fs::create_dir(target); + let from_filename = &format!("{target}/foo"); + let _ = File::create(from_filename).unwrap(); let from = ["a", "b", "c", ""].join("\n"); - let to_filename = "bar"; + let to_filename = &format!("{target}/bar"); + let _ = File::create(to_filename).unwrap(); let to = ["a", "d", "c", ""].join("\n"); let diff_full = diff( @@ -884,9 +902,14 @@ mod tests { ..Default::default() }, ); + + let diff_full_text = str::from_utf8(&diff_full).unwrap(); + let re = Regex::new(r"\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d+ [+-]\d{4}").unwrap(); + let diff_full = re.replace_all(diff_full_text, ""); + let expected_full = [ - "--- foo\t", - "+++ bar\t", + "--- target/context-diff/foo\t", + "+++ target/context-diff/bar\t", "@@ -1,3 +1,3 @@", " a", "-b", @@ -895,7 +918,7 @@ mod tests { "", ] .join("\n"); - assert_eq!(diff_full, expected_full.as_bytes()); + assert_eq!(diff_full, expected_full); let diff_brief = diff( from.as_bytes(), @@ -907,8 +930,17 @@ mod tests { ..Default::default() }, ); - let expected_brief = ["--- foo\t", "+++ bar\t", ""].join("\n"); - assert_eq!(diff_brief, expected_brief.as_bytes()); + + let diff_brief_text = str::from_utf8(&diff_brief).unwrap(); + let re = Regex::new(r"\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d+ [+-]\d{4}").unwrap(); + let diff_brief = re.replace_all(diff_brief_text, ""); + let expected_brief = [ + "--- target/context-diff/foo\t", + "+++ target/context-diff/bar\t", + "", + ] + .join("\n"); + assert_eq!(diff_brief, expected_brief); let nodiff_full = diff( from.as_bytes(), diff --git a/src/utils.rs b/src/utils.rs index 94d950f..e05fca6 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -51,6 +51,22 @@ pub fn do_write_line( } } +pub fn get_modification_time(file_path: &str) -> String { + use chrono::{DateTime, Local}; + use std::fs; + + let metadata = fs::metadata(file_path).expect("Failed to get metadata"); + let modification_time = metadata + .modified() + .expect("Failed to get modification time"); + let modification_time: DateTime = modification_time.into(); + let modification_time: String = modification_time + .format("%Y-%m-%d %H:%M:%S%.9f %z") + .to_string(); + + modification_time +} + #[cfg(test)] mod tests { use super::*; From 281098d75157944a1b1815ccaea271f501c5c25f Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Wed, 3 Apr 2024 23:25:31 +0200 Subject: [PATCH 36/61] Run clippy pedantic fixes --- src/context_diff.rs | 8 ++++---- src/ed_diff.rs | 8 ++++---- src/main.rs | 2 +- src/normal_diff.rs | 12 +++++------- src/params.rs | 2 +- src/unified_diff.rs | 10 +++++----- src/utils.rs | 1 + 7 files changed, 21 insertions(+), 22 deletions(-) diff --git a/src/context_diff.rs b/src/context_diff.rs index 9c567d8..f0c1caa 100644 --- a/src/context_diff.rs +++ b/src/context_diff.rs @@ -450,7 +450,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(); @@ -531,7 +531,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(); @@ -615,7 +615,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(); @@ -702,7 +702,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(); diff --git a/src/ed_diff.rs b/src/ed_diff.rs index 1be37a5..d2d8499 100644 --- a/src/ed_diff.rs +++ b/src/ed_diff.rs @@ -133,7 +133,7 @@ pub fn diff(expected: &[u8], actual: &[u8], params: &Params) -> Result, expected_count + line_number_expected - 1 ) .unwrap(), - (1, _) => writeln!(&mut output, "{}c", line_number_expected).unwrap(), + (1, _) => writeln!(&mut output, "{line_number_expected}c").unwrap(), _ => writeln!( &mut output, "{},{}c", @@ -241,7 +241,7 @@ mod tests { .stdin(File::open("target/ab.ed").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(); @@ -312,7 +312,7 @@ mod tests { .stdin(File::open("target/ab_.ed").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("target/alef_").unwrap(); @@ -389,7 +389,7 @@ mod tests { .stdin(File::open("target/abr.ed").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(); diff --git a/src/main.rs b/src/main.rs index dab0eff..a19e646 100644 --- a/src/main.rs +++ b/src/main.rs @@ -35,7 +35,7 @@ fn main() -> ExitCode { "Files {} and {} are identical", params.from.to_string_lossy(), params.to.to_string_lossy(), - ) + ); } }; if same_file::is_same_file(¶ms.from, ¶ms.to).unwrap_or(false) { diff --git a/src/normal_diff.rs b/src/normal_diff.rs index cfa389c..d6f8297 100644 --- a/src/normal_diff.rs +++ b/src/normal_diff.rs @@ -155,9 +155,7 @@ pub fn diff(expected: &[u8], actual: &[u8], params: &Params) -> Vec { // 'c' stands for "Change lines" // exactly one line replaced by one line &mut output, - "{}c{}", - line_number_expected, - line_number_actual + "{line_number_expected}c{line_number_actual}" ) .unwrap(), (1, _) => writeln!( @@ -293,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(); @@ -386,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(); @@ -459,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(); @@ -537,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(); diff --git a/src/params.rs b/src/params.rs index 7017646..74e8d15 100644 --- a/src/params.rs +++ b/src/params.rs @@ -99,7 +99,7 @@ pub fn parse_params>(opts: I) -> Result() { Ok(num) => num, - Err(_) => return Err(format!("invalid tabsize «{}»", tabsize_str)), + Err(_) => return Err(format!("invalid tabsize «{tabsize_str}»")), }; continue; } diff --git a/src/unified_diff.rs b/src/unified_diff.rs index 3af51c2..97ce764 100644 --- a/src/unified_diff.rs +++ b/src/unified_diff.rs @@ -494,7 +494,7 @@ mod tests { .unwrap(); println!("{}", String::from_utf8_lossy(&output.stdout)); println!("{}", String::from_utf8_lossy(&output.stderr)); - assert!(output.status.success(), "{:?}", output); + assert!(output.status.success(), "{output:?}"); let alef = fs::read(&format!("{target}/alef")).unwrap(); assert_eq!(alef, bet); } @@ -592,7 +592,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(); @@ -688,7 +688,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(); @@ -769,7 +769,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(); @@ -855,7 +855,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(); diff --git a/src/utils.rs b/src/utils.rs index 94d950f..c503fd9 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -10,6 +10,7 @@ use unicode_width::UnicodeWidthStr; /// Replace tabs by spaces in the input line. /// Correctly handle multi-bytes characters. /// This assumes that line does not contain any line breaks (if it does, the result is undefined). +#[must_use] pub fn do_expand_tabs(line: &[u8], tabsize: usize) -> Vec { let tab = b'\t'; let ntabs = line.iter().filter(|c| **c == tab).count(); From bbfca84e1776d54a326925d289d02a931832701d Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 4 Apr 2024 00:29:50 +0200 Subject: [PATCH 37/61] chore: wow shiny new cargo-dist CI --- .github/workflows/release.yml | 266 ++++++++++++++++++++++++++++++++++ Cargo.toml | 18 +++ 2 files changed, 284 insertions(+) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..502a5ff --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,266 @@ +# Copyright 2022-2023, 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.12.0/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: + - uses: actions/checkout@v4 + with: + submodules: recursive + - uses: swatinem/rust-cache@v2 + - 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<> "$GITHUB_OUTPUT" + jq --raw-output ".artifacts[]?.path | select( . != null )" 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.12.0/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<> "$GITHUB_OUTPUT" + jq --raw-output ".artifacts[]?.path | select( . != null )" 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.12.0/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/*" diff --git a/Cargo.toml b/Cargo.toml index 4ddf5fb..7a3126d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,3 +25,21 @@ pretty_assertions = "1" assert_cmd = "2.0.14" predicates = "3.1.0" tempfile = "3.10.0" + +# 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.12.0" +# 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" From 44ef772e4ae5c3e388995162b0e428613d55a4b8 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Thu, 4 Apr 2024 00:30:46 +0200 Subject: [PATCH 38/61] release: version 0.4.0 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 15c81e6..43bbdbf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -75,7 +75,7 @@ checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" [[package]] name = "diffutils" -version = "0.3.0" +version = "0.4.0" dependencies = [ "assert_cmd", "diff", diff --git a/Cargo.toml b/Cargo.toml index 7a3126d..b512a7b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "diffutils" -version = "0.3.0" +version = "0.4.0" edition = "2021" description = "A CLI app for generating diff files" license = "MIT OR Apache-2.0" From 6be94d8683fea64d50eecb92c81e485c35033794 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 3 Apr 2024 22:31:44 +0000 Subject: [PATCH 39/61] chore(deps): update rust crate tempfile to 3.10.1 --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 43bbdbf..69c69d5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -293,9 +293,9 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.10.0" +version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a365e8cd18e44762ef95d87f284f4b5cd04107fec2ff3052bd6a3e6069669e67" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", "fastrand", diff --git a/Cargo.toml b/Cargo.toml index b512a7b..6de6dcc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ unicode-width = "0.1.11" pretty_assertions = "1" assert_cmd = "2.0.14" predicates = "3.1.0" -tempfile = "3.10.0" +tempfile = "3.10.1" # The profile that 'cargo dist' will build with [profile.dist] From 2d9e625a5bea309ef88e25677775d73e4c4fa322 Mon Sep 17 00:00:00 2001 From: Daniel Hofstetter Date: Thu, 4 Apr 2024 08:30:54 +0200 Subject: [PATCH 40/61] Disable tests on Windows that use ed --- src/ed_diff.rs | 45 +++++++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/src/ed_diff.rs b/src/ed_diff.rs index d2d8499..ba39046 100644 --- a/src/ed_diff.rs +++ b/src/ed_diff.rs @@ -236,12 +236,15 @@ 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:?}"); + #[cfg(not(windows))] // there's no ed on windows + { + 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(); @@ -307,12 +310,15 @@ mod tests { 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:?}"); + #[cfg(not(windows))] // there's no ed on windows + { + 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(); @@ -384,12 +390,15 @@ 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:?}"); + #[cfg(not(windows))] // there's no ed on windows + { + 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(); From 72da7fca402680988b3846e625d40fe00165f656 Mon Sep 17 00:00:00 2001 From: Tanmay Patil Date: Thu, 4 Apr 2024 20:01:11 +0530 Subject: [PATCH 41/61] Show current time if fs::metadata errors --- src/utils.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/utils.rs b/src/utils.rs index 1b600c1..e0592ef 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -55,11 +55,12 @@ pub fn do_write_line( pub fn get_modification_time(file_path: &str) -> String { use chrono::{DateTime, Local}; use std::fs; + use std::time::SystemTime; + + let modification_time: SystemTime = fs::metadata(file_path) + .and_then(|m| m.modified()) + .unwrap_or(SystemTime::now()); - let metadata = fs::metadata(file_path).expect("Failed to get metadata"); - let modification_time = metadata - .modified() - .expect("Failed to get modification time"); let modification_time: DateTime = modification_time.into(); let modification_time: String = modification_time .format("%Y-%m-%d %H:%M:%S%.9f %z") From c325291696b10de5a8fde65b5322d40163e2f74d Mon Sep 17 00:00:00 2001 From: Olivier Tilloy Date: Fri, 5 Apr 2024 23:22:26 +0200 Subject: [PATCH 42/61] Unit test to verify that conflicting output styles result in an error --- src/params.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/params.rs b/src/params.rs index 74e8d15..18f8949 100644 --- a/src/params.rs +++ b/src/params.rs @@ -502,4 +502,15 @@ mod tests { fn empty() { assert!(parse_params([].iter().cloned()).is_err()); } + #[test] + fn conflicting_output_styles() { + for (arg1, arg2) in [("-u", "-c"), ("-u", "-e"), ("-c", "-u"), ("-c", "-U42")] { + assert!(parse_params( + [os("diff"), os(arg1), os(arg2), os("foo"), os("bar")] + .iter() + .cloned() + ) + .is_err()); + } + } } From 6dc34fed44ea7bd8f87de41b40785f9cd6486cba Mon Sep 17 00:00:00 2001 From: Olivier Tilloy Date: Wed, 3 Apr 2024 22:18:12 +0200 Subject: [PATCH 43/61] Handle the rewrite of "-" to "/dev/stdin" in main to leave the filenames unchanged (fixes #46) --- src/main.rs | 14 ++++++++++---- src/params.rs | 12 ++++++------ tests/integration.rs | 27 ++++++++++++++++----------- 3 files changed, 32 insertions(+), 21 deletions(-) diff --git a/src/main.rs b/src/main.rs index a19e646..de4bbbb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,7 +5,7 @@ use crate::params::{parse_params, Format}; use std::env; - +use std::ffi::OsString; use std::fs; use std::io::{self, Write}; use std::process::{exit, ExitCode}; @@ -38,19 +38,25 @@ fn main() -> ExitCode { ); } }; - if same_file::is_same_file(¶ms.from, ¶ms.to).unwrap_or(false) { + 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 - let from_content = match fs::read(¶ms.from) { + fn read_file_contents(filepath: &OsString) -> io::Result> { + let stdin = OsString::from("/dev/stdin"); + fs::read(if filepath == "-" { &stdin } else { filepath }) + } + let from_content = match read_file_contents(¶ms.from) { Ok(from_content) => from_content, Err(e) => { eprintln!("Failed to read from-file: {e}"); return ExitCode::from(2); } }; - let to_content = match fs::read(¶ms.to) { + let to_content = match read_file_contents(¶ms.to) { Ok(to_content) => to_content, Err(e) => { eprintln!("Failed to read to-file: {e}"); diff --git a/src/params.rs b/src/params.rs index 18f8949..7e9cc78 100644 --- a/src/params.rs +++ b/src/params.rs @@ -67,9 +67,9 @@ pub fn parse_params>(opts: I) -> Result ", exe.to_string_lossy())); } @@ -461,14 +461,14 @@ mod tests { assert_eq!( Ok(Params { from: os("foo"), - to: os("/dev/stdin"), + to: os("-"), ..Default::default() }), parse_params([os("diff"), os("foo"), os("-")].iter().cloned()) ); assert_eq!( Ok(Params { - from: os("/dev/stdin"), + from: os("-"), to: os("bar"), ..Default::default() }), @@ -476,8 +476,8 @@ mod tests { ); assert_eq!( Ok(Params { - from: os("/dev/stdin"), - to: os("/dev/stdin"), + from: os("-"), + to: os("-"), ..Default::default() }), parse_params([os("diff"), os("-"), os("-")].iter().cloned()) diff --git a/tests/integration.rs b/tests/integration.rs index 5ad624e..240d60b 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -22,25 +22,30 @@ fn unknown_param() -> Result<(), Box> { } #[test] -fn cannot_read_from_file() -> Result<(), Box> { +fn cannot_read_files() -> Result<(), Box> { + let file = NamedTempFile::new()?; + let mut cmd = Command::cargo_bin("diffutils")?; - cmd.arg("foo.txt").arg("bar.txt"); + cmd.arg("foo.txt").arg(file.path()); cmd.assert() .code(predicate::eq(2)) .failure() .stderr(predicate::str::starts_with("Failed to read from-file")); - Ok(()) -} -#[test] -fn cannot_read_to_file() -> Result<(), Box> { - let file = NamedTempFile::new()?; let mut cmd = Command::cargo_bin("diffutils")?; - cmd.arg(file.path()).arg("bar.txt"); + cmd.arg(file.path()).arg("foo.txt"); cmd.assert() .code(predicate::eq(2)) .failure() .stderr(predicate::str::starts_with("Failed to read to-file")); + + let mut cmd = Command::cargo_bin("diffutils")?; + cmd.arg("foo.txt").arg("foo.txt"); + cmd.assert() + .code(predicate::eq(2)) + .failure() + .stderr(predicate::str::starts_with("Failed to read from-file")); + Ok(()) } @@ -177,7 +182,7 @@ fn read_from_stdin() -> Result<(), Box> { .code(predicate::eq(1)) .failure() .stdout(predicate::eq(format!( - "--- {}\t\n+++ /dev/stdin\t\n@@ -1 +1 @@\n-foo\n+bar\n", + "--- {}\t\n+++ -\t\n@@ -1 +1 @@\n-foo\n+bar\n", file1.path().to_string_lossy() ))); @@ -190,12 +195,12 @@ fn read_from_stdin() -> Result<(), Box> { .code(predicate::eq(1)) .failure() .stdout(predicate::eq(format!( - "--- /dev/stdin\t\n+++ {}\t\n@@ -1 +1 @@\n-foo\n+bar\n", + "--- -\t\n+++ {}\t\n@@ -1 +1 @@\n-foo\n+bar\n", file2.path().to_string_lossy() ))); let mut cmd = Command::cargo_bin("diffutils")?; - cmd.arg("-u").arg("-").arg("-").write_stdin("foo\n"); + cmd.arg("-u").arg("-").arg("-"); cmd.assert() .code(predicate::eq(0)) .success() From 84ad116845db263cfb04234fb509011f59cbb8f1 Mon Sep 17 00:00:00 2001 From: Olivier Tilloy Date: Thu, 4 Apr 2024 22:14:03 +0200 Subject: [PATCH 44/61] Use io::stdin() to read from standard input in a portable manner --- src/main.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main.rs b/src/main.rs index de4bbbb..6858f5e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,7 +7,7 @@ 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; @@ -46,8 +46,12 @@ fn main() -> ExitCode { } // read files fn read_file_contents(filepath: &OsString) -> io::Result> { - let stdin = OsString::from("/dev/stdin"); - fs::read(if filepath == "-" { &stdin } else { filepath }) + 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(¶ms.from) { Ok(from_content) => from_content, From e1c319f96b371bf80864399490a39a74ecef2e6b Mon Sep 17 00:00:00 2001 From: Olivier Tilloy Date: Mon, 8 Apr 2024 22:36:14 +0200 Subject: [PATCH 45/61] Add an integration test for reading from "/dev/stdin" on unix-like systems --- tests/integration.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/integration.rs b/tests/integration.rs index 240d60b..8c0491a 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -206,5 +206,21 @@ fn read_from_stdin() -> Result<(), Box> { .success() .stdout(predicate::str::is_empty()); + #[cfg(unix)] + { + let mut cmd = Command::cargo_bin("diffutils")?; + cmd.arg("-u") + .arg(file1.path()) + .arg("/dev/stdin") + .write_stdin("bar\n"); + cmd.assert() + .code(predicate::eq(1)) + .failure() + .stdout(predicate::eq(format!( + "--- {}\t\n+++ /dev/stdin\t\n@@ -1 +1 @@\n-foo\n+bar\n", + file1.path().to_string_lossy() + ))); + } + Ok(()) } From 00e18a6b0cfcfa76f70d3a8f8829d975d789131c Mon Sep 17 00:00:00 2001 From: Tanmay Patil Date: Wed, 10 Apr 2024 22:20:48 +0530 Subject: [PATCH 46/61] Define assert_diff_eq macro for context&unified diff comparison --- Cargo.toml | 2 +- src/context_diff.rs | 14 +++----------- src/lib.rs | 1 + src/macros.rs | 13 +++++++++++++ src/main.rs | 1 + src/unified_diff.rs | 14 +++----------- tests/integration.rs | 27 +++++++++++++++++---------- 7 files changed, 39 insertions(+), 33 deletions(-) create mode 100644 src/macros.rs diff --git a/Cargo.toml b/Cargo.toml index ea8a1e1..20cffa6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ path = "src/main.rs" [dependencies] chrono = "0.4.35" diff = "0.1.10" -regex = "1.10.4" +regex = "1.10.3" same-file = "1.0.6" unicode-width = "0.1.11" diff --git a/src/context_diff.rs b/src/context_diff.rs index 80c602d..93c38a5 100644 --- a/src/context_diff.rs +++ b/src/context_diff.rs @@ -726,9 +726,8 @@ mod tests { #[test] fn test_stop_early() { - use regex::Regex; + use crate::assert_diff_eq; use std::fs::File; - use std::str; let target = "target/context-diff"; let _ = std::fs::create_dir(target); @@ -749,10 +748,6 @@ mod tests { }, ); - let diff_full_text = str::from_utf8(&diff_full).unwrap(); - let re = Regex::new(r"\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d+ [+-]\d{4}").unwrap(); - let diff_full = re.replace_all(diff_full_text, ""); - let expected_full = [ "*** target/context-diff/foo\t", "--- target/context-diff/bar\t", @@ -768,7 +763,7 @@ mod tests { "", ] .join("\n"); - assert_eq!(diff_full, expected_full); + assert_diff_eq!(diff_full, expected_full); let diff_brief = diff( from.as_bytes(), @@ -781,16 +776,13 @@ mod tests { }, ); - let diff_brief_text = str::from_utf8(&diff_brief).unwrap(); - let re = Regex::new(r"\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d+ [+-]\d{4}").unwrap(); - let diff_brief = re.replace_all(diff_brief_text, ""); let expected_brief = [ "*** target/context-diff/foo\t", "--- target/context-diff/bar\t", "", ] .join("\n"); - assert_eq!(diff_brief, expected_brief); + assert_diff_eq!(diff_brief, expected_brief); let nodiff_full = diff( from.as_bytes(), diff --git a/src/lib.rs b/src/lib.rs index 7ed36a6..0bb911b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ pub mod context_diff; pub mod ed_diff; +pub mod macros; pub mod normal_diff; pub mod params; pub mod unified_diff; diff --git a/src/macros.rs b/src/macros.rs new file mode 100644 index 0000000..401bff4 --- /dev/null +++ b/src/macros.rs @@ -0,0 +1,13 @@ +#[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.replace_all(diff, ""); + + assert_eq!(actual, $expected); + }}; +} diff --git a/src/main.rs b/src/main.rs index a19e646..624bc3b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,6 +12,7 @@ use std::process::{exit, ExitCode}; mod context_diff; mod ed_diff; +mod macros; mod normal_diff; mod params; mod unified_diff; diff --git a/src/unified_diff.rs b/src/unified_diff.rs index 62dd198..57925e1 100644 --- a/src/unified_diff.rs +++ b/src/unified_diff.rs @@ -880,9 +880,8 @@ mod tests { #[test] fn test_stop_early() { - use regex::Regex; + use crate::assert_diff_eq; use std::fs::File; - use std::str; let target = "target/context-diff"; let _ = std::fs::create_dir(target); @@ -903,10 +902,6 @@ mod tests { }, ); - let diff_full_text = str::from_utf8(&diff_full).unwrap(); - let re = Regex::new(r"\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d+ [+-]\d{4}").unwrap(); - let diff_full = re.replace_all(diff_full_text, ""); - let expected_full = [ "--- target/context-diff/foo\t", "+++ target/context-diff/bar\t", @@ -918,7 +913,7 @@ mod tests { "", ] .join("\n"); - assert_eq!(diff_full, expected_full); + assert_diff_eq!(diff_full, expected_full); let diff_brief = diff( from.as_bytes(), @@ -931,16 +926,13 @@ mod tests { }, ); - let diff_brief_text = str::from_utf8(&diff_brief).unwrap(); - let re = Regex::new(r"\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d+ [+-]\d{4}").unwrap(); - let diff_brief = re.replace_all(diff_brief_text, ""); let expected_brief = [ "--- target/context-diff/foo\t", "+++ target/context-diff/bar\t", "", ] .join("\n"); - assert_eq!(diff_brief, expected_brief); + assert_diff_eq!(diff_brief, expected_brief); let nodiff_full = diff( from.as_bytes(), diff --git a/tests/integration.rs b/tests/integration.rs index 5ad624e..4291ef4 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -4,6 +4,7 @@ // files that was distributed with this source code. use assert_cmd::cmd::Command; +use diffutilslib::assert_diff_eq; use predicates::prelude::*; use std::io::Write; use tempfile::NamedTempFile; @@ -173,26 +174,32 @@ fn read_from_stdin() -> Result<(), Box> { .arg(file1.path()) .arg("-") .write_stdin("bar\n"); - cmd.assert() - .code(predicate::eq(1)) - .failure() - .stdout(predicate::eq(format!( + cmd.assert().code(predicate::eq(1)).failure(); + + let output = cmd.output().unwrap().stdout; + assert_diff_eq!( + output, + format!( "--- {}\t\n+++ /dev/stdin\t\n@@ -1 +1 @@\n-foo\n+bar\n", file1.path().to_string_lossy() - ))); + ) + ); let mut cmd = Command::cargo_bin("diffutils")?; cmd.arg("-u") .arg("-") .arg(file2.path()) .write_stdin("foo\n"); - cmd.assert() - .code(predicate::eq(1)) - .failure() - .stdout(predicate::eq(format!( + cmd.assert().code(predicate::eq(1)).failure(); + + let output = cmd.output().unwrap().stdout; + assert_diff_eq!( + output, + format!( "--- /dev/stdin\t\n+++ {}\t\n@@ -1 +1 @@\n-foo\n+bar\n", file2.path().to_string_lossy() - ))); + ) + ); let mut cmd = Command::cargo_bin("diffutils")?; cmd.arg("-u").arg("-").arg("-").write_stdin("foo\n"); From 0a77fe12b9f4c9794a25dfe3e2ac89ba335e2dcc Mon Sep 17 00:00:00 2001 From: Tanmay Patil Date: Wed, 10 Apr 2024 23:01:28 +0530 Subject: [PATCH 47/61] Add tests for get_modification_time function --- src/utils.rs | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/utils.rs b/src/utils.rs index e0592ef..4320ed6 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -52,6 +52,8 @@ pub fn do_write_line( } } +/// Retrieves the modification time of the input file specified by file path +/// If an error occurs, it returns the current system time pub fn get_modification_time(file_path: &str) -> String { use chrono::{DateTime, Local}; use std::fs; @@ -132,4 +134,40 @@ mod tests { assert_line_written("foo bar\tbaz", true, 8, "foo bar baz"); } } + + mod modification_time { + use super::*; + + #[test] + fn set_time() { + use chrono::{DateTime, Local}; + use std::fs::File; + use std::time::SystemTime; + + let target = "target/utils"; + let _ = std::fs::create_dir(target); + let filename = &format!("{target}/foo"); + let temp = File::create(filename).unwrap(); + + // set file modification time equal to current time + let current = SystemTime::now(); + let _ = temp.set_modified(current); + + // format current time + let current: DateTime = current.into(); + let current: String = current.format("%Y-%m-%d %H:%M:%S%.9f %z").to_string(); + + // verify + assert_eq!(current, get_modification_time(filename)); + } + + #[test] + fn invalid_file() { + let invalid_file = "target/utils/invalid-file"; + + let m_time = get_modification_time(invalid_file); + + assert!(!m_time.is_empty()); + } + } } From 900e1c3a680718fe502380d349d03846ecd59722 Mon Sep 17 00:00:00 2001 From: Tanmay Patil Date: Sun, 14 Apr 2024 13:43:30 +0530 Subject: [PATCH 48/61] Tests: Replace modification time in diff with "TIMESTAMP" placeholder --- src/context_diff.rs | 8 ++++---- src/macros.rs | 14 +++++++++++++- src/unified_diff.rs | 8 ++++---- tests/integration.rs | 6 +++--- 4 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/context_diff.rs b/src/context_diff.rs index 93c38a5..8a1b831 100644 --- a/src/context_diff.rs +++ b/src/context_diff.rs @@ -749,8 +749,8 @@ mod tests { ); let expected_full = [ - "*** target/context-diff/foo\t", - "--- target/context-diff/bar\t", + "*** target/context-diff/foo\tTIMESTAMP", + "--- target/context-diff/bar\tTIMESTAMP", "***************", "*** 1,3 ****", " a", @@ -777,8 +777,8 @@ mod tests { ); let expected_brief = [ - "*** target/context-diff/foo\t", - "--- target/context-diff/bar\t", + "*** target/context-diff/foo\tTIMESTAMP", + "--- target/context-diff/bar\tTIMESTAMP", "", ] .join("\n"); diff --git a/src/macros.rs b/src/macros.rs index 401bff4..7a3c693 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -1,3 +1,15 @@ +// asserts equality of the actual diff and expected diff +// considering datetime varitations +// +// It replaces the modification time in the actual diff +// with placeholer "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) => {{ @@ -6,7 +18,7 @@ macro_rules! assert_diff_eq { 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.replace_all(diff, ""); + let actual = re.replace_all(diff, "TIMESTAMP"); assert_eq!(actual, $expected); }}; diff --git a/src/unified_diff.rs b/src/unified_diff.rs index 57925e1..ed1226f 100644 --- a/src/unified_diff.rs +++ b/src/unified_diff.rs @@ -903,8 +903,8 @@ mod tests { ); let expected_full = [ - "--- target/context-diff/foo\t", - "+++ target/context-diff/bar\t", + "--- target/context-diff/foo\tTIMESTAMP", + "+++ target/context-diff/bar\tTIMESTAMP", "@@ -1,3 +1,3 @@", " a", "-b", @@ -927,8 +927,8 @@ mod tests { ); let expected_brief = [ - "--- target/context-diff/foo\t", - "+++ target/context-diff/bar\t", + "--- target/context-diff/foo\tTIMESTAMP", + "+++ target/context-diff/bar\tTIMESTAMP", "", ] .join("\n"); diff --git a/tests/integration.rs b/tests/integration.rs index 661aa58..853ba4d 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -185,7 +185,7 @@ fn read_from_stdin() -> Result<(), Box> { assert_diff_eq!( output, format!( - "--- {}\t\n+++ -\t\n@@ -1 +1 @@\n-foo\n+bar\n", + "--- {}\tTIMESTAMP\n+++ -\tTIMESTAMP\n@@ -1 +1 @@\n-foo\n+bar\n", file1.path().to_string_lossy() ) ); @@ -201,7 +201,7 @@ fn read_from_stdin() -> Result<(), Box> { assert_diff_eq!( output, format!( - "--- -\t\n+++ {}\t\n@@ -1 +1 @@\n-foo\n+bar\n", + "--- -\tTIMESTAMP\n+++ {}\tTIMESTAMP\n@@ -1 +1 @@\n-foo\n+bar\n", file2.path().to_string_lossy() ) ); @@ -226,7 +226,7 @@ fn read_from_stdin() -> Result<(), Box> { assert_diff_eq!( output, format!( - "--- {}\t\n+++ /dev/stdin\t\n@@ -1 +1 @@\n-foo\n+bar\n", + "--- {}\tTIMESTAMP\n+++ /dev/stdin\tTIMESTAMP\n@@ -1 +1 @@\n-foo\n+bar\n", file1.path().to_string_lossy() ) ); From 33783d094e7ec94060ef719cabeeb0547d86d389 Mon Sep 17 00:00:00 2001 From: Tanmay Patil Date: Sun, 14 Apr 2024 17:16:53 +0530 Subject: [PATCH 49/61] Improve tests --- src/utils.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/utils.rs b/src/utils.rs index 4320ed6..7fb48cc 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -163,11 +163,18 @@ mod tests { #[test] fn invalid_file() { + use chrono::{DateTime, Local}; + use std::time::SystemTime; + let invalid_file = "target/utils/invalid-file"; - let m_time = get_modification_time(invalid_file); + // store current time before calling `get_modification_time` + // Because the file is invalid, it will return SystemTime::now() + // which will be greater than previously saved time + let current_time: DateTime = SystemTime::now().into(); + let m_time: DateTime = get_modification_time(invalid_file).parse().unwrap(); - assert!(!m_time.is_empty()); + assert!(m_time > current_time); } } } From ba7cb0aef9dcdeb1930c5adab23c61bedb7878dd Mon Sep 17 00:00:00 2001 From: Tanmay Patil Date: Sun, 14 Apr 2024 18:24:00 +0530 Subject: [PATCH 50/61] Do not create dummy files Since we now returning SystemTime::now() for invalid file input, there is no need to crate dummy files --- src/context_diff.rs | 40 +++++++++++++--------------------------- src/unified_diff.rs | 45 +++++++++++++++------------------------------ 2 files changed, 28 insertions(+), 57 deletions(-) diff --git a/src/context_diff.rs b/src/context_diff.rs index 8a1b831..e276ce5 100644 --- a/src/context_diff.rs +++ b/src/context_diff.rs @@ -427,15 +427,13 @@ mod tests { if f != 2 { bet.write_all(b"l\n").unwrap(); } - let _ = File::create(&format!("{target}/aalef")).unwrap(); - let mut fa = File::create(&format!("{target}/alef")).unwrap(); // This test diff is intentionally reversed. // We want it to turn the alef into bet. let diff = diff( &alef, &bet, &Params { - from: (&format!("{target}/aalef")).into(), + from: "a/alef".into(), to: (&format!("{target}/alef")).into(), context_count: 2, ..Default::default() @@ -445,6 +443,7 @@ mod tests { .unwrap() .write_all(&diff) .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(); @@ -509,15 +508,13 @@ mod tests { if f != 2 { bet.write_all(b"l\n").unwrap(); } - let _ = File::create(&format!("{target}/aalef_")).unwrap(); - let mut fa = File::create(&format!("{target}/alef_")).unwrap(); // This test diff is intentionally reversed. // We want it to turn the alef into bet. let diff = diff( &alef, &bet, &Params { - from: (&format!("{target}/aalef_")).into(), + from: "a/alef_".into(), to: (&format!("{target}/alef_")).into(), context_count: 2, ..Default::default() @@ -527,6 +524,7 @@ mod tests { .unwrap() .write_all(&diff) .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(); @@ -594,15 +592,13 @@ mod tests { if alef.is_empty() && bet.is_empty() { continue; }; - let _ = File::create(&format!("{target}/aalefx")).unwrap(); - let mut fa = File::create(&format!("{target}/alefx")).unwrap(); // This test diff is intentionally reversed. // We want it to turn the alef into bet. let diff = diff( &alef, &bet, &Params { - from: (&format!("{target}/aalefx")).into(), + from: "a/alefx".into(), to: (&format!("{target}/alefx")).into(), context_count: 2, ..Default::default() @@ -612,6 +608,7 @@ mod tests { .unwrap() .write_all(&diff) .unwrap(); + let mut fa = File::create(&format!("{target}/alefx")).unwrap(); fa.write_all(&alef[..]).unwrap(); let mut fb = File::create(&format!("{target}/betx")).unwrap(); fb.write_all(&bet[..]).unwrap(); @@ -682,15 +679,13 @@ mod tests { if f != 2 { bet.write_all(b"f\n").unwrap(); } - let _ = File::create(&format!("{target}/aalefr")).unwrap(); - let mut fa = File::create(&format!("{target}/alefr")).unwrap(); // This test diff is intentionally reversed. // We want it to turn the alef into bet. let diff = diff( &alef, &bet, &Params { - from: (&format!("{target}/aalefr")).into(), + from: "a/alefr".into(), to: (&format!("{target}/alefr")).into(), context_count: 2, ..Default::default() @@ -700,6 +695,7 @@ mod tests { .unwrap() .write_all(&diff) .unwrap(); + let mut fa = File::create(&format!("{target}/alefr")).unwrap(); fa.write_all(&alef[..]).unwrap(); let mut fb = File::create(&format!("{target}/betr")).unwrap(); fb.write_all(&bet[..]).unwrap(); @@ -727,15 +723,10 @@ mod tests { #[test] fn test_stop_early() { use crate::assert_diff_eq; - use std::fs::File; - let target = "target/context-diff"; - let _ = std::fs::create_dir(target); - let from_filename = &format!("{target}/foo"); - let _ = File::create(from_filename).unwrap(); + let from_filename = "foo"; let from = ["a", "b", "c", ""].join("\n"); - let to_filename = &format!("{target}/bar"); - let _ = File::create(to_filename).unwrap(); + let to_filename = "bar"; let to = ["a", "d", "c", ""].join("\n"); let diff_full = diff( @@ -749,8 +740,8 @@ mod tests { ); let expected_full = [ - "*** target/context-diff/foo\tTIMESTAMP", - "--- target/context-diff/bar\tTIMESTAMP", + "*** foo\tTIMESTAMP", + "--- bar\tTIMESTAMP", "***************", "*** 1,3 ****", " a", @@ -776,12 +767,7 @@ mod tests { }, ); - let expected_brief = [ - "*** target/context-diff/foo\tTIMESTAMP", - "--- target/context-diff/bar\tTIMESTAMP", - "", - ] - .join("\n"); + let expected_brief = ["*** foo\tTIMESTAMP", "--- bar\tTIMESTAMP", ""].join("\n"); assert_diff_eq!(diff_brief, expected_brief); let nodiff_full = diff( diff --git a/src/unified_diff.rs b/src/unified_diff.rs index ed1226f..11299d7 100644 --- a/src/unified_diff.rs +++ b/src/unified_diff.rs @@ -454,15 +454,13 @@ mod tests { if f != 2 { bet.write_all(b"l\n").unwrap(); } - let _ = File::create(&format!("{target}/aalef")).unwrap(); - let mut fa = File::create(&format!("{target}/alef")).unwrap(); // This test diff is intentionally reversed. // We want it to turn the alef into bet. let diff = diff( &alef, &bet, &Params { - from: (&format!("{target}/aalef")).into(), + from: "a/alef".into(), to: (&format!("{target}/alef")).into(), context_count: 2, ..Default::default() @@ -472,6 +470,7 @@ mod tests { .unwrap() .write_all(&diff) .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(); @@ -571,15 +570,13 @@ mod tests { } _ => unreachable!(), } - let _ = File::create(&format!("{target}/aalefn")).unwrap(); - let mut fa = File::create(&format!("{target}/alefn")).unwrap(); // This test diff is intentionally reversed. // We want it to turn the alef into bet. let diff = diff( &alef, &bet, &Params { - from: (&format!("{target}/aalefn")).into(), + from: "a/alefn".into(), to: (&format!("{target}/alefn")).into(), context_count: 2, ..Default::default() @@ -589,6 +586,7 @@ mod tests { .unwrap() .write_all(&diff) .unwrap(); + let mut fa = File::create(&format!("{target}/alefn")).unwrap(); fa.write_all(&alef[..]).unwrap(); let mut fb = File::create(&format!("{target}/betn")).unwrap(); fb.write_all(&bet[..]).unwrap(); @@ -668,15 +666,13 @@ mod tests { 3 => {} _ => unreachable!(), } - let _ = File::create(&format!("{target}/aalef_")).unwrap(); - let mut fa = File::create(&format!("{target}/alef_")).unwrap(); // This test diff is intentionally reversed. // We want it to turn the alef into bet. let diff = diff( &alef, &bet, &Params { - from: (&format!("{target}/aalef_")).into(), + from: "a/alef_".into(), to: (&format!("{target}/alef_")).into(), context_count: 2, ..Default::default() @@ -686,6 +682,7 @@ mod tests { .unwrap() .write_all(&diff) .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(); @@ -750,15 +747,13 @@ mod tests { if f != 2 { bet.write_all(b"l\n").unwrap(); } - let _ = File::create(&format!("{target}/aalefx")).unwrap(); - let mut fa = File::create(&format!("{target}/alefx")).unwrap(); // This test diff is intentionally reversed. // We want it to turn the alef into bet. let diff = diff( &alef, &bet, &Params { - from: (&format!("{target}/aalefx")).into(), + from: "a/alefx".into(), to: (&format!("{target}/alefx")).into(), context_count: 2, ..Default::default() @@ -768,6 +763,7 @@ mod tests { .unwrap() .write_all(&diff) .unwrap(); + let mut fa = File::create(&format!("{target}/alefx")).unwrap(); fa.write_all(&alef[..]).unwrap(); let mut fb = File::create(&format!("{target}/betx")).unwrap(); fb.write_all(&bet[..]).unwrap(); @@ -837,15 +833,13 @@ mod tests { if f != 2 { bet.write_all(b"f\n").unwrap(); } - let _ = File::create(&format!("{target}/aalefr")).unwrap(); - let mut fa = File::create(&format!("{target}/alefr")).unwrap(); // This test diff is intentionally reversed. // We want it to turn the alef into bet. let diff = diff( &alef, &bet, &Params { - from: (&format!("{target}/aalefr")).into(), + from: "a/alefr".into(), to: (&format!("{target}/alefr")).into(), context_count: 2, ..Default::default() @@ -855,6 +849,7 @@ mod tests { .unwrap() .write_all(&diff) .unwrap(); + let mut fa = File::create(&format!("{target}/alefr")).unwrap(); fa.write_all(&alef[..]).unwrap(); let mut fb = File::create(&format!("{target}/betr")).unwrap(); fb.write_all(&bet[..]).unwrap(); @@ -881,15 +876,10 @@ mod tests { #[test] fn test_stop_early() { use crate::assert_diff_eq; - use std::fs::File; - let target = "target/context-diff"; - let _ = std::fs::create_dir(target); - let from_filename = &format!("{target}/foo"); - let _ = File::create(from_filename).unwrap(); + let from_filename = "foo"; let from = ["a", "b", "c", ""].join("\n"); - let to_filename = &format!("{target}/bar"); - let _ = File::create(to_filename).unwrap(); + let to_filename = "bar"; let to = ["a", "d", "c", ""].join("\n"); let diff_full = diff( @@ -903,8 +893,8 @@ mod tests { ); let expected_full = [ - "--- target/context-diff/foo\tTIMESTAMP", - "+++ target/context-diff/bar\tTIMESTAMP", + "--- foo\tTIMESTAMP", + "+++ bar\tTIMESTAMP", "@@ -1,3 +1,3 @@", " a", "-b", @@ -926,12 +916,7 @@ mod tests { }, ); - let expected_brief = [ - "--- target/context-diff/foo\tTIMESTAMP", - "+++ target/context-diff/bar\tTIMESTAMP", - "", - ] - .join("\n"); + let expected_brief = ["--- foo\tTIMESTAMP", "+++ bar\tTIMESTAMP", ""].join("\n"); assert_diff_eq!(diff_brief, expected_brief); let nodiff_full = diff( From 54c02bdf0bbda37c463dc1099d188636583838fc Mon Sep 17 00:00:00 2001 From: Tanmay Patil Date: Tue, 16 Apr 2024 10:17:09 +0530 Subject: [PATCH 51/61] Use NamedTempFile instead of manually creating files --- src/utils.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/utils.rs b/src/utils.rs index 7fb48cc..7dc38e4 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -141,24 +141,20 @@ mod tests { #[test] fn set_time() { use chrono::{DateTime, Local}; - use std::fs::File; use std::time::SystemTime; + use tempfile::NamedTempFile; - let target = "target/utils"; - let _ = std::fs::create_dir(target); - let filename = &format!("{target}/foo"); - let temp = File::create(filename).unwrap(); - + let temp = NamedTempFile::new().unwrap(); // set file modification time equal to current time let current = SystemTime::now(); - let _ = temp.set_modified(current); + let _ = temp.as_file().set_modified(current); // format current time let current: DateTime = current.into(); let current: String = current.format("%Y-%m-%d %H:%M:%S%.9f %z").to_string(); // verify - assert_eq!(current, get_modification_time(filename)); + assert_eq!(current, get_modification_time(&temp.path().to_string_lossy())); } #[test] From aedd0684d199a5b9e0a57e48b7e9a42f6f0ea7ad Mon Sep 17 00:00:00 2001 From: Tanmay Patil Date: Tue, 16 Apr 2024 10:26:32 +0530 Subject: [PATCH 52/61] Replace only the first two occurences of timestamp regex --- src/macros.rs | 2 +- src/utils.rs | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/macros.rs b/src/macros.rs index 7a3c693..1f1a994 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -18,7 +18,7 @@ macro_rules! assert_diff_eq { 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.replace_all(diff, "TIMESTAMP"); + let actual = re.replacen(diff, 2, "TIMESTAMP"); assert_eq!(actual, $expected); }}; diff --git a/src/utils.rs b/src/utils.rs index 7dc38e4..561f2b9 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -154,7 +154,10 @@ mod tests { let current: String = current.format("%Y-%m-%d %H:%M:%S%.9f %z").to_string(); // verify - assert_eq!(current, get_modification_time(&temp.path().to_string_lossy())); + assert_eq!( + current, + get_modification_time(&temp.path().to_string_lossy()) + ); } #[test] From 1149a247dd01d4d560f844e9dae32ea2542cb5fd Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 16 Apr 2024 16:20:52 +0000 Subject: [PATCH 53/61] Update Rust crate chrono to 0.4.38 --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cdbb0f6..d70d307 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -90,9 +90,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.35" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", diff --git a/Cargo.toml b/Cargo.toml index 9a53418..753fc09 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ name = "diffutils" path = "src/main.rs" [dependencies] -chrono = "0.4.35" +chrono = "0.4.38" diff = "0.1.10" regex = "1.10.3" same-file = "1.0.6" From 7f7821f5585e71462879c7750e3307daff419fc4 Mon Sep 17 00:00:00 2001 From: Olivier Tilloy Date: Tue, 16 Apr 2024 18:37:59 +0200 Subject: [PATCH 54/61] Use the private Codecov token stored as a secret, to work around rate-limiting issues like https://github.com/codecov/codecov-action/issues/557 --- .github/workflows/ci.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8b8e474..9af3854 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -143,9 +143,8 @@ jobs: 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 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 }} From b8efad6b90ac665d9756710b2fa722cce7fc3229 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 16 Apr 2024 19:35:01 +0000 Subject: [PATCH 55/61] Update Rust crate diff to 0.1.13 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 753fc09..1c16c15 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ path = "src/main.rs" [dependencies] chrono = "0.4.38" -diff = "0.1.10" +diff = "0.1.13" regex = "1.10.3" same-file = "1.0.6" unicode-width = "0.1.11" From fcec7277c9ac6bad264a08475993e5a47da35855 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 16 Apr 2024 19:35:04 +0000 Subject: [PATCH 56/61] Update codecov/codecov-action action to v4 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9af3854..77b9218 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -142,7 +142,7 @@ 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 + uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} file: ${{ steps.coverage.outputs.report }} From 674974d5e66762fc22995595d2456aaca1f040c9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 16 Apr 2024 19:54:53 +0000 Subject: [PATCH 57/61] Update Rust crate regex to 1.10.4 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 1c16c15..0e8dab9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ path = "src/main.rs" [dependencies] chrono = "0.4.38" diff = "0.1.13" -regex = "1.10.3" +regex = "1.10.4" same-file = "1.0.6" unicode-width = "0.1.11" From bf104648c1e9b217ef4109221d0eebe42284398c Mon Sep 17 00:00:00 2001 From: Olivier Tilloy Date: Sat, 20 Apr 2024 19:17:42 +0200 Subject: [PATCH 58/61] CI: On Windows, use GNU's patch.exe instead of Strawberry Perl patch --- .github/workflows/ci.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 77b9218..be76d96 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,6 +28,10 @@ jobs: steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable + - 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: @@ -103,6 +107,10 @@ jobs: - name: rust toolchain ~ install uses: dtolnay/rust-toolchain@nightly + - 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: From 831348d1fc7c457cc5c79828c2d2cb9f4120a8b0 Mon Sep 17 00:00:00 2001 From: Olivier Tilloy Date: Sat, 20 Apr 2024 23:30:20 +0200 Subject: [PATCH 59/61] Fix file path in ed diff tests --- src/ed_diff.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/ed_diff.rs b/src/ed_diff.rs index ba39046..3a524d3 100644 --- a/src/ed_diff.rs +++ b/src/ed_diff.rs @@ -226,7 +226,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(); @@ -240,7 +240,7 @@ mod tests { { let output = Command::new("ed") .arg(&format!("{target}/alef")) - .stdin(File::open("target/ab.ed").unwrap()) + .stdin(File::open(&format!("{target}/ab.ed")).unwrap()) .output() .unwrap(); assert!(output.status.success(), "{output:?}"); @@ -299,12 +299,12 @@ 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(); @@ -313,15 +313,15 @@ mod tests { #[cfg(not(windows))] // there's no ed on windows { let output = Command::new("ed") - .arg("target/alef_") - .stdin(File::open("target/ab_.ed").unwrap()) + .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 = fs::read("target/alef_").unwrap(); + let alef = fs::read(&format!("{target}/alef_")).unwrap(); assert_eq!(alef, bet); } } @@ -380,7 +380,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(); @@ -394,7 +394,7 @@ mod tests { { let output = Command::new("ed") .arg(&format!("{target}/alefr")) - .stdin(File::open("target/abr.ed").unwrap()) + .stdin(File::open(&format!("{target}/abr.ed")).unwrap()) .output() .unwrap(); assert!(output.status.success(), "{output:?}"); From 14799eea897dff48e3e612e8ad25ae7c40b80793 Mon Sep 17 00:00:00 2001 From: Olivier Tilloy Date: Sun, 21 Apr 2024 00:01:23 +0200 Subject: [PATCH 60/61] Move test assertions in the cfg block where they belong --- src/ed_diff.rs | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/ed_diff.rs b/src/ed_diff.rs index 3a524d3..304a26f 100644 --- a/src/ed_diff.rs +++ b/src/ed_diff.rs @@ -188,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" }) @@ -238,17 +237,18 @@ mod tests { let _ = fb; #[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); } - //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); } } } @@ -268,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(); @@ -312,17 +311,18 @@ mod tests { let _ = fb; #[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); } - //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); } } } @@ -342,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" }) @@ -392,17 +391,18 @@ mod tests { let _ = fb; #[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); } - //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); } } } From 3a8eddfe2c01c6d8134fdaa55376365eaf96b504 Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru Date: Sun, 21 Apr 2024 16:07:01 +0200 Subject: [PATCH 61/61] Fix typos --- src/macros.rs | 2 +- tests/run-upstream-testsuite.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/macros.rs b/src/macros.rs index 1f1a994..90a4eaa 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -2,7 +2,7 @@ // considering datetime varitations // // It replaces the modification time in the actual diff -// with placeholer "TIMESTAMP" and then asserts the equality +// with placeholder "TIMESTAMP" and then asserts the equality // // For eg. // let brief = "*** fruits_old.txt\t2024-03-24 23:43:05.189597645 +0530\n diff --git a/tests/run-upstream-testsuite.sh b/tests/run-upstream-testsuite.sh index d85dfd6..cb59834 100755 --- a/tests/run-upstream-testsuite.sh +++ b/tests/run-upstream-testsuite.sh @@ -19,7 +19,7 @@ # By default it expects a release build of the diffutils binary, but a # different build profile can be specified as an argument # (e.g. 'dev' or 'test'). -# Unless overriden by the $TESTS environment variable, all tests in the test +# Unless overridden by the $TESTS environment variable, all tests in the test # suite will be run. Tests targeting a command that is not yet implemented # (e.g. cmp, diff3 or sdiff) are skipped.