Compare commits

48 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
Sylvestre Ledru 46a26e896b Merge pull request #58 from oSoMoN/ed-diff-tests-fix-path
Move test assertions in the cfg block where they belong (fixes #3)
2024-04-21 09:37:58 +02:00
Olivier Tilloy 14799eea89 Move test assertions in the cfg block where they belong 2024-04-21 00:13:52 +02:00
Olivier Tilloy 831348d1fc Fix file path in ed diff tests 2024-04-21 00:12:43 +02:00
Sylvestre Ledru 00a5c0ba44 Merge pull request #57 from oSoMoN/windows-fix-path-for-ci
CI: On Windows, use GNU's patch.exe instead of Strawberry Perl patch
2024-04-20 19:44:40 +02:00
Olivier Tilloy bf104648c1 CI: On Windows, use GNU's patch.exe instead of Strawberry Perl patch 2024-04-20 19:30:34 +02:00
Olivier Tilloy 5669f164b3 Merge pull request #34 from uutils/renovate/regex-1.x
Update Rust crate regex to 1.10.4
2024-04-17 19:19:00 +02:00
Sylvestre Ledru 11bf271666 Merge pull request #9 from uutils/renovate/codecov-codecov-action-4.x
Update codecov/codecov-action action to v4
2024-04-16 22:18:53 +02:00
renovate[bot] 674974d5e6 Update Rust crate regex to 1.10.4 2024-04-16 19:54:53 +00:00
Sylvestre Ledru 2ba35db431 Merge pull request #4 from uutils/renovate/diff-0.x
Update Rust crate diff to 0.1.13
2024-04-16 21:53:45 +02:00
renovate[bot] fcec7277c9 Update codecov/codecov-action action to v4 2024-04-16 19:35:04 +00:00
renovate[bot] b8efad6b90 Update Rust crate diff to 0.1.13 2024-04-16 19:35:01 +00:00
Sylvestre Ledru 68e2f51983 Merge pull request #54 from uutils/renovate/chrono-0.x
Update Rust crate chrono to 0.4.38
2024-04-16 21:34:16 +02:00
Sylvestre Ledru 4edaee190f Merge pull request #55 from oSoMoN/use-codecov-token
Use the private Codecov token stored as a secret,
2024-04-16 21:34:01 +02:00
Olivier Tilloy 7f7821f558 Use the private Codecov token stored as a secret,
to work around rate-limiting issues like https://github.com/codecov/codecov-action/issues/557
2024-04-16 18:37:59 +02:00
renovate[bot] 1149a247dd Update Rust crate chrono to 0.4.38 2024-04-16 16:20:52 +00:00
Olivier Tilloy 1b311c6673 Merge pull request #33 from TanmayPatil105/context-diff-modification-time
Display modification times of input files in context and unified diff
2024-04-16 18:20:00 +02:00
Tanmay Patil aedd0684d1 Replace only the first two occurences of timestamp regex 2024-04-16 10:41:38 +05:30
Tanmay Patil 54c02bdf0b Use NamedTempFile instead of manually creating files 2024-04-16 10:17:09 +05:30
Tanmay Patil ba7cb0aef9 Do not create dummy files
Since we now returning SystemTime::now() for invalid file input,
there is no need to crate dummy files
2024-04-14 22:56:37 +05:30
Tanmay Patil 33783d094e Improve tests 2024-04-14 17:16:53 +05:30
Tanmay Patil 900e1c3a68 Tests: Replace modification time in diff with "TIMESTAMP" placeholder 2024-04-14 13:43:30 +05:30
Tanmay Patil 0a77fe12b9 Add tests for get_modification_time function 2024-04-13 21:31:13 +05:30
Tanmay Patil 86bd05c739 Merge branch 'context-diff-modification-time' of github.com:TanmayPatil105/diffutils into context-diff-modification-time 2024-04-10 22:31:09 +05:30
Tanmay Patil 00e18a6b0c Define assert_diff_eq macro for context&unified diff comparison 2024-04-10 22:20:48 +05:30
Tanmay f6eb0835b0 Merge branch 'main' into context-diff-modification-time 2024-04-10 22:13:18 +05:30
Sylvestre Ledru be66ff3299 Merge pull request #47 from oSoMoN/handle-stdin-filename
Handle the rewrite of "-" to "/dev/stdin" in main to leave the filenames unchanged (fixes #46)
2024-04-09 09:53:53 +02:00
Olivier Tilloy e1c319f96b Add an integration test for reading from "/dev/stdin" on unix-like systems 2024-04-08 22:36:14 +02:00
Olivier Tilloy 84ad116845 Use io::stdin() to read from standard input in a portable manner 2024-04-08 20:21:24 +02:00
Olivier Tilloy 6dc34fed44 Handle the rewrite of "-" to "/dev/stdin" in main to leave the filenames unchanged (fixes #46) 2024-04-08 20:21:24 +02:00
Sylvestre Ledru 9507ca28d7 Merge pull request #51 from oSoMoN/unit-tests-for-conflicting-output-style
Unit test to verify that conflicting output styles result in an error
2024-04-06 08:47:53 +02:00
Olivier Tilloy c325291696 Unit test to verify that conflicting output styles result in an error 2024-04-05 23:22:26 +02:00
Olivier Tilloy c08e0b6e1f Merge pull request #25 from uutils/renovate/tempfile-3.x
chore(deps): update rust crate tempfile to 3.10.1
2024-04-04 22:50:50 +02:00
Tanmay Patil 72da7fca40 Show current time if fs::metadata errors 2024-04-04 20:01:11 +05:30
Tanmay 61fb0657c1 Merge branch 'main' into context-diff-modification-time 2024-04-04 19:56:13 +05:30
Sylvestre Ledru 096aa1dad9 Merge pull request #50 from cakebaker/disable_tests_using_ed_on_windows
Disable tests on Windows that use `ed`
2024-04-04 08:48:41 +02:00
Daniel Hofstetter 2d9e625a5b Disable tests on Windows that use ed 2024-04-04 08:30:54 +02:00
Daniel Hofstetter d863fe443a Merge pull request #48 from uutils/clip
Run clippy pedantic fixes
2024-04-04 07:45:18 +02:00
renovate[bot] 6be94d8683 chore(deps): update rust crate tempfile to 3.10.1 2024-04-03 22:31:44 +00:00
Sylvestre Ledru 44ef772e4a release: version 0.4.0 2024-04-04 00:30:46 +02:00
Sylvestre Ledru bbfca84e17 chore: wow shiny new cargo-dist CI 2024-04-04 00:29:50 +02:00
Tanmay Patil a3a372ff36 Display modification times of input files in unified diff 2024-04-04 00:13:41 +05:30
Tanmay Patil 5b814f8530 Fix tests 2024-04-03 10:50:52 +05:30
Tanmay Patil 88a7568b52 Merge branch 'main' into context-diff-modification-time 2024-04-01 13:05:37 +05:30
Tanmay Patil 80c9944bf7 Create foo/bar in target/context-diff 2024-03-31 22:57:51 +05:30
Sylvestre Ledru 043c5f9493 Merge branch 'main' into context-diff-modification-time 2024-03-31 16:17:23 +02:00
Tanmay Patil 9ff8f89626 Fix tests 2024-03-31 16:14:44 +05:30
Tanmay Patil 42eb15b87a Display modification times of input files in context diff
Fixes #31
2024-03-27 22:46:23 +05:30
13 changed files with 716 additions and 99 deletions
+10 -3
View File
@@ -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:
@@ -142,10 +150,9 @@ jobs:
grcov . --output-type lcov --output-path "${COVERAGE_REPORT_FILE}" --branch --ignore build.rs --ignore "vendor/*" --ignore "/*" --ignore "[a-zA-Z]:/*" --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()"
echo "report=${COVERAGE_REPORT_FILE}" >> $GITHUB_OUTPUT
- name: Upload coverage results (to Codecov.io)
uses: codecov/codecov-action@v3
# if: steps.vars.outputs.HAS_CODECOV_TOKEN
uses: codecov/codecov-action@v4
with:
# token: ${{ secrets.CODECOV_TOKEN }}
token: ${{ secrets.CODECOV_TOKEN }}
file: ${{ steps.coverage.outputs.report }}
## flags: IntegrationTests, UnitTests, ${{ steps.vars.outputs.CODECOV_FLAGS }}
flags: ${{ steps.vars.outputs.CODECOV_FLAGS }}
+266
View File
@@ -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<<EOF" >> "$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<<EOF" >> "$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/*"
Generated
+160 -5
View File
@@ -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.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
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"
@@ -75,9 +122,10 @@ checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
[[package]]
name = "diffutils"
version = "0.3.0"
version = "0.4.0"
dependencies = [
"assert_cmd",
"chrono",
"diff",
"predicates",
"pretty_assertions",
@@ -118,6 +166,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"
@@ -130,6 +210,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"
@@ -151,6 +237,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"
@@ -211,9 +303,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",
@@ -293,9 +385,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",
@@ -330,6 +422,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"
@@ -361,6 +507,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"
+23 -4
View File
@@ -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"
@@ -15,8 +15,9 @@ name = "diffutils"
path = "src/main.rs"
[dependencies]
diff = "0.1.10"
regex = "1.10.3"
chrono = "0.4.38"
diff = "0.1.13"
regex = "1.10.4"
same-file = "1.0.6"
unicode-width = "0.1.11"
@@ -24,4 +25,22 @@ 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]
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"
+16 -7
View File
@@ -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 {
@@ -267,10 +268,14 @@ fn make_diff(
#[must_use]
pub fn diff(expected: &[u8], actual: &[u8], params: &Params) -> Vec<u8> {
let from_modified_time = get_modification_time(&params.from.to_string_lossy());
let to_modified_time = get_modification_time(&params.to.to_string_lossy());
let mut output = format!(
"*** {0}\t\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);
@@ -717,6 +722,8 @@ mod tests {
#[test]
fn test_stop_early() {
use crate::assert_diff_eq;
let from_filename = "foo";
let from = ["a", "b", "c", ""].join("\n");
let to_filename = "bar";
@@ -731,9 +738,10 @@ mod tests {
..Default::default()
},
);
let expected_full = [
"*** foo\t",
"--- bar\t",
"*** foo\tTIMESTAMP",
"--- bar\tTIMESTAMP",
"***************",
"*** 1,3 ****",
" a",
@@ -746,7 +754,7 @@ mod tests {
"",
]
.join("\n");
assert_eq!(diff_full, expected_full.as_bytes());
assert_diff_eq!(diff_full, expected_full);
let diff_brief = diff(
from.as_bytes(),
@@ -758,8 +766,9 @@ mod tests {
..Default::default()
},
);
let expected_brief = ["*** foo\t", "--- bar\t", ""].join("\n");
assert_eq!(diff_brief, expected_brief.as_bytes());
let expected_brief = ["*** foo\tTIMESTAMP", "--- bar\tTIMESTAMP", ""].join("\n");
assert_diff_eq!(diff_brief, expected_brief);
let nodiff_full = diff(
from.as_bytes(),
+53 -41
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.
@@ -188,9 +189,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" })
@@ -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();
@@ -236,16 +236,20 @@ mod tests {
fb.write_all(&bet[..]).unwrap();
let _ = fa;
let _ = fb;
let output = Command::new("ed")
.arg(&format!("{target}/alef"))
.stdin(File::open("target/ab.ed").unwrap())
.output()
.unwrap();
assert!(output.status.success(), "{output:?}");
//println!("{}", String::from_utf8_lossy(&output.stdout));
//println!("{}", String::from_utf8_lossy(&output.stderr));
let alef = fs::read(&format!("{target}/alef")).unwrap();
assert_eq!(alef, bet);
#[cfg(not(windows))] // there's no ed on windows
{
use std::process::Command;
let output = Command::new("ed")
.arg(&format!("{target}/alef"))
.stdin(File::open(&format!("{target}/ab.ed")).unwrap())
.output()
.unwrap();
assert!(output.status.success(), "{output:?}");
//println!("{}", String::from_utf8_lossy(&output.stdout));
//println!("{}", String::from_utf8_lossy(&output.stderr));
let alef = std::fs::read(&format!("{target}/alef")).unwrap();
assert_eq!(alef, bet);
}
}
}
}
@@ -255,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.
@@ -265,9 +270,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();
@@ -296,27 +300,31 @@ mod tests {
}
// This test diff is intentionally reversed.
// We want it to turn the alef into bet.
let diff = diff_w(&alef, &bet, "target/alef_").unwrap();
File::create("target/ab_.ed")
let diff = diff_w(&alef, &bet, &format!("{target}/alef_")).unwrap();
File::create(&format!("{target}/ab_.ed"))
.unwrap()
.write_all(&diff)
.unwrap();
let mut fa = File::create("target/alef_").unwrap();
let mut fa = File::create(&format!("{target}/alef_")).unwrap();
fa.write_all(&alef[..]).unwrap();
let mut fb = File::create(&format!("{target}/bet_")).unwrap();
fb.write_all(&bet[..]).unwrap();
let _ = fa;
let _ = fb;
let output = Command::new("ed")
.arg("target/alef_")
.stdin(File::open("target/ab_.ed").unwrap())
.output()
.unwrap();
assert!(output.status.success(), "{output:?}");
//println!("{}", String::from_utf8_lossy(&output.stdout));
//println!("{}", String::from_utf8_lossy(&output.stderr));
let alef = fs::read("target/alef_").unwrap();
assert_eq!(alef, bet);
#[cfg(not(windows))] // there's no ed on windows
{
use std::process::Command;
let output = Command::new("ed")
.arg(&format!("{target}/alef_"))
.stdin(File::open(&format!("{target}/ab_.ed")).unwrap())
.output()
.unwrap();
assert!(output.status.success(), "{output:?}");
//println!("{}", String::from_utf8_lossy(&output.stdout));
//println!("{}", String::from_utf8_lossy(&output.stderr));
let alef = std::fs::read(&format!("{target}/alef_")).unwrap();
assert_eq!(alef, bet);
}
}
}
}
@@ -326,6 +334,7 @@ mod tests {
}
#[test]
#[cfg(not(windows))]
fn test_permutations_reverse() {
let target = "target/ed-diff/";
// test all possible six-line files.
@@ -336,9 +345,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" })
@@ -374,7 +382,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();
@@ -384,16 +392,20 @@ mod tests {
fb.write_all(&bet[..]).unwrap();
let _ = fa;
let _ = fb;
let output = Command::new("ed")
.arg(&format!("{target}/alefr"))
.stdin(File::open("target/abr.ed").unwrap())
.output()
.unwrap();
assert!(output.status.success(), "{output:?}");
//println!("{}", String::from_utf8_lossy(&output.stdout));
//println!("{}", String::from_utf8_lossy(&output.stderr));
let alef = fs::read(&format!("{target}/alefr")).unwrap();
assert_eq!(alef, bet);
#[cfg(not(windows))] // there's no ed on windows
{
use std::process::Command;
let output = Command::new("ed")
.arg(&format!("{target}/alefr"))
.stdin(File::open(&format!("{target}/abr.ed")).unwrap())
.output()
.unwrap();
assert!(output.status.success(), "{output:?}");
//println!("{}", String::from_utf8_lossy(&output.stdout));
//println!("{}", String::from_utf8_lossy(&output.stderr));
let alef = std::fs::read(&format!("{target}/alefr")).unwrap();
assert_eq!(alef, bet);
}
}
}
}
+1
View File
@@ -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;
+25
View File
@@ -0,0 +1,25 @@
// asserts equality of the actual diff and expected diff
// considering datetime varitations
//
// It replaces the modification time in the actual diff
// with 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) => {{
use regex::Regex;
use std::str;
let diff = str::from_utf8(&$actual).unwrap();
let re = Regex::new(r"\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d+ [+-]\d{4}").unwrap();
let actual = re.replacen(diff, 2, "TIMESTAMP");
assert_eq!(actual, $expected);
}};
}
+16 -5
View File
@@ -5,13 +5,14 @@
use crate::params::{parse_params, Format};
use std::env;
use std::ffi::OsString;
use std::fs;
use std::io::{self, Write};
use std::io::{self, Read, Write};
use std::process::{exit, ExitCode};
mod context_diff;
mod ed_diff;
mod macros;
mod normal_diff;
mod params;
mod unified_diff;
@@ -38,19 +39,29 @@ fn main() -> ExitCode {
);
}
};
if same_file::is_same_file(&params.from, &params.to).unwrap_or(false) {
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
let from_content = match fs::read(&params.from) {
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 fs::read(&params.to) {
let to_content = match read_file_contents(&params.to) {
Ok(to_content) => to_content,
Err(e) => {
eprintln!("Failed to read to-file: {e}");
+17 -6
View File
@@ -67,9 +67,9 @@ pub fn parse_params<I: IntoIterator<Item = OsString>>(opts: I) -> Result<Params,
}
if param == "-" {
if from.is_none() {
from = Some(OsString::from("/dev/stdin"));
from = Some(param);
} else if to.is_none() {
to = Some(OsString::from("/dev/stdin"));
to = Some(param);
} else {
return Err(format!("Usage: {} <from> <to>", 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())
@@ -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());
}
}
}
+16 -7
View File
@@ -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<u8> {
let from_modified_time = get_modification_time(&params.from.to_string_lossy());
let to_modified_time = get_modification_time(&params.to.to_string_lossy());
let mut output = format!(
"--- {0}\t\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);
@@ -870,6 +875,8 @@ mod tests {
#[test]
fn test_stop_early() {
use crate::assert_diff_eq;
let from_filename = "foo";
let from = ["a", "b", "c", ""].join("\n");
let to_filename = "bar";
@@ -884,9 +891,10 @@ mod tests {
..Default::default()
},
);
let expected_full = [
"--- foo\t",
"+++ bar\t",
"--- foo\tTIMESTAMP",
"+++ bar\tTIMESTAMP",
"@@ -1,3 +1,3 @@",
" a",
"-b",
@@ -895,7 +903,7 @@ mod tests {
"",
]
.join("\n");
assert_eq!(diff_full, expected_full.as_bytes());
assert_diff_eq!(diff_full, expected_full);
let diff_brief = diff(
from.as_bytes(),
@@ -907,8 +915,9 @@ mod tests {
..Default::default()
},
);
let expected_brief = ["--- foo\t", "+++ bar\t", ""].join("\n");
assert_eq!(diff_brief, expected_brief.as_bytes());
let expected_brief = ["--- foo\tTIMESTAMP", "+++ bar\tTIMESTAMP", ""].join("\n");
assert_diff_eq!(diff_brief, expected_brief);
let nodiff_full = diff(
from.as_bytes(),
+61
View File
@@ -52,6 +52,25 @@ 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;
use std::time::SystemTime;
let modification_time: SystemTime = fs::metadata(file_path)
.and_then(|m| m.modified())
.unwrap_or(SystemTime::now());
let modification_time: DateTime<Local> = 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::*;
@@ -115,4 +134,46 @@ 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::time::SystemTime;
use tempfile::NamedTempFile;
let temp = NamedTempFile::new().unwrap();
// set file modification time equal to current time
let current = SystemTime::now();
let _ = temp.as_file().set_modified(current);
// format current time
let current: DateTime<Local> = current.into();
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())
);
}
#[test]
fn invalid_file() {
use chrono::{DateTime, Local};
use std::time::SystemTime;
let invalid_file = "target/utils/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<Local> = SystemTime::now().into();
let m_time: DateTime<Local> = get_modification_time(invalid_file).parse().unwrap();
assert!(m_time > current_time);
}
}
}
+52 -21
View File
@@ -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;
@@ -22,25 +23,30 @@ fn unknown_param() -> Result<(), Box<dyn std::error::Error>> {
}
#[test]
fn cannot_read_from_file() -> Result<(), Box<dyn std::error::Error>> {
fn cannot_read_files() -> Result<(), Box<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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(())
}
@@ -173,33 +179,58 @@ fn read_from_stdin() -> Result<(), Box<dyn std::error::Error>> {
.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",
cmd.assert().code(predicate::eq(1)).failure();
let output = cmd.output().unwrap().stdout;
assert_diff_eq!(
output,
format!(
"--- {}\tTIMESTAMP\n+++ -\tTIMESTAMP\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",
cmd.assert().code(predicate::eq(1)).failure();
let output = cmd.output().unwrap().stdout;
assert_diff_eq!(
output,
format!(
"--- -\tTIMESTAMP\n+++ {}\tTIMESTAMP\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()
.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();
let output = cmd.output().unwrap().stdout;
assert_diff_eq!(
output,
format!(
"--- {}\tTIMESTAMP\n+++ /dev/stdin\tTIMESTAMP\n@@ -1 +1 @@\n-foo\n+bar\n",
file1.path().to_string_lossy()
)
);
}
Ok(())
}