Compare commits

..

1 Commits

Author SHA1 Message Date
Sylvestre Ledru fe605ff853 ed_diff::tests::test_permutations: disable temporary tests on Windows 2024-04-21 15:52:52 +02:00
25 changed files with 565 additions and 3045 deletions
+3 -11
View File
@@ -28,9 +28,6 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: install GNU patch on MacOS
if: runner.os == 'macOS'
run: brew install gpatch
- name: set up PATH on Windows
# Needed to use GNU's patch.exe instead of Strawberry Perl patch
if: runner.os == 'Windows'
@@ -110,10 +107,6 @@ jobs:
- name: rust toolchain ~ install
uses: dtolnay/rust-toolchain@nightly
- run: rustup component add llvm-tools-preview
- name: install GNU patch on MacOS
if: runner.os == 'macOS'
run: brew install gpatch
- name: set up PATH on Windows
# Needed to use GNU's patch.exe instead of Strawberry Perl patch
if: runner.os == 'Windows'
@@ -123,9 +116,8 @@ jobs:
env:
CARGO_INCREMENTAL: "0"
RUSTC_WRAPPER: ""
RUSTFLAGS: "-Cinstrument-coverage -Zcoverage-options=branch -Ccodegen-units=1 -Copt-level=0 -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort"
RUSTFLAGS: "-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort"
RUSTDOCFLAGS: "-Cpanic=abort"
LLVM_PROFILE_FILE: "diffutils-%p-%m.profraw"
- name: "`grcov` ~ install"
id: build_grcov
shell: bash
@@ -153,9 +145,9 @@ jobs:
COVERAGE_REPORT_FILE="${COVERAGE_REPORT_DIR}/lcov.info"
mkdir -p "${COVERAGE_REPORT_DIR}"
# display coverage files
grcov . --output-type files --binary-path "${COVERAGE_REPORT_DIR}" | sort --unique
grcov . --output-type files --ignore build.rs --ignore "vendor/*" --ignore "/*" --ignore "[a-zA-Z]:/*" --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()" | sort --unique
# generate coverage report
grcov . --output-type lcov --output-path "${COVERAGE_REPORT_FILE}" --binary-path "${COVERAGE_REPORT_DIR}" --branch
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@v4
-2
View File
@@ -41,8 +41,6 @@ jobs:
strategy:
matrix:
test-target:
- { name: fuzz_cmp, should_pass: true }
- { name: fuzz_cmp_args, should_pass: true }
- { name: fuzz_ed, should_pass: true }
- { name: fuzz_normal, should_pass: true }
- { name: fuzz_patch, should_pass: true }
+13 -18
View File
@@ -1,4 +1,4 @@
# Copyright 2022-2024, axodotdev
# Copyright 2022-2023, axodotdev
# SPDX-License-Identifier: MIT or Apache-2.0
#
# CI that:
@@ -6,9 +6,9 @@
# * 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
# * on success, uploads the artifacts to a Github Release
#
# Note that the GitHub Release will be created with a generated
# Note that the Github Release will be created with a generated
# title/body based on your changelogs.
name: Release
@@ -31,7 +31,7 @@ permissions:
# 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
# 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.
#
@@ -62,7 +62,7 @@ jobs:
# we specify bash to get pipefail; it guards against the `curl` command
# failing. otherwise `sh` won't catch that `curl` returned non-0
shell: bash
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.13.3/cargo-dist-installer.sh | sh"
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.
@@ -105,15 +105,10 @@ jobs:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BUILD_MANIFEST_NAME: target/distrib/${{ join(matrix.targets, '-') }}-dist-manifest.json
steps:
- name: enable windows longpaths
run: |
git config --global core.longpaths true
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: swatinem/rust-cache@v2
with:
key: ${{ join(matrix.targets, '-') }}
- name: Install cargo-dist
run: ${{ matrix.install_dist }}
# Get the dist-manifest
@@ -140,7 +135,7 @@ jobs:
run: |
# Parse out what we just built and upload it to scratch storage
echo "paths<<EOF" >> "$GITHUB_OUTPUT"
jq --raw-output ".upload_files[]" dist-manifest.json >> "$GITHUB_OUTPUT"
jq --raw-output ".artifacts[]?.path | select( . != null )" dist-manifest.json >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"
cp dist-manifest.json "$BUILD_MANIFEST_NAME"
@@ -167,7 +162,7 @@ jobs:
submodules: recursive
- name: Install cargo-dist
shell: bash
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.13.3/cargo-dist-installer.sh | sh"
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
@@ -183,7 +178,7 @@ jobs:
# Parse out what we just built and upload it to scratch storage
echo "paths<<EOF" >> "$GITHUB_OUTPUT"
jq --raw-output ".upload_files[]" dist-manifest.json >> "$GITHUB_OUTPUT"
jq --raw-output ".artifacts[]?.path | select( . != null )" dist-manifest.json >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"
cp dist-manifest.json "$BUILD_MANIFEST_NAME"
@@ -212,7 +207,7 @@ jobs:
with:
submodules: recursive
- name: Install cargo-dist
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.13.3/cargo-dist-installer.sh | sh"
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
@@ -220,7 +215,7 @@ jobs:
pattern: artifacts-*
path: target/distrib/
merge-multiple: true
# This is a harmless no-op for GitHub Releases, hosting for that happens in "announce"
# This is a harmless no-op for Github Releases, hosting for that happens in "announce"
- id: host
shell: bash
run: |
@@ -235,7 +230,7 @@ jobs:
name: artifacts-dist-manifest
path: dist-manifest.json
# Create a GitHub Release while uploading all files to it
# Create a Github Release while uploading all files to it
announce:
needs:
- plan
@@ -251,7 +246,7 @@ jobs:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: "Download GitHub Artifacts"
- name: "Download Github Artifacts"
uses: actions/download-artifact@v4
with:
pattern: artifacts-*
@@ -261,7 +256,7 @@ jobs:
run: |
# Remove the granular manifests
rm -f artifacts/*-dist-manifest.json
- name: Create GitHub Release
- name: Create Github Release
uses: ncipollo/release-action@v1
with:
tag: ${{ needs.plan.outputs.tag }}
Generated
+46 -71
View File
@@ -34,14 +34,13 @@ checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc"
[[package]]
name = "assert_cmd"
version = "2.0.16"
version = "2.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc1835b7f27878de8525dc71410b5a31cdcc5f230aed5ba5df968e09c201b23d"
checksum = "ed72493ac66d5804837f480ab3766c72bdfab91a65e565fc54fa9e42db0073a8"
dependencies = [
"anstyle",
"bstr",
"doc-comment",
"libc",
"predicates",
"predicates-core",
"predicates-tree",
@@ -123,12 +122,11 @@ checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
[[package]]
name = "diffutils"
version = "0.4.2"
version = "0.4.0"
dependencies = [
"assert_cmd",
"chrono",
"diff",
"itoa",
"predicates",
"pretty_assertions",
"regex",
@@ -150,14 +148,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
dependencies = [
"libc",
"windows-sys 0.52.0",
"windows-sys",
]
[[package]]
name = "fastrand"
version = "2.1.1"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6"
checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
[[package]]
name = "float-cmp"
@@ -191,12 +189,6 @@ dependencies = [
"cc",
]
[[package]]
name = "itoa"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]]
name = "js-sys"
version = "0.3.69"
@@ -208,15 +200,15 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.159"
version = "0.2.153"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5"
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
[[package]]
name = "linux-raw-sys"
version = "0.4.14"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
[[package]]
name = "log"
@@ -253,9 +245,9 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "predicates"
version = "3.1.2"
version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e9086cc7640c29a356d1a29fd134380bee9d8f79a17410aa76e7ad295f42c97"
checksum = "68b87bfd4605926cdfefc1c3b5f8fe560e3feca9d5552cf68c466d3d8236c7e8"
dependencies = [
"anstyle",
"difflib",
@@ -283,9 +275,9 @@ dependencies = [
[[package]]
name = "pretty_assertions"
version = "1.4.1"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d"
checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66"
dependencies = [
"diff",
"yansi",
@@ -311,9 +303,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.11.0"
version = "1.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8"
checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c"
dependencies = [
"aho-corasick",
"memchr",
@@ -323,9 +315,9 @@ dependencies = [
[[package]]
name = "regex-automata"
version = "0.4.8"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3"
checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd"
dependencies = [
"aho-corasick",
"memchr",
@@ -334,21 +326,21 @@ dependencies = [
[[package]]
name = "regex-syntax"
version = "0.8.5"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
[[package]]
name = "rustix"
version = "0.38.37"
version = "0.38.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811"
checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.52.0",
"windows-sys",
]
[[package]]
@@ -393,15 +385,14 @@ dependencies = [
[[package]]
name = "tempfile"
version = "3.13.0"
version = "3.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b"
checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1"
dependencies = [
"cfg-if",
"fastrand",
"once_cell",
"rustix",
"windows-sys 0.59.0",
"windows-sys",
]
[[package]]
@@ -418,9 +409,9 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "unicode-width"
version = "0.2.0"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
[[package]]
name = "wait-timeout"
@@ -534,25 +525,15 @@ dependencies = [
"windows-targets",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
version = "0.52.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
checksum = "d98532992affa02e52709d5b4d145a3668ae10d9081eea4a7f26f719a8476f71"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
@@ -561,54 +542,48 @@ dependencies = [
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
version = "0.52.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
checksum = "e7269c1442e75af9fa59290383f7665b828efc76c429cc0b7f2ecb33cf51ebae"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
version = "0.52.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
checksum = "f70ab2cebf332b7ecbdd98900c2da5298a8c862472fb35c75fc297eabb9d89b8"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
version = "0.52.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
checksum = "679f235acf6b1639408c0f6db295697a19d103b0cdc88146aa1b992c580c647d"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
version = "0.52.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
checksum = "3480ac194b55ae274a7e135c21645656825da4a7f5b6e9286291b2113c94a78b"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
version = "0.52.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
checksum = "42c46bab241c121402d1cb47d028ea3680ee2f359dcc287482dcf7fdddc73363"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
version = "0.52.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
checksum = "dc885a4332ee1afb9a1bacf11514801011725570d35675abc229ce7e3afe4d20"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
version = "0.52.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
checksum = "9e440c60457f84b0bee09208e62acc7ade264b38c4453f6312b8c9ab1613e73c"
[[package]]
name = "yansi"
version = "1.0.1"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
+4 -5
View File
@@ -1,6 +1,6 @@
[package]
name = "diffutils"
version = "0.4.2"
version = "0.4.0"
edition = "2021"
description = "A CLI app for generating diff files"
license = "MIT OR Apache-2.0"
@@ -17,13 +17,12 @@ path = "src/main.rs"
[dependencies]
chrono = "0.4.38"
diff = "0.1.13"
itoa = "1.0.11"
regex = "1.10.4"
same-file = "1.0.6"
unicode-width = "0.2.0"
unicode-width = "0.1.11"
[dev-dependencies]
pretty_assertions = "1.4.0"
pretty_assertions = "1"
assert_cmd = "2.0.14"
predicates = "3.1.0"
tempfile = "3.10.1"
@@ -36,7 +35,7 @@ lto = "thin"
# Config for 'cargo dist'
[workspace.metadata.dist]
# The preferred cargo-dist version to use in CI (Cargo.toml SemVer syntax)
cargo-dist-version = "0.13.3"
cargo-dist-version = "0.12.0"
# CI backends to support
ci = ["github"]
# The installers to generate for each app
-3
View File
@@ -1,6 +1,3 @@
Copyright (c) Michael Howell
Copyright (c) uutils developers
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
-3
View File
@@ -1,6 +1,3 @@
Copyright (c) Michael Howell
Copyright (c) uutils developers
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
+1 -1
View File
@@ -5,7 +5,7 @@
[![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/) (diff, cmp, diff3, sdiff) 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.
+1 -13
View File
@@ -9,25 +9,13 @@ edition = "2018"
cargo-fuzz = true
[dependencies]
libfuzzer-sys = "0.4.7"
libfuzzer-sys = "0.4"
diffutils = { path = "../" }
# Prevent this from interfering with workspaces
[workspace]
members = ["."]
[[bin]]
name = "fuzz_cmp"
path = "fuzz_targets/fuzz_cmp.rs"
test = false
doc = false
[[bin]]
name = "fuzz_cmp_args"
path = "fuzz_targets/fuzz_cmp_args.rs"
test = false
doc = false
[[bin]]
name = "fuzz_patch"
path = "fuzz_targets/fuzz_patch.rs"
-36
View File
@@ -1,36 +0,0 @@
"-l"
"--verbose"
"-b"
"--print-bytes"
"-lb"
"-bl"
"-n"
"--bytes"
"--bytes="
"--bytes=1024"
"--bytes=99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999"
"-i"
"--ignore-initial"
"--ignore-initial="
"--ignore-initial=1024"
"--ignore-initial=99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999:9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999"
"-s"
"-q"
"--quiet"
"--silent"
"-"
"--"
"1kB"
"1G"
"1GB"
"1T"
"1TB"
"1P"
"1PB"
"1Z"
"1ZB"
"1Y"
"1YB"
"1Y"
"0"
"1:2"
-51
View File
@@ -1,51 +0,0 @@
#![no_main]
#[macro_use]
extern crate libfuzzer_sys;
use diffutilslib::cmp::{self, Cmp};
use std::ffi::OsString;
use std::fs::File;
use std::io::Write;
fn os(s: &str) -> OsString {
OsString::from(s)
}
fuzz_target!(|x: (Vec<u8>, Vec<u8>)| {
let args = vec!["cmp", "-l", "-b", "target/fuzz.cmp.a", "target/fuzz.cmp.b"]
.into_iter()
.map(|s| os(s))
.peekable();
let (from, to) = x;
File::create("target/fuzz.cmp.a")
.unwrap()
.write_all(&from)
.unwrap();
File::create("target/fuzz.cmp.b")
.unwrap()
.write_all(&to)
.unwrap();
let params =
cmp::parse_params(args).unwrap_or_else(|e| panic!("Failed to parse params: {}", e));
let ret = cmp::cmp(&params);
if from == to && !matches!(ret, Ok(Cmp::Equal)) {
panic!(
"target/fuzz.cmp.a and target/fuzz.cmp.b are equal, but cmp returned {:?}.",
ret
);
} else if from != to && !matches!(ret, Ok(Cmp::Different)) {
panic!(
"target/fuzz.cmp.a and target/fuzz.cmp.b are different, but cmp returned {:?}.",
ret
);
} else if ret.is_err() {
panic!(
"target/fuzz.cmp.a and target/fuzz.cmp.b caused cmp to error ({:?}).",
ret
);
}
});
-23
View File
@@ -1,23 +0,0 @@
#![no_main]
#[macro_use]
extern crate libfuzzer_sys;
use diffutilslib::cmp;
use libfuzzer_sys::Corpus;
use std::ffi::OsString;
fn os(s: &str) -> OsString {
OsString::from(s)
}
fuzz_target!(|x: Vec<OsString>| -> Corpus {
if x.len() > 6 {
// Make sure we try to parse an option when we get longer args. x[0] will be
// the executable name.
if ![os("-l"), os("-b"), os("-s"), os("-n"), os("-i")].contains(&x[1]) {
return Corpus::Reject;
}
}
let _ = cmp::parse_params(x.into_iter().peekable());
Corpus::Keep
});
-1185
View File
File diff suppressed because it is too large Load Diff
+20 -20
View File
@@ -439,26 +439,26 @@ mod tests {
..Default::default()
},
);
File::create(format!("{target}/ab.diff"))
File::create(&format!("{target}/ab.diff"))
.unwrap()
.write_all(&diff)
.unwrap();
let mut fa = File::create(format!("{target}/alef")).unwrap();
let mut fa = File::create(&format!("{target}/alef")).unwrap();
fa.write_all(&alef[..]).unwrap();
let mut fb = File::create(format!("{target}/bet")).unwrap();
let mut fb = File::create(&format!("{target}/bet")).unwrap();
fb.write_all(&bet[..]).unwrap();
let _ = fa;
let _ = fb;
let output = Command::new("patch")
.arg("-p0")
.arg("--context")
.stdin(File::open(format!("{target}/ab.diff")).unwrap())
.stdin(File::open(&format!("{target}/ab.diff")).unwrap())
.output()
.unwrap();
assert!(output.status.success(), "{output:?}");
//println!("{}", String::from_utf8_lossy(&output.stdout));
//println!("{}", String::from_utf8_lossy(&output.stderr));
let alef = fs::read(format!("{target}/alef")).unwrap();
let alef = fs::read(&format!("{target}/alef")).unwrap();
assert_eq!(alef, bet);
}
}
@@ -520,26 +520,26 @@ mod tests {
..Default::default()
},
);
File::create(format!("{target}/ab_.diff"))
File::create(&format!("{target}/ab_.diff"))
.unwrap()
.write_all(&diff)
.unwrap();
let mut fa = File::create(format!("{target}/alef_")).unwrap();
let mut fa = File::create(&format!("{target}/alef_")).unwrap();
fa.write_all(&alef[..]).unwrap();
let mut fb = File::create(format!("{target}/bet_")).unwrap();
let mut fb = File::create(&format!("{target}/bet_")).unwrap();
fb.write_all(&bet[..]).unwrap();
let _ = fa;
let _ = fb;
let output = Command::new("patch")
.arg("-p0")
.arg("--context")
.stdin(File::open(format!("{target}/ab_.diff")).unwrap())
.stdin(File::open(&format!("{target}/ab_.diff")).unwrap())
.output()
.unwrap();
assert!(output.status.success(), "{output:?}");
//println!("{}", String::from_utf8_lossy(&output.stdout));
//println!("{}", String::from_utf8_lossy(&output.stderr));
let alef = fs::read(format!("{target}/alef_")).unwrap();
let alef = fs::read(&format!("{target}/alef_")).unwrap();
assert_eq!(alef, bet);
}
}
@@ -604,26 +604,26 @@ mod tests {
..Default::default()
},
);
File::create(format!("{target}/abx.diff"))
File::create(&format!("{target}/abx.diff"))
.unwrap()
.write_all(&diff)
.unwrap();
let mut fa = File::create(format!("{target}/alefx")).unwrap();
let mut fa = File::create(&format!("{target}/alefx")).unwrap();
fa.write_all(&alef[..]).unwrap();
let mut fb = File::create(format!("{target}/betx")).unwrap();
let mut fb = File::create(&format!("{target}/betx")).unwrap();
fb.write_all(&bet[..]).unwrap();
let _ = fa;
let _ = fb;
let output = Command::new("patch")
.arg("-p0")
.arg("--context")
.stdin(File::open(format!("{target}/abx.diff")).unwrap())
.stdin(File::open(&format!("{target}/abx.diff")).unwrap())
.output()
.unwrap();
assert!(output.status.success(), "{output:?}");
//println!("{}", String::from_utf8_lossy(&output.stdout));
//println!("{}", String::from_utf8_lossy(&output.stderr));
let alef = fs::read(format!("{target}/alefx")).unwrap();
let alef = fs::read(&format!("{target}/alefx")).unwrap();
assert_eq!(alef, bet);
}
}
@@ -691,26 +691,26 @@ mod tests {
..Default::default()
},
);
File::create(format!("{target}/abr.diff"))
File::create(&format!("{target}/abr.diff"))
.unwrap()
.write_all(&diff)
.unwrap();
let mut fa = File::create(format!("{target}/alefr")).unwrap();
let mut fa = File::create(&format!("{target}/alefr")).unwrap();
fa.write_all(&alef[..]).unwrap();
let mut fb = File::create(format!("{target}/betr")).unwrap();
let mut fb = File::create(&format!("{target}/betr")).unwrap();
fb.write_all(&bet[..]).unwrap();
let _ = fa;
let _ = fb;
let output = Command::new("patch")
.arg("-p0")
.arg("--context")
.stdin(File::open(format!("{target}/abr.diff")).unwrap())
.stdin(File::open(&format!("{target}/abr.diff")).unwrap())
.output()
.unwrap();
assert!(output.status.success(), "{output:?}");
//println!("{}", String::from_utf8_lossy(&output.stdout));
//println!("{}", String::from_utf8_lossy(&output.stderr));
let alef = fs::read(format!("{target}/alefr")).unwrap();
let alef = fs::read(&format!("{target}/alefr")).unwrap();
assert_eq!(alef, bet);
}
}
-98
View File
@@ -1,98 +0,0 @@
// This file is part of the uutils diffutils package.
//
// For the full copyright and license information, please view the LICENSE-*
// files that was distributed with this source code.
use crate::params::{parse_params, Format};
use crate::utils::report_failure_to_read_input_file;
use crate::{context_diff, ed_diff, normal_diff, unified_diff};
use std::env::ArgsOs;
use std::ffi::OsString;
use std::fs;
use std::io::{self, Read, Write};
use std::iter::Peekable;
use std::process::{exit, ExitCode};
// Exit codes are documented at
// https://www.gnu.org/software/diffutils/manual/html_node/Invoking-diff.html.
// An exit status of 0 means no differences were found,
// 1 means some differences were found,
// and 2 means trouble.
pub fn main(opts: Peekable<ArgsOs>) -> ExitCode {
let params = parse_params(opts).unwrap_or_else(|error| {
eprintln!("{error}");
exit(2);
});
// if from and to are the same file, no need to perform any comparison
let maybe_report_identical_files = || {
if params.report_identical_files {
println!(
"Files {} and {} are identical",
params.from.to_string_lossy(),
params.to.to_string_lossy(),
);
}
};
if params.from == "-" && params.to == "-"
|| same_file::is_same_file(&params.from, &params.to).unwrap_or(false)
{
maybe_report_identical_files();
return ExitCode::SUCCESS;
}
// read files
fn read_file_contents(filepath: &OsString) -> io::Result<Vec<u8>> {
if filepath == "-" {
let mut content = Vec::new();
io::stdin().read_to_end(&mut content).and(Ok(content))
} else {
fs::read(filepath)
}
}
let mut io_error = false;
let from_content = match read_file_contents(&params.from) {
Ok(from_content) => from_content,
Err(e) => {
report_failure_to_read_input_file(&params.executable, &params.from, &e);
io_error = true;
vec![]
}
};
let to_content = match read_file_contents(&params.to) {
Ok(to_content) => to_content,
Err(e) => {
report_failure_to_read_input_file(&params.executable, &params.to, &e);
io_error = true;
vec![]
}
};
if io_error {
return ExitCode::from(2);
}
// run diff
let result: Vec<u8> = match params.format {
Format::Normal => normal_diff::diff(&from_content, &to_content, &params),
Format::Unified => unified_diff::diff(&from_content, &to_content, &params),
Format::Context => context_diff::diff(&from_content, &to_content, &params),
Format::Ed => ed_diff::diff(&from_content, &to_content, &params).unwrap_or_else(|error| {
eprintln!("{error}");
exit(2);
}),
};
if params.brief && !result.is_empty() {
println!(
"Files {} and {} differ",
params.from.to_string_lossy(),
params.to.to_string_lossy()
);
} else {
io::stdout().write_all(&result).unwrap();
}
if result.is_empty() {
maybe_report_identical_files();
ExitCode::SUCCESS
} else {
ExitCode::from(1)
}
}
+21 -18
View File
@@ -178,6 +178,7 @@ mod tests {
}
#[test]
#[cfg(not(windows))]
fn test_permutations() {
let target = "target/ed-diff/";
// test all possible six-line files.
@@ -225,13 +226,13 @@ 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(format!("{target}/ab.ed"))
File::create(&format!("{target}/ab.ed"))
.unwrap()
.write_all(&diff)
.unwrap();
let mut fa = File::create(format!("{target}/alef")).unwrap();
let mut fa = File::create(&format!("{target}/alef")).unwrap();
fa.write_all(&alef[..]).unwrap();
let mut fb = File::create(format!("{target}/bet")).unwrap();
let mut fb = File::create(&format!("{target}/bet")).unwrap();
fb.write_all(&bet[..]).unwrap();
let _ = fa;
let _ = fb;
@@ -239,14 +240,14 @@ mod tests {
{
use std::process::Command;
let output = Command::new("ed")
.arg(format!("{target}/alef"))
.stdin(File::open(format!("{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 = std::fs::read(format!("{target}/alef")).unwrap();
let alef = std::fs::read(&format!("{target}/alef")).unwrap();
assert_eq!(alef, bet);
}
}
@@ -258,6 +259,7 @@ mod tests {
}
#[test]
#[cfg(not(windows))]
fn test_permutations_empty_lines() {
let target = "target/ed-diff/";
// test all possible six-line files with missing newlines.
@@ -299,13 +301,13 @@ 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(format!("{target}/ab_.ed"))
File::create(&format!("{target}/ab_.ed"))
.unwrap()
.write_all(&diff)
.unwrap();
let mut fa = File::create(format!("{target}/alef_")).unwrap();
let mut fa = File::create(&format!("{target}/alef_")).unwrap();
fa.write_all(&alef[..]).unwrap();
let mut fb = File::create(format!("{target}/bet_")).unwrap();
let mut fb = File::create(&format!("{target}/bet_")).unwrap();
fb.write_all(&bet[..]).unwrap();
let _ = fa;
let _ = fb;
@@ -313,14 +315,14 @@ mod tests {
{
use std::process::Command;
let output = Command::new("ed")
.arg(format!("{target}/alef_"))
.stdin(File::open(format!("{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 = std::fs::read(format!("{target}/alef_")).unwrap();
let alef = std::fs::read(&format!("{target}/alef_")).unwrap();
assert_eq!(alef, bet);
}
}
@@ -332,6 +334,7 @@ mod tests {
}
#[test]
#[cfg(not(windows))]
fn test_permutations_reverse() {
let target = "target/ed-diff/";
// test all possible six-line files.
@@ -379,13 +382,13 @@ 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(format!("{target}/abr.ed"))
File::create(&format!("{target}/abr.ed"))
.unwrap()
.write_all(&diff)
.unwrap();
let mut fa = File::create(format!("{target}/alefr")).unwrap();
let mut fa = File::create(&format!("{target}/alefr")).unwrap();
fa.write_all(&alef[..]).unwrap();
let mut fb = File::create(format!("{target}/betr")).unwrap();
let mut fb = File::create(&format!("{target}/betr")).unwrap();
fb.write_all(&bet[..]).unwrap();
let _ = fa;
let _ = fb;
@@ -393,14 +396,14 @@ mod tests {
{
use std::process::Command;
let output = Command::new("ed")
.arg(format!("{target}/alefr"))
.stdin(File::open(format!("{target}/abr.ed")).unwrap())
.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();
let alef = std::fs::read(&format!("{target}/alefr")).unwrap();
assert_eq!(alef, bet);
}
}
-1
View File
@@ -1,4 +1,3 @@
pub mod cmp;
pub mod context_diff;
pub mod ed_diff;
pub mod macros;
+1 -1
View File
@@ -2,7 +2,7 @@
// considering datetime varitations
//
// It replaces the modification time in the actual diff
// with placeholder "TIMESTAMP" and then asserts the equality
// with placeholer "TIMESTAMP" and then asserts the equality
//
// For eg.
// let brief = "*** fruits_old.txt\t2024-03-24 23:43:05.189597645 +0530\n
+78 -62
View File
@@ -3,17 +3,14 @@
// For the full copyright and license information, please view the LICENSE-*
// files that was distributed with this source code.
use std::{
env::ArgsOs,
ffi::{OsStr, OsString},
iter::Peekable,
path::{Path, PathBuf},
process::ExitCode,
};
use crate::params::{parse_params, Format};
use std::env;
use std::ffi::OsString;
use std::fs;
use std::io::{self, Read, Write};
use std::process::{exit, ExitCode};
mod cmp;
mod context_diff;
mod diff;
mod ed_diff;
mod macros;
mod normal_diff;
@@ -21,60 +18,79 @@ mod params;
mod unified_diff;
mod utils;
/// # Panics
/// Panics if the binary path cannot be determined
fn binary_path(args: &mut Peekable<ArgsOs>) -> PathBuf {
match args.peek() {
Some(ref s) if !s.is_empty() => PathBuf::from(s),
_ => std::env::current_exe().unwrap(),
}
}
/// #Panics
/// Panics if path has no UTF-8 valid name
fn name(binary_path: &Path) -> &OsStr {
binary_path.file_stem().unwrap()
}
const VERSION: &str = env!("CARGO_PKG_VERSION");
fn usage(name: &str) {
println!("{name} {VERSION} (multi-call binary)\n");
println!("Usage: {name} [function [arguments...]]\n");
println!("Currently defined functions:\n");
println!(" cmp, diff\n");
}
fn second_arg_error(name: &OsStr) -> ! {
eprintln!("Expected utility name as second argument, got nothing.");
usage(&name.to_string_lossy());
std::process::exit(0);
}
// 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 mut args = std::env::args_os().peekable();
let exe_path = binary_path(&mut args);
let exe_name = name(&exe_path);
let util_name = if exe_name == "diffutils" {
// Discard the item we peeked.
let _ = args.next();
args.peek()
.cloned()
.unwrap_or_else(|| second_arg_error(exe_name))
} else {
OsString::from(exe_name)
};
match util_name.to_str() {
Some("diff") => diff::main(args),
Some("cmp") => cmp::main(args),
Some(name) => {
eprintln!("{}: utility not supported", name);
ExitCode::from(2)
let opts = env::args_os();
let params = parse_params(opts).unwrap_or_else(|error| {
eprintln!("{error}");
exit(2);
});
// if from and to are the same file, no need to perform any comparison
let maybe_report_identical_files = || {
if params.report_identical_files {
println!(
"Files {} and {} are identical",
params.from.to_string_lossy(),
params.to.to_string_lossy(),
);
}
None => second_arg_error(exe_name),
};
if params.from == "-" && params.to == "-"
|| same_file::is_same_file(&params.from, &params.to).unwrap_or(false)
{
maybe_report_identical_files();
return ExitCode::SUCCESS;
}
// read files
fn read_file_contents(filepath: &OsString) -> io::Result<Vec<u8>> {
if filepath == "-" {
let mut content = Vec::new();
io::stdin().read_to_end(&mut content).and(Ok(content))
} else {
fs::read(filepath)
}
}
let from_content = match read_file_contents(&params.from) {
Ok(from_content) => from_content,
Err(e) => {
eprintln!("Failed to read from-file: {e}");
return ExitCode::from(2);
}
};
let to_content = match read_file_contents(&params.to) {
Ok(to_content) => to_content,
Err(e) => {
eprintln!("Failed to read to-file: {e}");
return ExitCode::from(2);
}
};
// run diff
let result: Vec<u8> = match params.format {
Format::Normal => normal_diff::diff(&from_content, &to_content, &params),
Format::Unified => unified_diff::diff(&from_content, &to_content, &params),
Format::Context => context_diff::diff(&from_content, &to_content, &params),
Format::Ed => ed_diff::diff(&from_content, &to_content, &params).unwrap_or_else(|error| {
eprintln!("{error}");
exit(2);
}),
};
if params.brief && !result.is_empty() {
println!(
"Files {} and {} differ",
params.from.to_string_lossy(),
params.to.to_string_lossy()
);
} else {
io::stdout().write_all(&result).unwrap();
}
if result.is_empty() {
maybe_report_identical_files();
ExitCode::SUCCESS
} else {
ExitCode::from(1)
}
}
+24 -24
View File
@@ -275,26 +275,26 @@ mod tests {
// This test diff is intentionally reversed.
// We want it to turn the alef into bet.
let diff = diff(&alef, &bet, &Params::default());
File::create(format!("{target}/ab.diff"))
File::create(&format!("{target}/ab.diff"))
.unwrap()
.write_all(&diff)
.unwrap();
let mut fa = File::create(format!("{target}/alef")).unwrap();
let mut fa = File::create(&format!("{target}/alef")).unwrap();
fa.write_all(&alef[..]).unwrap();
let mut fb = File::create(format!("{target}/bet")).unwrap();
let mut fb = File::create(&format!("{target}/bet")).unwrap();
fb.write_all(&bet[..]).unwrap();
let _ = fa;
let _ = fb;
let output = Command::new("patch")
.arg("-p0")
.arg(format!("{target}/alef"))
.stdin(File::open(format!("{target}/ab.diff")).unwrap())
.arg(&format!("{target}/alef"))
.stdin(File::open(&format!("{target}/ab.diff")).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();
let alef = fs::read(&format!("{target}/alef")).unwrap();
assert_eq!(alef, bet);
}
}
@@ -367,27 +367,27 @@ mod tests {
// This test diff is intentionally reversed.
// We want it to turn the alef into bet.
let diff = diff(&alef, &bet, &Params::default());
File::create(format!("{target}/abn.diff"))
File::create(&format!("{target}/abn.diff"))
.unwrap()
.write_all(&diff)
.unwrap();
let mut fa = File::create(format!("{target}/alefn")).unwrap();
let mut fa = File::create(&format!("{target}/alefn")).unwrap();
fa.write_all(&alef[..]).unwrap();
let mut fb = File::create(format!("{target}/betn")).unwrap();
let mut fb = File::create(&format!("{target}/betn")).unwrap();
fb.write_all(&bet[..]).unwrap();
let _ = fa;
let _ = fb;
let output = Command::new("patch")
.arg("-p0")
.arg("--normal")
.arg(format!("{target}/alefn"))
.stdin(File::open(format!("{target}/abn.diff")).unwrap())
.arg(&format!("{target}/alefn"))
.stdin(File::open(&format!("{target}/abn.diff")).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}/alefn")).unwrap();
let alef = fs::read(&format!("{target}/alefn")).unwrap();
assert_eq!(alef, bet);
}
}
@@ -441,26 +441,26 @@ mod tests {
// This test diff is intentionally reversed.
// We want it to turn the alef into bet.
let diff = diff(&alef, &bet, &Params::default());
File::create(format!("{target}/ab_.diff"))
File::create(&format!("{target}/ab_.diff"))
.unwrap()
.write_all(&diff)
.unwrap();
let mut fa = File::create(format!("{target}/alef_")).unwrap();
let mut fa = File::create(&format!("{target}/alef_")).unwrap();
fa.write_all(&alef[..]).unwrap();
let mut fb = File::create(format!("{target}/bet_")).unwrap();
let mut fb = File::create(&format!("{target}/bet_")).unwrap();
fb.write_all(&bet[..]).unwrap();
let _ = fa;
let _ = fb;
let output = Command::new("patch")
.arg("-p0")
.arg(format!("{target}/alef_"))
.stdin(File::open(format!("{target}/ab_.diff")).unwrap())
.arg(&format!("{target}/alef_"))
.stdin(File::open(&format!("{target}/ab_.diff")).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();
let alef = fs::read(&format!("{target}/alef_")).unwrap();
assert_eq!(alef, bet);
}
}
@@ -519,26 +519,26 @@ mod tests {
// This test diff is intentionally reversed.
// We want it to turn the alef into bet.
let diff = diff(&alef, &bet, &Params::default());
File::create(format!("{target}/abr.diff"))
File::create(&format!("{target}/abr.diff"))
.unwrap()
.write_all(&diff)
.unwrap();
let mut fa = File::create(format!("{target}/alefr")).unwrap();
let mut fa = File::create(&format!("{target}/alefr")).unwrap();
fa.write_all(&alef[..]).unwrap();
let mut fb = File::create(format!("{target}/betr")).unwrap();
let mut fb = File::create(&format!("{target}/betr")).unwrap();
fb.write_all(&bet[..]).unwrap();
let _ = fa;
let _ = fb;
let output = Command::new("patch")
.arg("-p0")
.arg(format!("{target}/alefr"))
.stdin(File::open(format!("{target}/abr.diff")).unwrap())
.arg(&format!("{target}/alefr"))
.stdin(File::open(&format!("{target}/abr.diff")).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();
let alef = fs::read(&format!("{target}/alefr")).unwrap();
assert_eq!(alef, bet);
}
}
+99 -467
View File
@@ -1,6 +1,4 @@
use std::ffi::OsString;
use std::iter::Peekable;
use std::path::PathBuf;
use std::ffi::{OsStr, OsString};
use regex::Regex;
@@ -13,9 +11,19 @@ pub enum Format {
Ed,
}
#[cfg(unix)]
fn osstr_bytes(osstr: &OsStr) -> &[u8] {
use std::os::unix::ffi::OsStrExt;
osstr.as_bytes()
}
#[cfg(not(unix))]
fn osstr_bytes(osstr: &OsStr) -> Vec<u8> {
osstr.to_string_lossy().bytes().collect()
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Params {
pub executable: OsString,
pub from: OsString,
pub to: OsString,
pub format: Format,
@@ -29,7 +37,6 @@ pub struct Params {
impl Default for Params {
fn default() -> Self {
Self {
executable: OsString::default(),
from: OsString::default(),
to: OsString::default(),
format: Format::default(),
@@ -42,23 +49,19 @@ impl Default for Params {
}
}
pub fn parse_params<I: Iterator<Item = OsString>>(mut opts: Peekable<I>) -> Result<Params, String> {
pub fn parse_params<I: IntoIterator<Item = OsString>>(opts: I) -> Result<Params, String> {
let mut opts = opts.into_iter();
// parse CLI
let Some(executable) = opts.next() else {
let Some(exe) = opts.next() else {
return Err("Usage: <exe> <from> <to>".to_string());
};
let mut params = Params {
executable,
..Default::default()
};
let mut params = Params::default();
let mut from = None;
let mut to = None;
let mut format = None;
let mut context = None;
let tabsize_re = Regex::new(r"^--tabsize=(?<num>\d+)$").unwrap();
while let Some(param) = opts.next() {
let next_param = opts.peek();
if param == "--" {
break;
}
@@ -68,10 +71,7 @@ pub fn parse_params<I: Iterator<Item = OsString>>(mut opts: Peekable<I>) -> Resu
} else if to.is_none() {
to = Some(param);
} else {
return Err(format!(
"Usage: {} <from> <to>",
params.executable.to_string_lossy()
));
return Err(format!("Usage: {} <from> <to>", exe.to_string_lossy()));
}
continue;
}
@@ -87,20 +87,6 @@ pub fn parse_params<I: Iterator<Item = OsString>>(mut opts: Peekable<I>) -> Resu
params.expand_tabs = true;
continue;
}
if param == "--normal" {
if format.is_some() && format != Some(Format::Normal) {
return Err("Conflicting output style options".to_string());
}
format = Some(Format::Normal);
continue;
}
if param == "-e" || param == "--ed" {
if format.is_some() && format != Some(Format::Ed) {
return Err("Conflicting output style options".to_string());
}
format = Some(Format::Ed);
continue;
}
if tabsize_re.is_match(param.to_string_lossy().as_ref()) {
// Because param matches the regular expression,
// it is safe to assume it is valid UTF-8.
@@ -117,56 +103,65 @@ pub fn parse_params<I: Iterator<Item = OsString>>(mut opts: Peekable<I>) -> Resu
};
continue;
}
match match_context_diff_params(&param, next_param, format) {
Ok(DiffStyleMatch {
is_match,
context_count,
next_param_consumed,
}) => {
if is_match {
format = Some(Format::Context);
if context_count.is_some() {
context = context_count;
let p = osstr_bytes(&param);
if p.first() == Some(&b'-') && p.get(1) != Some(&b'-') {
let mut bit = p[1..].iter().copied().peekable();
// Can't use a for loop because `diff -30u` is supposed to make a diff
// with 30 lines of context.
while let Some(b) = bit.next() {
match b {
b'0'..=b'9' => {
params.context_count = (b - b'0') as usize;
while let Some(b'0'..=b'9') = bit.peek() {
params.context_count *= 10;
params.context_count += (bit.next().unwrap() - b'0') as usize;
}
}
if next_param_consumed {
opts.next();
b'c' => {
if format.is_some() && format != Some(Format::Context) {
return Err("Conflicting output style options".to_string());
}
format = Some(Format::Context);
}
continue;
b'e' => {
if format.is_some() && format != Some(Format::Ed) {
return Err("Conflicting output style options".to_string());
}
format = Some(Format::Ed);
}
b'u' => {
if format.is_some() && format != Some(Format::Unified) {
return Err("Conflicting output style options".to_string());
}
format = Some(Format::Unified);
}
b'U' => {
if format.is_some() && format != Some(Format::Unified) {
return Err("Conflicting output style options".to_string());
}
format = Some(Format::Unified);
let context_count_maybe = if bit.peek().is_some() {
String::from_utf8(bit.collect::<Vec<u8>>()).ok()
} else {
opts.next().map(|x| x.to_string_lossy().into_owned())
};
if let Some(context_count_maybe) =
context_count_maybe.and_then(|x| x.parse().ok())
{
params.context_count = context_count_maybe;
break;
}
return Err("Invalid context count".to_string());
}
_ => return Err(format!("Unknown option: {}", String::from_utf8_lossy(&[b]))),
}
}
Err(error) => return Err(error),
}
match match_unified_diff_params(&param, next_param, format) {
Ok(DiffStyleMatch {
is_match,
context_count,
next_param_consumed,
}) => {
if is_match {
format = Some(Format::Unified);
if context_count.is_some() {
context = context_count;
}
if next_param_consumed {
opts.next();
}
continue;
}
}
Err(error) => return Err(error),
}
if param.to_string_lossy().starts_with('-') {
return Err(format!("Unknown option: {:?}", param));
}
if from.is_none() {
} else if from.is_none() {
from = Some(param);
} else if to.is_none() {
to = Some(param);
} else {
return Err(format!(
"Usage: {} <from> <to>",
params.executable.to_string_lossy()
));
return Err(format!("Usage: {} <from> <to>", exe.to_string_lossy()));
}
}
params.from = if let Some(from) = from {
@@ -174,140 +169,19 @@ pub fn parse_params<I: Iterator<Item = OsString>>(mut opts: Peekable<I>) -> Resu
} else if let Some(param) = opts.next() {
param
} else {
return Err(format!(
"Usage: {} <from> <to>",
params.executable.to_string_lossy()
));
return Err(format!("Usage: {} <from> <to>", exe.to_string_lossy()));
};
params.to = if let Some(to) = to {
to
} else if let Some(param) = opts.next() {
param
} else {
return Err(format!(
"Usage: {} <from> <to>",
params.executable.to_string_lossy()
));
return Err(format!("Usage: {} <from> <to>", exe.to_string_lossy()));
};
// diff DIRECTORY FILE => diff DIRECTORY/FILE FILE
// diff FILE DIRECTORY => diff FILE DIRECTORY/FILE
let mut from_path: PathBuf = PathBuf::from(&params.from);
let mut to_path: PathBuf = PathBuf::from(&params.to);
if from_path.is_dir() && to_path.is_file() {
from_path.push(to_path.file_name().unwrap());
params.from = from_path.into_os_string();
} else if from_path.is_file() && to_path.is_dir() {
to_path.push(from_path.file_name().unwrap());
params.to = to_path.into_os_string();
}
params.format = format.unwrap_or(Format::default());
if let Some(context_count) = context {
params.context_count = context_count;
}
Ok(params)
}
struct DiffStyleMatch {
is_match: bool,
context_count: Option<usize>,
next_param_consumed: bool,
}
fn match_context_diff_params(
param: &OsString,
next_param: Option<&OsString>,
format: Option<Format>,
) -> Result<DiffStyleMatch, String> {
const CONTEXT_RE: &str = r"^(-[cC](?<num1>\d*)|--context(=(?<num2>\d*))?|-(?<num3>\d+)c)$";
let regex = Regex::new(CONTEXT_RE).unwrap();
let is_match = regex.is_match(param.to_string_lossy().as_ref());
let mut context_count = None;
let mut next_param_consumed = false;
if is_match {
if format.is_some() && format != Some(Format::Context) {
return Err("Conflicting output style options".to_string());
}
let captures = regex.captures(param.to_str().unwrap()).unwrap();
let num = captures
.name("num1")
.or(captures.name("num2"))
.or(captures.name("num3"));
if let Some(numvalue) = num {
if !numvalue.as_str().is_empty() {
context_count = Some(numvalue.as_str().parse::<usize>().unwrap());
}
}
if param == "-C" && next_param.is_some() {
match next_param.unwrap().to_string_lossy().parse::<usize>() {
Ok(context_size) => {
context_count = Some(context_size);
next_param_consumed = true;
}
Err(_) => {
return Err(format!(
"invalid context length '{}'",
next_param.unwrap().to_string_lossy()
))
}
}
}
}
Ok(DiffStyleMatch {
is_match,
context_count,
next_param_consumed,
})
}
fn match_unified_diff_params(
param: &OsString,
next_param: Option<&OsString>,
format: Option<Format>,
) -> Result<DiffStyleMatch, String> {
const UNIFIED_RE: &str = r"^(-[uU](?<num1>\d*)|--unified(=(?<num2>\d*))?|-(?<num3>\d+)u)$";
let regex = Regex::new(UNIFIED_RE).unwrap();
let is_match = regex.is_match(param.to_string_lossy().as_ref());
let mut context_count = None;
let mut next_param_consumed = false;
if is_match {
if format.is_some() && format != Some(Format::Unified) {
return Err("Conflicting output style options".to_string());
}
let captures = regex.captures(param.to_str().unwrap()).unwrap();
let num = captures
.name("num1")
.or(captures.name("num2"))
.or(captures.name("num3"));
if let Some(numvalue) = num {
if !numvalue.as_str().is_empty() {
context_count = Some(numvalue.as_str().parse::<usize>().unwrap());
}
}
if param == "-U" && next_param.is_some() {
match next_param.unwrap().to_string_lossy().parse::<usize>() {
Ok(context_size) => {
context_count = Some(context_size);
next_param_consumed = true;
}
Err(_) => {
return Err(format!(
"invalid context length '{}'",
next_param.unwrap().to_string_lossy()
))
}
}
}
}
Ok(DiffStyleMatch {
is_match,
context_count,
next_param_consumed,
})
}
#[cfg(test)]
mod tests {
use super::*;
@@ -318,176 +192,29 @@ mod tests {
fn basics() {
assert_eq!(
Ok(Params {
executable: os("diff"),
from: os("foo"),
to: os("bar"),
..Default::default()
}),
parse_params(
[os("diff"), os("foo"), os("bar")]
.iter()
.cloned()
.peekable()
)
);
assert_eq!(
Ok(Params {
executable: os("diff"),
from: os("foo"),
to: os("bar"),
..Default::default()
}),
parse_params(
[os("diff"), os("--normal"), os("foo"), os("bar")]
.iter()
.cloned()
.peekable()
)
parse_params([os("diff"), os("foo"), os("bar")].iter().cloned())
);
}
#[test]
fn basics_ed() {
for arg in ["-e", "--ed"] {
assert_eq!(
Ok(Params {
executable: os("diff"),
from: os("foo"),
to: os("bar"),
format: Format::Ed,
..Default::default()
}),
parse_params(
[os("diff"), os(arg), os("foo"), os("bar")]
.iter()
.cloned()
.peekable()
)
);
}
}
#[test]
fn context_valid() {
for args in [vec!["-c"], vec!["--context"], vec!["--context="]] {
let mut params = vec!["diff"];
params.extend(args);
params.extend(["foo", "bar"]);
assert_eq!(
Ok(Params {
executable: os("diff"),
from: os("foo"),
to: os("bar"),
format: Format::Context,
..Default::default()
}),
parse_params(params.iter().map(|x| os(x)).peekable())
);
}
for args in [
vec!["-c42"],
vec!["-C42"],
vec!["-C", "42"],
vec!["--context=42"],
vec!["-42c"],
] {
let mut params = vec!["diff"];
params.extend(args);
params.extend(["foo", "bar"]);
assert_eq!(
Ok(Params {
executable: os("diff"),
from: os("foo"),
to: os("bar"),
format: Format::Context,
context_count: 42,
..Default::default()
}),
parse_params(params.iter().map(|x| os(x)).peekable())
);
}
}
#[test]
fn context_invalid() {
for args in [
vec!["-c", "42"],
vec!["-c=42"],
vec!["-c="],
vec!["-C"],
vec!["-C=42"],
vec!["-C="],
vec!["--context42"],
vec!["--context", "42"],
vec!["-42C"],
] {
let mut params = vec!["diff"];
params.extend(args);
params.extend(["foo", "bar"]);
assert!(parse_params(params.iter().map(|x| os(x)).peekable()).is_err());
}
}
#[test]
fn unified_valid() {
for args in [vec!["-u"], vec!["--unified"], vec!["--unified="]] {
let mut params = vec!["diff"];
params.extend(args);
params.extend(["foo", "bar"]);
assert_eq!(
Ok(Params {
executable: os("diff"),
from: os("foo"),
to: os("bar"),
format: Format::Unified,
..Default::default()
}),
parse_params(params.iter().map(|x| os(x)).peekable())
);
}
for args in [
vec!["-u42"],
vec!["-U42"],
vec!["-U", "42"],
vec!["--unified=42"],
vec!["-42u"],
] {
let mut params = vec!["diff"];
params.extend(args);
params.extend(["foo", "bar"]);
assert_eq!(
Ok(Params {
executable: os("diff"),
from: os("foo"),
to: os("bar"),
format: Format::Unified,
context_count: 42,
..Default::default()
}),
parse_params(params.iter().map(|x| os(x)).peekable())
);
}
}
#[test]
fn unified_invalid() {
for args in [
vec!["-u", "42"],
vec!["-u=42"],
vec!["-u="],
vec!["-U"],
vec!["-U=42"],
vec!["-U="],
vec!["--unified42"],
vec!["--unified", "42"],
vec!["-42U"],
] {
let mut params = vec!["diff"];
params.extend(args);
params.extend(["foo", "bar"]);
assert!(parse_params(params.iter().map(|x| os(x)).peekable()).is_err());
}
assert_eq!(
Ok(Params {
from: os("foo"),
to: os("bar"),
format: Format::Ed,
..Default::default()
}),
parse_params([os("diff"), os("-e"), os("foo"), os("bar")].iter().cloned())
);
}
#[test]
fn context_count() {
assert_eq!(
Ok(Params {
executable: os("diff"),
from: os("foo"),
to: os("bar"),
format: Format::Unified,
@@ -498,12 +225,10 @@ mod tests {
[os("diff"), os("-u54"), os("foo"), os("bar")]
.iter()
.cloned()
.peekable()
)
);
assert_eq!(
Ok(Params {
executable: os("diff"),
from: os("foo"),
to: os("bar"),
format: Format::Unified,
@@ -514,12 +239,10 @@ mod tests {
[os("diff"), os("-U54"), os("foo"), os("bar")]
.iter()
.cloned()
.peekable()
)
);
assert_eq!(
Ok(Params {
executable: os("diff"),
from: os("foo"),
to: os("bar"),
format: Format::Unified,
@@ -530,12 +253,10 @@ mod tests {
[os("diff"), os("-U"), os("54"), os("foo"), os("bar")]
.iter()
.cloned()
.peekable()
)
);
assert_eq!(
Ok(Params {
executable: os("diff"),
from: os("foo"),
to: os("bar"),
format: Format::Context,
@@ -546,7 +267,6 @@ mod tests {
[os("diff"), os("-c54"), os("foo"), os("bar")]
.iter()
.cloned()
.peekable()
)
);
}
@@ -554,36 +274,23 @@ mod tests {
fn report_identical_files() {
assert_eq!(
Ok(Params {
executable: os("diff"),
from: os("foo"),
to: os("bar"),
..Default::default()
}),
parse_params(
[os("diff"), os("foo"), os("bar")]
.iter()
.cloned()
.peekable()
)
parse_params([os("diff"), os("foo"), os("bar")].iter().cloned())
);
assert_eq!(
Ok(Params {
executable: os("diff"),
from: os("foo"),
to: os("bar"),
report_identical_files: true,
..Default::default()
}),
parse_params(
[os("diff"), os("-s"), os("foo"), os("bar")]
.iter()
.cloned()
.peekable()
)
parse_params([os("diff"), os("-s"), os("foo"), os("bar")].iter().cloned())
);
assert_eq!(
Ok(Params {
executable: os("diff"),
from: os("foo"),
to: os("bar"),
report_identical_files: true,
@@ -598,7 +305,6 @@ mod tests {
]
.iter()
.cloned()
.peekable()
)
);
}
@@ -606,36 +312,23 @@ mod tests {
fn brief() {
assert_eq!(
Ok(Params {
executable: os("diff"),
from: os("foo"),
to: os("bar"),
..Default::default()
}),
parse_params(
[os("diff"), os("foo"), os("bar")]
.iter()
.cloned()
.peekable()
)
parse_params([os("diff"), os("foo"), os("bar")].iter().cloned())
);
assert_eq!(
Ok(Params {
executable: os("diff"),
from: os("foo"),
to: os("bar"),
brief: true,
..Default::default()
}),
parse_params(
[os("diff"), os("-q"), os("foo"), os("bar")]
.iter()
.cloned()
.peekable()
)
parse_params([os("diff"), os("-q"), os("foo"), os("bar")].iter().cloned())
);
assert_eq!(
Ok(Params {
executable: os("diff"),
from: os("foo"),
to: os("bar"),
brief: true,
@@ -645,7 +338,6 @@ mod tests {
[os("diff"), os("--brief"), os("foo"), os("bar"),]
.iter()
.cloned()
.peekable()
)
);
}
@@ -653,22 +345,15 @@ mod tests {
fn expand_tabs() {
assert_eq!(
Ok(Params {
executable: os("diff"),
from: os("foo"),
to: os("bar"),
..Default::default()
}),
parse_params(
[os("diff"), os("foo"), os("bar")]
.iter()
.cloned()
.peekable()
)
parse_params([os("diff"), os("foo"), os("bar")].iter().cloned())
);
for option in ["-t", "--expand-tabs"] {
assert_eq!(
Ok(Params {
executable: os("diff"),
from: os("foo"),
to: os("bar"),
expand_tabs: true,
@@ -678,7 +363,6 @@ mod tests {
[os("diff"), os(option), os("foo"), os("bar")]
.iter()
.cloned()
.peekable()
)
);
}
@@ -687,21 +371,14 @@ mod tests {
fn tabsize() {
assert_eq!(
Ok(Params {
executable: os("diff"),
from: os("foo"),
to: os("bar"),
..Default::default()
}),
parse_params(
[os("diff"), os("foo"), os("bar")]
.iter()
.cloned()
.peekable()
)
parse_params([os("diff"), os("foo"), os("bar")].iter().cloned())
);
assert_eq!(
Ok(Params {
executable: os("diff"),
from: os("foo"),
to: os("bar"),
tabsize: 0,
@@ -711,12 +388,10 @@ mod tests {
[os("diff"), os("--tabsize=0"), os("foo"), os("bar")]
.iter()
.cloned()
.peekable()
)
);
assert_eq!(
Ok(Params {
executable: os("diff"),
from: os("foo"),
to: os("bar"),
tabsize: 42,
@@ -726,42 +401,36 @@ mod tests {
[os("diff"), os("--tabsize=42"), os("foo"), os("bar")]
.iter()
.cloned()
.peekable()
)
);
assert!(parse_params(
[os("diff"), os("--tabsize"), os("foo"), os("bar")]
.iter()
.cloned()
.peekable()
)
.is_err());
assert!(parse_params(
[os("diff"), os("--tabsize="), os("foo"), os("bar")]
.iter()
.cloned()
.peekable()
)
.is_err());
assert!(parse_params(
[os("diff"), os("--tabsize=r2"), os("foo"), os("bar")]
.iter()
.cloned()
.peekable()
)
.is_err());
assert!(parse_params(
[os("diff"), os("--tabsize=-1"), os("foo"), os("bar")]
.iter()
.cloned()
.peekable()
)
.is_err());
assert!(parse_params(
[os("diff"), os("--tabsize=r2"), os("foo"), os("bar")]
.iter()
.cloned()
.peekable()
)
.is_err());
assert!(parse_params(
@@ -773,7 +442,6 @@ mod tests {
]
.iter()
.cloned()
.peekable()
)
.is_err());
}
@@ -781,102 +449,66 @@ mod tests {
fn double_dash() {
assert_eq!(
Ok(Params {
executable: os("diff"),
from: os("-g"),
to: os("-h"),
..Default::default()
}),
parse_params(
[os("diff"), os("--"), os("-g"), os("-h")]
.iter()
.cloned()
.peekable()
)
parse_params([os("diff"), os("--"), os("-g"), os("-h")].iter().cloned())
);
}
#[test]
fn default_to_stdin() {
assert_eq!(
Ok(Params {
executable: os("diff"),
from: os("foo"),
to: os("-"),
..Default::default()
}),
parse_params([os("diff"), os("foo"), os("-")].iter().cloned().peekable())
parse_params([os("diff"), os("foo"), os("-")].iter().cloned())
);
assert_eq!(
Ok(Params {
executable: os("diff"),
from: os("-"),
to: os("bar"),
..Default::default()
}),
parse_params([os("diff"), os("-"), os("bar")].iter().cloned().peekable())
parse_params([os("diff"), os("-"), os("bar")].iter().cloned())
);
assert_eq!(
Ok(Params {
executable: os("diff"),
from: os("-"),
to: os("-"),
..Default::default()
}),
parse_params([os("diff"), os("-"), os("-")].iter().cloned().peekable())
parse_params([os("diff"), os("-"), os("-")].iter().cloned())
);
assert!(parse_params(
[os("diff"), os("foo"), os("bar"), os("-")]
.iter()
.cloned()
.peekable()
)
.is_err());
assert!(parse_params(
[os("diff"), os("-"), os("-"), os("-")]
.iter()
.cloned()
.peekable()
)
.is_err());
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 missing_arguments() {
assert!(parse_params([os("diff")].iter().cloned().peekable()).is_err());
assert!(parse_params([os("diff"), os("foo")].iter().cloned().peekable()).is_err());
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()
.peekable()
)
.is_err());
assert!(
parse_params([os("diff"), os("-g"), os("bar")].iter().cloned().peekable()).is_err()
parse_params([os("diff"), os("-g"), os("foo"), os("bar")].iter().cloned()).is_err()
);
assert!(parse_params([os("diff"), os("-g")].iter().cloned().peekable()).is_err());
assert!(parse_params([os("diff"), os("-g"), os("bar")].iter().cloned()).is_err());
assert!(parse_params([os("diff"), os("-g")].iter().cloned()).is_err());
}
#[test]
fn empty() {
assert!(parse_params([].iter().cloned().peekable()).is_err());
assert!(parse_params([].iter().cloned()).is_err());
}
#[test]
fn conflicting_output_styles() {
for (arg1, arg2) in [
("-u", "-c"),
("-u", "-e"),
("-c", "-u"),
("-c", "-U42"),
("-u", "--normal"),
("--normal", "-e"),
("--context", "--normal"),
] {
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()
.peekable()
)
.is_err());
}
+25 -25
View File
@@ -466,13 +466,13 @@ mod tests {
..Default::default()
},
);
File::create(format!("{target}/ab.diff"))
File::create(&format!("{target}/ab.diff"))
.unwrap()
.write_all(&diff)
.unwrap();
let mut fa = File::create(format!("{target}/alef")).unwrap();
let mut fa = File::create(&format!("{target}/alef")).unwrap();
fa.write_all(&alef[..]).unwrap();
let mut fb = File::create(format!("{target}/bet")).unwrap();
let mut fb = File::create(&format!("{target}/bet")).unwrap();
fb.write_all(&bet[..]).unwrap();
let _ = fa;
let _ = fb;
@@ -494,13 +494,13 @@ mod tests {
let output = Command::new("patch")
.arg("-p0")
.stdin(File::open(format!("{target}/ab.diff")).unwrap())
.stdin(File::open(&format!("{target}/ab.diff")).unwrap())
.output()
.unwrap();
println!("{}", String::from_utf8_lossy(&output.stdout));
println!("{}", String::from_utf8_lossy(&output.stderr));
assert!(output.status.success(), "{output:?}");
let alef = fs::read(format!("{target}/alef")).unwrap();
let alef = fs::read(&format!("{target}/alef")).unwrap();
assert_eq!(alef, bet);
}
}
@@ -582,25 +582,25 @@ mod tests {
..Default::default()
},
);
File::create(format!("{target}/abn.diff"))
File::create(&format!("{target}/abn.diff"))
.unwrap()
.write_all(&diff)
.unwrap();
let mut fa = File::create(format!("{target}/alefn")).unwrap();
let mut fa = File::create(&format!("{target}/alefn")).unwrap();
fa.write_all(&alef[..]).unwrap();
let mut fb = File::create(format!("{target}/betn")).unwrap();
let mut fb = File::create(&format!("{target}/betn")).unwrap();
fb.write_all(&bet[..]).unwrap();
let _ = fa;
let _ = fb;
let output = Command::new("patch")
.arg("-p0")
.stdin(File::open(format!("{target}/abn.diff")).unwrap())
.stdin(File::open(&format!("{target}/abn.diff")).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}/alefn")).unwrap();
let alef = fs::read(&format!("{target}/alefn")).unwrap();
assert_eq!(alef, bet);
}
}
@@ -678,25 +678,25 @@ mod tests {
..Default::default()
},
);
File::create(format!("{target}/ab_.diff"))
File::create(&format!("{target}/ab_.diff"))
.unwrap()
.write_all(&diff)
.unwrap();
let mut fa = File::create(format!("{target}/alef_")).unwrap();
let mut fa = File::create(&format!("{target}/alef_")).unwrap();
fa.write_all(&alef[..]).unwrap();
let mut fb = File::create(format!("{target}/bet_")).unwrap();
let mut fb = File::create(&format!("{target}/bet_")).unwrap();
fb.write_all(&bet[..]).unwrap();
let _ = fa;
let _ = fb;
let output = Command::new("patch")
.arg("-p0")
.stdin(File::open(format!("{target}/ab_.diff")).unwrap())
.stdin(File::open(&format!("{target}/ab_.diff")).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();
let alef = fs::read(&format!("{target}/alef_")).unwrap();
assert_eq!(alef, bet);
}
}
@@ -759,25 +759,25 @@ mod tests {
..Default::default()
},
);
File::create(format!("{target}/abx.diff"))
File::create(&format!("{target}/abx.diff"))
.unwrap()
.write_all(&diff)
.unwrap();
let mut fa = File::create(format!("{target}/alefx")).unwrap();
let mut fa = File::create(&format!("{target}/alefx")).unwrap();
fa.write_all(&alef[..]).unwrap();
let mut fb = File::create(format!("{target}/betx")).unwrap();
let mut fb = File::create(&format!("{target}/betx")).unwrap();
fb.write_all(&bet[..]).unwrap();
let _ = fa;
let _ = fb;
let output = Command::new("patch")
.arg("-p0")
.stdin(File::open(format!("{target}/abx.diff")).unwrap())
.stdin(File::open(&format!("{target}/abx.diff")).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}/alefx")).unwrap();
let alef = fs::read(&format!("{target}/alefx")).unwrap();
assert_eq!(alef, bet);
}
}
@@ -845,25 +845,25 @@ mod tests {
..Default::default()
},
);
File::create(format!("{target}/abr.diff"))
File::create(&format!("{target}/abr.diff"))
.unwrap()
.write_all(&diff)
.unwrap();
let mut fa = File::create(format!("{target}/alefr")).unwrap();
let mut fa = File::create(&format!("{target}/alefr")).unwrap();
fa.write_all(&alef[..]).unwrap();
let mut fb = File::create(format!("{target}/betr")).unwrap();
let mut fb = File::create(&format!("{target}/betr")).unwrap();
fb.write_all(&bet[..]).unwrap();
let _ = fa;
let _ = fb;
let output = Command::new("patch")
.arg("-p0")
.stdin(File::open(format!("{target}/abr.diff")).unwrap())
.stdin(File::open(&format!("{target}/abr.diff")).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();
let alef = fs::read(&format!("{target}/alefr")).unwrap();
assert_eq!(alef, bet);
}
}
+5 -34
View File
@@ -3,9 +3,8 @@
// For the full copyright and license information, please view the LICENSE-*
// files that was distributed with this source code.
use std::{ffi::OsString, io::Write};
use std::io::Write;
use regex::Regex;
use unicode_width::UnicodeWidthStr;
/// Replace tabs by spaces in the input line.
@@ -72,33 +71,6 @@ pub fn get_modification_time(file_path: &str) -> String {
modification_time
}
pub fn format_failure_to_read_input_file(
executable: &OsString,
filepath: &OsString,
error: &std::io::Error,
) -> String {
// std::io::Error's display trait outputs "{detail} (os error {code})"
// but we want only the {detail} (error string) part
let error_code_re = Regex::new(r"\ \(os\ error\ \d+\)$").unwrap();
format!(
"{}: {}: {}",
executable.to_string_lossy(),
filepath.to_string_lossy(),
error_code_re.replace(error.to_string().as_str(), ""),
)
}
pub fn report_failure_to_read_input_file(
executable: &OsString,
filepath: &OsString,
error: &std::io::Error,
) {
eprintln!(
"{}",
format_failure_to_read_input_file(executable, filepath, error)
);
}
#[cfg(test)]
mod tests {
use super::*;
@@ -129,11 +101,10 @@ mod tests {
// 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 has a print size of 2 columns.
// Terminal emulators tend to not support this, and display the two emojis
// side by side, thus accounting for a print size of 4 columns, but the
// unicode_width crate reports a correct size of 2.
assert_tab_expansion("foo\t👩‍🔬\tbaz", 6, "foo 👩‍🔬 baz");
// 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]
+198 -834
View File
File diff suppressed because it is too large Load Diff
+26 -39
View File
@@ -19,9 +19,9 @@
# 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 overridden by the $TESTS environment variable, all tests in the 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. diff3 or sdiff) are skipped.
# (e.g. cmp, diff3 or sdiff) are skipped.
scriptpath=$(dirname "$(readlink -f "$0")")
rev=$(git rev-parse HEAD)
@@ -57,13 +57,8 @@ upstreamrev=$(git rev-parse HEAD)
mkdir src
cd src
ln -s "$binary" diff
ln -s "$binary" cmp
cd ../tests
# Fetch tests/init.sh from the gnulib repository (needed since
# https://git.savannah.gnu.org/cgit/diffutils.git/commit/tests?id=1d2456f539)
curl -s "$gitserver/gitweb/?p=gnulib.git;a=blob_plain;f=tests/init.sh;hb=HEAD" -o init.sh
if [[ -n "$TESTS" ]]
then
tests="$TESTS"
@@ -76,6 +71,7 @@ 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
@@ -86,43 +82,35 @@ for test in $tests
do
result="FAIL"
url="$urlroot$test?id=$upstreamrev"
# Run only the tests that invoke `diff` or `cmp`,
# Run only the tests that invoke `diff`,
# because other binaries aren't implemented yet
if ! grep -E -s -q "(diff3|sdiff)" "$test"
if ! grep -E -s -q "(cmp|diff3|sdiff)" "$test"
then
sh "$test" 1> stdout.txt 2> stderr.txt && result="PASS"
if [[ $? = 77 ]]
then
result="SKIP"
else
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++ ))
fi
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
if [[ $result = "SKIP" ]]
then
(( skipped++ ))
json+="{\"test\":\"$test\",\"url\":\"$url\",\"result\":\"$result\"},"
color=3 # yellow
fi
[[ "$result" = "SKIP" ]] && color=3 # yellow
printf " %-40s $(tput setaf $color)$result$(tput sgr0)\n" "$test"
done
echo ""
@@ -150,5 +138,4 @@ resultsfile="test-results.json"
echo "$json" | jq > "$resultsfile"
echo "Results written to $scriptpath/$resultsfile"
(( failed > 0 )) && exit 1
exit 0
exit $exitcode