mirror of
https://github.com/uutils/findutils.git
synced 2026-06-10 15:48:30 -07:00
Compare commits
73 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8bfa9d200b | |||
| a235b35ac3 | |||
| ca252c9727 | |||
| c995e25eb4 | |||
| fd54f9f72a | |||
| f145469cfd | |||
| f2202dc23f | |||
| fa4cce8a95 | |||
| 7136628c45 | |||
| db9d69f8eb | |||
| 42075f53f0 | |||
| 57b670680c | |||
| cf8a29d73b | |||
| bc715f84fa | |||
| 8736cde582 | |||
| af1741ba85 | |||
| 5b19ea2125 | |||
| d329702b69 | |||
| 84e4be85c1 | |||
| 1979483175 | |||
| a750b3225a | |||
| a2e350ce96 | |||
| d35aebf5ed | |||
| 9502a7c4eb | |||
| f74ef7c2af | |||
| 3ec8d4da46 | |||
| 62e370179b | |||
| 98fd45a182 | |||
| 665165b297 | |||
| d78f44a923 | |||
| a2631ae925 | |||
| c2279ffae8 | |||
| fba701b928 | |||
| d7057e09b9 | |||
| add14dbebf | |||
| ed97c851aa | |||
| 0ca2450ab2 | |||
| 52ee81296c | |||
| a0c9f2dfd4 | |||
| bd759d5e21 | |||
| 3badb05772 | |||
| ab1c2bacb7 | |||
| 393f0b9672 | |||
| 18dfc9b0cc | |||
| 0693242553 | |||
| aa6ff46078 | |||
| ab03198152 | |||
| b5e7dd52d9 | |||
| b91820de17 | |||
| 6fe2c9c649 | |||
| 70b7b3633e | |||
| 92c60597ec | |||
| 0cfb381cd9 | |||
| ec0fff41e3 | |||
| 288f7f07a2 | |||
| c0b8cd0b3b | |||
| 5370f7f999 | |||
| baa09baf47 | |||
| bb667e388a | |||
| 415d17deed | |||
| acd5c4b8b2 | |||
| 2084d9d91b | |||
| 14d4629519 | |||
| 6a8379520f | |||
| d4c40acb9c | |||
| 89ca20cba5 | |||
| d8e2ce20ce | |||
| d94a8e9093 | |||
| f32425a83c | |||
| fd8ef0bb26 | |||
| 93469e6f97 | |||
| b801c97830 | |||
| da0de660ae |
@@ -53,9 +53,9 @@ jobs:
|
||||
- run: echo "LIBCLANG_PATH=$((gcm clang).source -replace "clang.exe")" >> $env:GITHUB_ENV
|
||||
if: matrix.os == 'windows-latest'
|
||||
|
||||
- name: Check
|
||||
- name: Test
|
||||
run: |
|
||||
cargo check
|
||||
cargo test
|
||||
|
||||
fmt:
|
||||
name: cargo fmt --all -- --check
|
||||
@@ -86,7 +86,7 @@ jobs:
|
||||
- run: rustup component add clippy
|
||||
- name: cargo clippy
|
||||
run: |
|
||||
cargo clippy -- -D warnings
|
||||
cargo clippy --all-targets -- -D warnings
|
||||
|
||||
grcov:
|
||||
name: Code coverage
|
||||
@@ -117,8 +117,8 @@ jobs:
|
||||
cargo test --all --no-fail-fast ${{ matrix.cargo_flags }}
|
||||
env:
|
||||
CARGO_INCREMENTAL: "0"
|
||||
RUSTFLAGS: '-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort -Cdebug-assertions=off'
|
||||
RUSTDOCFLAGS: '-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort -Cdebug-assertions=off'
|
||||
RUSTFLAGS: "-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort -Cdebug-assertions=off"
|
||||
RUSTDOCFLAGS: "-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort -Cdebug-assertions=off"
|
||||
|
||||
- name: Generate coverage data
|
||||
id: grcov
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: [External-testsuites]
|
||||
types: [completed]
|
||||
|
||||
name: Comment Test results on the PR
|
||||
|
||||
permissions: {}
|
||||
jobs:
|
||||
upload-pr-comment:
|
||||
if: ${{ github.event.workflow_run.event == 'pull_request' }}
|
||||
|
||||
name: Upload PR comment
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- name: List Annotations
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
let artifacts = await github.rest.actions.listWorkflowRunArtifacts({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
run_id: ${{ github.event.workflow_run.id }},
|
||||
});
|
||||
|
||||
// List all artifacts
|
||||
let matchArtifact = artifacts.data.artifacts.filter((artifact) => {
|
||||
return artifact.name == "comment"
|
||||
})[0];
|
||||
|
||||
// Download the artifact to github.workspace
|
||||
let download = await github.rest.actions.downloadArtifact({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
artifact_id: matchArtifact.id,
|
||||
archive_format: 'zip',
|
||||
});
|
||||
|
||||
let fs = require('fs');
|
||||
fs.writeFileSync('${{ github.workspace }}/comment.zip', Buffer.from(download.data));
|
||||
|
||||
- run: unzip comment.zip
|
||||
|
||||
- name: Comment on PR
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
let fs = require('fs');
|
||||
let annotations = JSON.parse(fs.readFileSync('./annotations.json', 'utf8'));
|
||||
|
||||
let annotationContent = annotations
|
||||
.data
|
||||
.map(annotation => `${annotation.run}: ${annotation.annotation.message}`)
|
||||
.join('\n');
|
||||
|
||||
// check if no changes
|
||||
let gnuTestReport = annotationContent.includes('Run GNU findutils tests: Gnu tests No changes');
|
||||
let bfsTestReport = annotationContent.includes('Run BFS tests: BFS tests No changes');
|
||||
|
||||
if (gnuTestReport && bfsTestReport) {
|
||||
console.log('No changes');
|
||||
return;
|
||||
}
|
||||
|
||||
// Comment on the PR
|
||||
github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: annotations.pull_request_number,
|
||||
body: 'Commit ${{ github.event.workflow_run.head_sha }} has GNU testsuite comparison:\n```\n' + annotationContent + '\n```\n'
|
||||
});
|
||||
+187
-136
@@ -1,149 +1,200 @@
|
||||
on: [push, pull_request]
|
||||
|
||||
name: External testsuites
|
||||
name: External-testsuites
|
||||
|
||||
jobs:
|
||||
gnu-tests:
|
||||
name: Run GNU findutils tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout findutils
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: findutils
|
||||
- name: Checkout GNU findutils
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: gnu-mirror-unofficial/findutils
|
||||
path: findutils.gnu
|
||||
ref: 5768a03ddfb5e18b1682e339d6cdd24ff721c510
|
||||
submodules: true
|
||||
- name: Install `rust` toolchain
|
||||
run: |
|
||||
## Install `rust` toolchain
|
||||
rustup toolchain install stable --no-self-update -c rustfmt --profile minimal
|
||||
rustup default stable
|
||||
- name: Install dependencies
|
||||
shell: bash
|
||||
run: |
|
||||
# Enable sources & install dependencies
|
||||
sudo find /etc/apt/sources.list* -type f -exec sed -i 'p; s/^deb /deb-src /' '{}' +
|
||||
sudo apt-get update
|
||||
sudo apt-get build-dep findutils
|
||||
- name: Run GNU tests
|
||||
shell: bash
|
||||
run: |
|
||||
cd findutils
|
||||
bash util/build-gnu.sh ||:
|
||||
- name: Extract testing info
|
||||
shell: bash
|
||||
run: |
|
||||
- name: Upload gnu-test-report
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: gnu-test-report
|
||||
path: |
|
||||
findutils.gnu/find/testsuite/*.log
|
||||
findutils.gnu/xargs/testsuite/*.log
|
||||
findutils.gnu/tests/**/*.log
|
||||
- name: Upload gnu-result
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: gnu-result
|
||||
path: gnu-result.json
|
||||
- name: Download the result
|
||||
uses: dawidd6/action-download-artifact@v3
|
||||
with:
|
||||
workflow: compat.yml
|
||||
workflow_conclusion: completed
|
||||
name: gnu-result
|
||||
repo: uutils/findutils
|
||||
branch: main
|
||||
path: dl
|
||||
- name: Download the log
|
||||
uses: dawidd6/action-download-artifact@v3
|
||||
with:
|
||||
workflow: compat.yml
|
||||
workflow_conclusion: completed
|
||||
name: gnu-test-report
|
||||
repo: uutils/findutils
|
||||
branch: main
|
||||
path: dl
|
||||
- name: Compare failing tests against master
|
||||
shell: bash
|
||||
run: |
|
||||
./findutils/util/diff-gnu.sh ./dl ./findutils.gnu
|
||||
- name: Compare against main results
|
||||
shell: bash
|
||||
run: |
|
||||
mv dl/gnu-result.json latest-gnu-result.json
|
||||
python findutils/util/compare_gnu_result.py
|
||||
- name: Checkout findutils
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: findutils
|
||||
- name: Checkout GNU findutils
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: gnu-mirror-unofficial/findutils
|
||||
path: findutils.gnu
|
||||
ref: 5768a03ddfb5e18b1682e339d6cdd24ff721c510
|
||||
submodules: true
|
||||
- name: Install `rust` toolchain
|
||||
run: |
|
||||
## Install `rust` toolchain
|
||||
rustup toolchain install stable --no-self-update -c rustfmt --profile minimal
|
||||
rustup default stable
|
||||
- name: Install dependencies
|
||||
shell: bash
|
||||
run: |
|
||||
# Enable sources & install dependencies
|
||||
sudo find /etc/apt/sources.list* -type f -exec sed -i 'p; s/^deb /deb-src /' '{}' +
|
||||
sudo apt-get update
|
||||
sudo apt-get build-dep findutils
|
||||
- name: Run GNU tests
|
||||
shell: bash
|
||||
run: |
|
||||
cd findutils
|
||||
bash util/build-gnu.sh ||:
|
||||
- name: Extract testing info
|
||||
shell: bash
|
||||
run: |
|
||||
- name: Upload gnu-test-report
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: gnu-test-report
|
||||
path: |
|
||||
findutils.gnu/find/testsuite/*.log
|
||||
findutils.gnu/xargs/testsuite/*.log
|
||||
findutils.gnu/tests/**/*.log
|
||||
- name: Upload gnu-result
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: gnu-result
|
||||
path: gnu-result.json
|
||||
- name: Download the result
|
||||
uses: dawidd6/action-download-artifact@v6
|
||||
with:
|
||||
workflow: compat.yml
|
||||
workflow_conclusion: completed
|
||||
name: gnu-result
|
||||
repo: uutils/findutils
|
||||
branch: main
|
||||
path: dl
|
||||
- name: Download the log
|
||||
uses: dawidd6/action-download-artifact@v6
|
||||
with:
|
||||
workflow: compat.yml
|
||||
workflow_conclusion: completed
|
||||
name: gnu-test-report
|
||||
repo: uutils/findutils
|
||||
branch: main
|
||||
path: dl
|
||||
- name: Compare failing tests against master
|
||||
shell: bash
|
||||
run: |
|
||||
./findutils/util/diff-gnu.sh ./dl ./findutils.gnu
|
||||
- name: Compare against main results
|
||||
shell: bash
|
||||
run: |
|
||||
mv dl/gnu-result.json latest-gnu-result.json
|
||||
python findutils/util/compare_gnu_result.py
|
||||
|
||||
bfs-tests:
|
||||
name: Run BFS tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout findutils
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: findutils
|
||||
- name: Checkout BFS
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: tavianator/bfs
|
||||
path: bfs
|
||||
ref: '3.1.3'
|
||||
- name: Install `rust` toolchain
|
||||
run: |
|
||||
## Install `rust` toolchain
|
||||
rustup toolchain install stable --no-self-update -c rustfmt --profile minimal
|
||||
rustup default stable
|
||||
- name: Install dependencies
|
||||
shell: bash
|
||||
run: |
|
||||
# Enable sources & install dependencies
|
||||
sudo find /etc/apt/sources.list* -type f -exec sed -i 'p; s/^deb /deb-src /' '{}' +
|
||||
sudo apt-get update
|
||||
sudo apt-get build-dep bfs
|
||||
- name: Run BFS tests
|
||||
shell: bash
|
||||
run: |
|
||||
cd findutils
|
||||
bash util/build-bfs.sh ||:
|
||||
- name: Upload bfs-test-report
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: bfs-test-report
|
||||
path: bfs/tests.log
|
||||
- name: Upload bfs-result
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: bfs-result
|
||||
path: bfs-result.json
|
||||
- name: Download the result
|
||||
uses: dawidd6/action-download-artifact@v3
|
||||
with:
|
||||
workflow: compat.yml
|
||||
workflow_conclusion: completed
|
||||
name: bfs-result
|
||||
repo: uutils/findutils
|
||||
branch: main
|
||||
path: dl
|
||||
- name: Download the log
|
||||
uses: dawidd6/action-download-artifact@v3
|
||||
with:
|
||||
workflow: compat.yml
|
||||
workflow_conclusion: completed
|
||||
name: bfs-test-report
|
||||
repo: uutils/findutils
|
||||
branch: main
|
||||
path: dl
|
||||
- name: Compare failing tests against main
|
||||
shell: bash
|
||||
run: |
|
||||
./findutils/util/diff-bfs.sh dl/tests.log bfs/tests.log
|
||||
- name: Compare against main results
|
||||
shell: bash
|
||||
run: |
|
||||
mv dl/bfs-result.json latest-bfs-result.json
|
||||
python findutils/util/compare_bfs_result.py
|
||||
- name: Checkout findutils
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: findutils
|
||||
- name: Checkout BFS
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: tavianator/bfs
|
||||
path: bfs
|
||||
ref: "3.1.3"
|
||||
- name: Install `rust` toolchain
|
||||
run: |
|
||||
## Install `rust` toolchain
|
||||
rustup toolchain install stable --no-self-update -c rustfmt --profile minimal
|
||||
rustup default stable
|
||||
- name: Install dependencies
|
||||
shell: bash
|
||||
run: |
|
||||
# Enable sources & install dependencies
|
||||
sudo find /etc/apt/sources.list* -type f -exec sed -i 'p; s/^deb /deb-src /' '{}' +
|
||||
sudo apt-get update
|
||||
sudo apt-get build-dep bfs
|
||||
- name: Run BFS tests
|
||||
shell: bash
|
||||
run: |
|
||||
cd findutils
|
||||
bash util/build-bfs.sh ||:
|
||||
- name: Upload bfs-test-report
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: bfs-test-report
|
||||
path: bfs/tests.log
|
||||
- name: Upload bfs-result
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: bfs-result
|
||||
path: bfs-result.json
|
||||
- name: Download the result
|
||||
uses: dawidd6/action-download-artifact@v6
|
||||
with:
|
||||
workflow: compat.yml
|
||||
workflow_conclusion: completed
|
||||
name: bfs-result
|
||||
repo: uutils/findutils
|
||||
branch: main
|
||||
path: dl
|
||||
- name: Download the log
|
||||
uses: dawidd6/action-download-artifact@v6
|
||||
with:
|
||||
workflow: compat.yml
|
||||
workflow_conclusion: completed
|
||||
name: bfs-test-report
|
||||
repo: uutils/findutils
|
||||
branch: main
|
||||
path: dl
|
||||
- name: Compare failing tests against main
|
||||
shell: bash
|
||||
run: |
|
||||
./findutils/util/diff-bfs.sh dl/tests.log bfs/tests.log
|
||||
- name: Compare against main results
|
||||
shell: bash
|
||||
run: |
|
||||
mv dl/bfs-result.json latest-bfs-result.json
|
||||
python findutils/util/compare_bfs_result.py
|
||||
|
||||
upload-annotations:
|
||||
name: Upload annotations
|
||||
runs-on: ubuntu-latest
|
||||
needs: [gnu-tests, bfs-tests]
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
steps:
|
||||
- name: List Annotations
|
||||
uses: actions/github-script@v7
|
||||
|
||||
with:
|
||||
script: |
|
||||
let runs = await github.rest.checks.listForRef({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
ref: '${{ github.event.pull_request.head.sha }}'
|
||||
});
|
||||
|
||||
let names = ['Run GNU findutils tests', 'Run BFS tests'];
|
||||
let results = [];
|
||||
runs.data.check_runs.filter(check => names.includes(check.name)).forEach(run => results.push(run));
|
||||
|
||||
let annotations = { data: [], pull_request_number: '${{ github.event.number }}' };
|
||||
for (let result of results) {
|
||||
let run = await github.rest.checks.listAnnotations({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
check_run_id: result.id
|
||||
});
|
||||
|
||||
run.data.forEach(data => {
|
||||
annotations.data.push({
|
||||
run: result.name,
|
||||
annotation: data
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Remove duplicate items.
|
||||
annotations.data = annotations.data.filter((value, index, self) =>
|
||||
self.findIndex(v => v.annotation.message === value.annotation.message) === index);
|
||||
|
||||
let fs = require('fs');
|
||||
fs.writeFileSync('${{ github.workspace }}/annotations.json', JSON.stringify(annotations));
|
||||
|
||||
- name: Upload annotations
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: comment
|
||||
path: annotations.json
|
||||
Generated
+88
-76
@@ -4,9 +4,9 @@ version = 3
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.18"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
|
||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
@@ -109,12 +109,11 @@ checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "1.0.1"
|
||||
version = "1.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fca0852af221f458706eb0725c03e4ed6c46af9ac98e6a689d5e634215d594dd"
|
||||
checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"once_cell",
|
||||
"regex-automata",
|
||||
"serde",
|
||||
]
|
||||
@@ -125,12 +124,6 @@ version = "3.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.72"
|
||||
@@ -150,10 +143,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.37"
|
||||
name = "cfg_aliases"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a0d04d43504c61aa6c7531f1871dd0d418d91130162063b789da00fd7057a5e"
|
||||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||
|
||||
[[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",
|
||||
@@ -165,18 +164,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.4"
|
||||
version = "4.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0"
|
||||
checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.2"
|
||||
version = "4.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4"
|
||||
checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -203,18 +202,6 @@ version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
|
||||
|
||||
[[package]]
|
||||
name = "dashmap"
|
||||
version = "5.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3495912c9c1ccf2e18976439f4443f3fee0fd61f424ff99fde6a66b15ecb448f"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"hashbrown",
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "diff"
|
||||
version = "0.1.13"
|
||||
@@ -280,14 +267,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "findutils"
|
||||
version = "0.5.0"
|
||||
version = "0.6.0"
|
||||
dependencies = [
|
||||
"assert_cmd",
|
||||
"chrono",
|
||||
"clap",
|
||||
"faccess",
|
||||
"filetime",
|
||||
"nix",
|
||||
"nix 0.29.0",
|
||||
"once_cell",
|
||||
"onig",
|
||||
"predicates",
|
||||
@@ -391,12 +378,6 @@ version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.47"
|
||||
@@ -420,17 +401,11 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.153"
|
||||
version = "0.2.155"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
|
||||
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
@@ -459,9 +434,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.4.0"
|
||||
version = "2.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc"
|
||||
checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
@@ -471,7 +446,19 @@ checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"cfg_aliases 0.1.1",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
"cfg-if",
|
||||
"cfg_aliases 0.2.1",
|
||||
"libc",
|
||||
]
|
||||
|
||||
@@ -490,6 +477,12 @@ dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "number_prefix"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.19.0"
|
||||
@@ -637,29 +630,32 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.7.3"
|
||||
version = "1.10.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d"
|
||||
checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.29"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
|
||||
checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
@@ -683,12 +679,27 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scc"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec96560eea317a9cc4e0bb1f6a2c93c09a19b8c4fc5cb3fcc0ec1c094cd783e2"
|
||||
dependencies = [
|
||||
"sdd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||
|
||||
[[package]]
|
||||
name = "sdd"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b84345e4c9bd703274a082fb80caaa99b7612be48dfaa1dd9266577ec412309d"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.147"
|
||||
@@ -697,23 +708,23 @@ checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965"
|
||||
|
||||
[[package]]
|
||||
name = "serial_test"
|
||||
version = "3.0.0"
|
||||
version = "3.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "953ad9342b3aaca7cb43c45c097dd008d4907070394bd0751a0aa8817e5a018d"
|
||||
checksum = "4b4b487fe2acf240a021cf57c6b2b4903b1e78ca0ecd862a71b71d2a51fed77d"
|
||||
dependencies = [
|
||||
"dashmap",
|
||||
"futures",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
"scc",
|
||||
"serial_test_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serial_test_derive"
|
||||
version = "3.0.0"
|
||||
version = "3.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b93fb4adc70021ac1b47f7d45e8cc4169baaa7ea58483bc5b721d19a26202212"
|
||||
checksum = "82fe9db325bcef1fbcde82e078a5cc4efdf787e96b3b9cf45b50b529f2083d67"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -797,15 +808,16 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
||||
|
||||
[[package]]
|
||||
name = "uucore"
|
||||
version = "0.0.25"
|
||||
version = "0.0.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23994a722acb43dbc56877e271c9723f167ae42c4c089f909b2d7dd106c3a9b4"
|
||||
checksum = "7b54aad02cf7e96f5fafabb6b836efa73eef934783b17530095a29ffd4fdc154"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"dunce",
|
||||
"glob",
|
||||
"libc",
|
||||
"nix",
|
||||
"nix 0.28.0",
|
||||
"number_prefix",
|
||||
"once_cell",
|
||||
"os_display",
|
||||
"uucore_procs",
|
||||
@@ -906,9 +918,9 @@ checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838"
|
||||
|
||||
[[package]]
|
||||
name = "wild"
|
||||
version = "2.2.0"
|
||||
version = "2.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10d01931a94d5a115a53f95292f51d316856b68a035618eb831bbba593a30b67"
|
||||
checksum = "a3131afc8c575281e1e80f36ed6a092aa502c08b18ed7524e86fbbb12bb410e1"
|
||||
dependencies = [
|
||||
"glob",
|
||||
]
|
||||
@@ -931,11 +943,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.6"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
|
||||
checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
+13
-7
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "findutils"
|
||||
version = "0.5.0"
|
||||
version = "0.6.0"
|
||||
homepage = "https://github.com/uutils/findutils"
|
||||
repository = "https://github.com/uutils/findutils"
|
||||
edition = "2021"
|
||||
@@ -10,21 +10,22 @@ description = "Rust implementation of GNU findutils"
|
||||
authors = ["uutils developers"]
|
||||
|
||||
[dependencies]
|
||||
chrono = "0.4.37"
|
||||
chrono = "0.4.38"
|
||||
clap = "4.5"
|
||||
faccess = "0.2.4"
|
||||
walkdir = "2.5"
|
||||
regex = "1.7"
|
||||
regex = "1.10"
|
||||
once_cell = "1.19"
|
||||
onig = { version = "6.4", default-features = false }
|
||||
uucore = { version = "0.0.25", features = ["entries", "fs", "fsext", "mode"] }
|
||||
uucore = { version = "0.0.27", features = ["entries", "fs", "fsext", "mode"] }
|
||||
nix = { version = "0.29", features = ["fs", "user"] }
|
||||
|
||||
[dev-dependencies]
|
||||
assert_cmd = "2"
|
||||
filetime = "0.2"
|
||||
nix = { version = "0.28", features = ["fs"] }
|
||||
nix = { version = "0.29", features = ["fs"] }
|
||||
predicates = "3"
|
||||
serial_test = "3.0"
|
||||
serial_test = "3.1"
|
||||
tempfile = "3"
|
||||
pretty_assertions = "1.4.0"
|
||||
|
||||
@@ -54,6 +55,11 @@ 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"]
|
||||
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"
|
||||
|
||||
@@ -5,6 +5,11 @@
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
fn main() {
|
||||
// Ignores the SIGPIPE signal.
|
||||
// This is to solve the problem that when find is used with a pipe character,
|
||||
// the downstream software of the standard output stream closes the pipe and triggers a panic.
|
||||
uucore::panic::mute_sigpipe_panic();
|
||||
|
||||
let args = std::env::args().collect::<Vec<String>>();
|
||||
let strs: Vec<&str> = args.iter().map(std::convert::AsRef::as_ref).collect();
|
||||
let deps = findutils::find::StandardDependencies::new();
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
// This file is part of the uutils findutils package.
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
use std::path::Path;
|
||||
use std::{
|
||||
error::Error,
|
||||
io::{stderr, Write},
|
||||
};
|
||||
|
||||
use super::Matcher;
|
||||
|
||||
/// Get the filesystem type of a file.
|
||||
/// 1. get the metadata of the file
|
||||
/// 2. get the device ID of the metadata
|
||||
/// 3. search the filesystem list
|
||||
///
|
||||
/// Returns an empty string when no file system list matches.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an error if the metadata could not be read.
|
||||
/// Returns an error if the filesystem list could not be read.
|
||||
///
|
||||
/// This is only supported on Unix.
|
||||
#[cfg(unix)]
|
||||
pub fn get_file_system_type(path: &Path) -> Result<String, Box<dyn Error>> {
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
|
||||
let metadata = match path.metadata() {
|
||||
Ok(metadata) => metadata,
|
||||
Err(err) => Err(err)?,
|
||||
};
|
||||
let dev_id = metadata.dev().to_string();
|
||||
let fs_list = match uucore::fsext::read_fs_list() {
|
||||
Ok(fs_list) => fs_list,
|
||||
Err(err) => Err(err)?,
|
||||
};
|
||||
let result = fs_list
|
||||
.into_iter()
|
||||
.find(|fs| fs.dev_id == dev_id)
|
||||
.map_or_else(String::new, |fs| fs.fs_type);
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// This matcher handles the -fstype argument.
|
||||
/// It matches the filesystem type of the file.
|
||||
///
|
||||
/// This is only supported on Unix.
|
||||
pub struct FileSystemMatcher {
|
||||
fs_text: String,
|
||||
}
|
||||
|
||||
impl FileSystemMatcher {
|
||||
pub fn new(fs_text: String) -> Self {
|
||||
Self { fs_text }
|
||||
}
|
||||
}
|
||||
|
||||
impl Matcher for FileSystemMatcher {
|
||||
fn matches(&self, file_info: &walkdir::DirEntry, _: &mut super::MatcherIO) -> bool {
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
false
|
||||
}
|
||||
#[cfg(unix)]
|
||||
{
|
||||
match get_file_system_type(file_info.path()) {
|
||||
Ok(result) => result == self.fs_text,
|
||||
Err(_) => {
|
||||
writeln!(
|
||||
&mut stderr(),
|
||||
"Error getting filesystem type for {}",
|
||||
file_info.path().to_string_lossy()
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_fs_matcher() {
|
||||
use crate::find::{
|
||||
matchers::{fs::get_file_system_type, tests::get_dir_entry_for, Matcher},
|
||||
tests::FakeDependencies,
|
||||
};
|
||||
use std::fs::File;
|
||||
use tempfile::Builder;
|
||||
|
||||
let deps = FakeDependencies::new();
|
||||
let mut matcher_io = deps.new_matcher_io();
|
||||
|
||||
// create temp file and get its fs type
|
||||
// We pass this file and the corresponding file system type into the Matcher for comparison.
|
||||
let temp_dir = Builder::new().prefix("fs_matcher").tempdir().unwrap();
|
||||
let foo_path = temp_dir.path().join("foo");
|
||||
let _ = File::create(foo_path).expect("create temp file");
|
||||
let file_info = get_dir_entry_for(&temp_dir.path().to_string_lossy(), "foo");
|
||||
|
||||
let target_fs_type = get_file_system_type(file_info.path()).unwrap();
|
||||
|
||||
// should match fs type
|
||||
let matcher = super::FileSystemMatcher::new(target_fs_type.clone());
|
||||
assert!(
|
||||
matcher.matches(&file_info, &mut matcher_io),
|
||||
"{} should match {}",
|
||||
file_info.path().to_string_lossy(),
|
||||
target_fs_type
|
||||
);
|
||||
|
||||
// should not match fs type
|
||||
let matcher = super::FileSystemMatcher::new(target_fs_type.clone() + "foo");
|
||||
assert!(
|
||||
!matcher.matches(&file_info, &mut matcher_io),
|
||||
"{} should not match {}",
|
||||
file_info.path().to_string_lossy(),
|
||||
target_fs_type
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
// This file is part of the uutils findutils package.
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
use super::Matcher;
|
||||
|
||||
#[cfg(unix)]
|
||||
use nix::unistd::Group;
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
|
||||
pub struct GroupMatcher {
|
||||
gid: Option<u32>,
|
||||
}
|
||||
|
||||
impl GroupMatcher {
|
||||
#[cfg(unix)]
|
||||
pub fn from_group_name(group: &str) -> GroupMatcher {
|
||||
// get gid from group name
|
||||
let Ok(group) = Group::from_name(group) else {
|
||||
return GroupMatcher { gid: None };
|
||||
};
|
||||
|
||||
let Some(group) = group else {
|
||||
// This if branch is to determine whether a certain group exists in the system.
|
||||
// If a certain group does not exist in the system,
|
||||
// the result will need to be returned according to
|
||||
// the flag bit of whether to invert the result.
|
||||
return GroupMatcher { gid: None };
|
||||
};
|
||||
|
||||
GroupMatcher {
|
||||
gid: Some(group.gid.as_raw()),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub fn from_gid(gid: u32) -> GroupMatcher {
|
||||
GroupMatcher { gid: Some(gid) }
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn from_group_name(_group: &str) -> GroupMatcher {
|
||||
GroupMatcher { gid: None }
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn from_gid(_gid: u32) -> GroupMatcher {
|
||||
GroupMatcher { gid: None }
|
||||
}
|
||||
|
||||
pub fn gid(&self) -> &Option<u32> {
|
||||
&self.gid
|
||||
}
|
||||
}
|
||||
|
||||
impl Matcher for GroupMatcher {
|
||||
#[cfg(unix)]
|
||||
fn matches(&self, file_info: &walkdir::DirEntry, _: &mut super::MatcherIO) -> bool {
|
||||
let Ok(metadata) = file_info.path().metadata() else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let file_gid = metadata.gid();
|
||||
|
||||
// When matching the -group parameter in find/matcher/mod.rs,
|
||||
// it has been judged that the group does not exist and an error is returned.
|
||||
// So use unwarp() directly here.
|
||||
self.gid.unwrap() == file_gid
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn matches(&self, _file_info: &walkdir::DirEntry, _: &mut super::MatcherIO) -> bool {
|
||||
// The user group acquisition function for Windows systems is not implemented in MetadataExt,
|
||||
// so it is somewhat difficult to implement it. :(
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NoGroupMatcher {}
|
||||
|
||||
impl Matcher for NoGroupMatcher {
|
||||
#[cfg(unix)]
|
||||
fn matches(&self, file_info: &walkdir::DirEntry, _: &mut super::MatcherIO) -> bool {
|
||||
use nix::unistd::Gid;
|
||||
|
||||
if file_info.path().is_symlink() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let Ok(metadata) = file_info.path().metadata() else {
|
||||
return true;
|
||||
};
|
||||
|
||||
let Ok(gid) = Group::from_gid(Gid::from_raw(metadata.gid())) else {
|
||||
return true;
|
||||
};
|
||||
|
||||
let Some(_group) = gid else {
|
||||
return true;
|
||||
};
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn matches(&self, _file_info: &walkdir::DirEntry, _: &mut super::MatcherIO) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_group_matcher() {
|
||||
use crate::find::matchers::{group::GroupMatcher, tests::get_dir_entry_for, Matcher};
|
||||
use crate::find::tests::FakeDependencies;
|
||||
use chrono::Local;
|
||||
use nix::unistd::{Gid, Group};
|
||||
use std::fs::File;
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
use tempfile::Builder;
|
||||
|
||||
let deps = FakeDependencies::new();
|
||||
let mut matcher_io = deps.new_matcher_io();
|
||||
|
||||
let temp_dir = Builder::new().prefix("group_matcher").tempdir().unwrap();
|
||||
let foo_path = temp_dir.path().join("foo");
|
||||
let _ = File::create(foo_path).expect("create temp file");
|
||||
let file_info = get_dir_entry_for(&temp_dir.path().to_string_lossy(), "foo");
|
||||
let file_gid = file_info.path().metadata().unwrap().gid();
|
||||
let file_group = Group::from_gid(Gid::from_raw(file_gid))
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.name;
|
||||
|
||||
let matcher = super::GroupMatcher::from_group_name(file_group.as_str());
|
||||
assert!(
|
||||
matcher.matches(&file_info, &mut matcher_io),
|
||||
"group should match"
|
||||
);
|
||||
|
||||
// Testing a non-existent group name
|
||||
let time_string = Local::now().format("%Y%m%d%H%M%S").to_string();
|
||||
let matcher = GroupMatcher::from_group_name(time_string.as_str());
|
||||
assert!(
|
||||
matcher.gid().is_none(),
|
||||
"group name {} should not exist",
|
||||
time_string
|
||||
);
|
||||
|
||||
// Testing group id
|
||||
let matcher = GroupMatcher::from_gid(file_gid);
|
||||
assert!(
|
||||
matcher.gid().is_some(),
|
||||
"group id {} should exist",
|
||||
file_gid
|
||||
);
|
||||
assert!(
|
||||
matcher.matches(&file_info, &mut matcher_io),
|
||||
"group id should match"
|
||||
);
|
||||
}
|
||||
}
|
||||
+242
-39
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,59 @@
|
||||
// This file is part of the uutils findutils package.
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
use super::Matcher;
|
||||
use std::error::Error;
|
||||
use std::path::Path;
|
||||
use uucore::fs::FileInformation;
|
||||
|
||||
pub struct SameFileMatcher {
|
||||
info: FileInformation,
|
||||
}
|
||||
|
||||
impl SameFileMatcher {
|
||||
pub fn new(path: impl AsRef<Path>) -> Result<Self, Box<dyn Error>> {
|
||||
let info = FileInformation::from_path(path, false)?;
|
||||
Ok(Self { info })
|
||||
}
|
||||
}
|
||||
|
||||
impl Matcher for SameFileMatcher {
|
||||
fn matches(&self, file_info: &walkdir::DirEntry, _matcher_io: &mut super::MatcherIO) -> bool {
|
||||
if let Ok(info) = FileInformation::from_path(file_info.path(), false) {
|
||||
info == self.info
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::fs;
|
||||
|
||||
#[test]
|
||||
fn test_samefile() {
|
||||
use crate::find::{
|
||||
matchers::{samefile::SameFileMatcher, tests::get_dir_entry_for, Matcher},
|
||||
tests::FakeDependencies,
|
||||
};
|
||||
|
||||
// remove file if hard link file exist.
|
||||
// But you can't delete a file that doesn't exist,
|
||||
// so ignore the error returned here.
|
||||
let _ = fs::remove_file("test_data/links/hard_link");
|
||||
|
||||
assert!(SameFileMatcher::new("test_data/links/hard_link").is_err());
|
||||
|
||||
fs::hard_link("test_data/links/abbbc", "test_data/links/hard_link").unwrap();
|
||||
|
||||
let file = get_dir_entry_for("test_data/links", "abbbc");
|
||||
let hard_link_file = get_dir_entry_for("test_data/links", "hard_link");
|
||||
let matcher = SameFileMatcher::new(file.into_path()).unwrap();
|
||||
|
||||
let deps = FakeDependencies::new();
|
||||
assert!(matcher.matches(&hard_link_file, &mut deps.new_matcher_io()));
|
||||
}
|
||||
}
|
||||
+10
-38
@@ -4,10 +4,8 @@
|
||||
// license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
|
||||
use std::error::Error;
|
||||
use walkdir::DirEntry;
|
||||
|
||||
use super::{ComparableValue, Matcher, MatcherIO};
|
||||
@@ -18,32 +16,18 @@ pub struct InodeMatcher {
|
||||
}
|
||||
|
||||
impl InodeMatcher {
|
||||
#[cfg(unix)]
|
||||
pub fn new(ino: ComparableValue) -> Result<Self, Box<dyn Error>> {
|
||||
Ok(Self { ino })
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
pub fn new(_ino: ComparableValue) -> Result<Self, Box<dyn Error>> {
|
||||
Err(From::from(
|
||||
"Inode numbers are not available on this platform",
|
||||
))
|
||||
pub fn new(ino: ComparableValue) -> Self {
|
||||
Self { ino }
|
||||
}
|
||||
}
|
||||
|
||||
impl Matcher for InodeMatcher {
|
||||
#[cfg(unix)]
|
||||
fn matches(&self, file_info: &DirEntry, _: &mut MatcherIO) -> bool {
|
||||
match file_info.metadata() {
|
||||
Ok(metadata) => self.ino.matches(metadata.ino()),
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
fn matches(&self, _: &DirEntry, _: &mut MatcherIO) -> bool {
|
||||
unreachable!("Inode numbers are not available on this platform")
|
||||
}
|
||||
}
|
||||
|
||||
/// Link count matcher.
|
||||
@@ -52,30 +36,18 @@ pub struct LinksMatcher {
|
||||
}
|
||||
|
||||
impl LinksMatcher {
|
||||
#[cfg(unix)]
|
||||
pub fn new(nlink: ComparableValue) -> Result<Self, Box<dyn Error>> {
|
||||
Ok(Self { nlink })
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
pub fn new(_nlink: ComparableValue) -> Result<Self, Box<dyn Error>> {
|
||||
Err(From::from("Link counts are not available on this platform"))
|
||||
pub fn new(nlink: ComparableValue) -> Self {
|
||||
Self { nlink }
|
||||
}
|
||||
}
|
||||
|
||||
impl Matcher for LinksMatcher {
|
||||
#[cfg(unix)]
|
||||
fn matches(&self, file_info: &DirEntry, _: &mut MatcherIO) -> bool {
|
||||
match file_info.metadata() {
|
||||
Ok(metadata) => self.nlink.matches(metadata.nlink()),
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
fn matches(&self, _: &DirEntry, _: &mut MatcherIO) -> bool {
|
||||
unreachable!("Link counts are not available on this platform")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -92,19 +64,19 @@ mod tests {
|
||||
let metadata = file_info.metadata().unwrap();
|
||||
let deps = FakeDependencies::new();
|
||||
|
||||
let matcher = InodeMatcher::new(ComparableValue::EqualTo(metadata.ino())).unwrap();
|
||||
let matcher = InodeMatcher::new(ComparableValue::EqualTo(metadata.ino()));
|
||||
assert!(
|
||||
matcher.matches(&file_info, &mut deps.new_matcher_io()),
|
||||
"inode number should match"
|
||||
);
|
||||
|
||||
let matcher = InodeMatcher::new(ComparableValue::LessThan(metadata.ino())).unwrap();
|
||||
let matcher = InodeMatcher::new(ComparableValue::LessThan(metadata.ino()));
|
||||
assert!(
|
||||
!matcher.matches(&file_info, &mut deps.new_matcher_io()),
|
||||
"inode number should not match"
|
||||
);
|
||||
|
||||
let matcher = InodeMatcher::new(ComparableValue::MoreThan(metadata.ino())).unwrap();
|
||||
let matcher = InodeMatcher::new(ComparableValue::MoreThan(metadata.ino()));
|
||||
assert!(
|
||||
!matcher.matches(&file_info, &mut deps.new_matcher_io()),
|
||||
"inode number should not match"
|
||||
@@ -116,19 +88,19 @@ mod tests {
|
||||
let file_info = get_dir_entry_for("test_data/simple", "abbbc");
|
||||
let deps = FakeDependencies::new();
|
||||
|
||||
let matcher = LinksMatcher::new(ComparableValue::EqualTo(1)).unwrap();
|
||||
let matcher = LinksMatcher::new(ComparableValue::EqualTo(1));
|
||||
assert!(
|
||||
matcher.matches(&file_info, &mut deps.new_matcher_io()),
|
||||
"link count should match"
|
||||
);
|
||||
|
||||
let matcher = LinksMatcher::new(ComparableValue::LessThan(1)).unwrap();
|
||||
let matcher = LinksMatcher::new(ComparableValue::LessThan(1));
|
||||
assert!(
|
||||
!matcher.matches(&file_info, &mut deps.new_matcher_io()),
|
||||
"link count should not match"
|
||||
);
|
||||
|
||||
let matcher = LinksMatcher::new(ComparableValue::MoreThan(1)).unwrap();
|
||||
let matcher = LinksMatcher::new(ComparableValue::MoreThan(1));
|
||||
assert!(
|
||||
!matcher.matches(&file_info, &mut deps.new_matcher_io()),
|
||||
"link count should not match"
|
||||
|
||||
+184
-46
@@ -171,14 +171,17 @@ impl NewerTimeMatcher {
|
||||
|
||||
fn matches_impl(&self, file_info: &DirEntry) -> Result<bool, Box<dyn Error>> {
|
||||
let this_time = self.newer_time_type.get_file_time(file_info.metadata()?)?;
|
||||
let timestamp = match this_time.duration_since(UNIX_EPOCH) {
|
||||
Ok(duration) => duration,
|
||||
Err(e) => e.duration(),
|
||||
};
|
||||
let timestamp = this_time
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap_or_else(|e| e.duration());
|
||||
|
||||
// timestamp.as_millis() return u128 but time is i64
|
||||
// This may leave memory implications. :(
|
||||
Ok(self.time <= timestamp.as_millis().try_into().unwrap())
|
||||
Ok(self.time
|
||||
<= timestamp
|
||||
.as_millis()
|
||||
.try_into()
|
||||
.expect("timestamp memory implications"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,6 +280,54 @@ impl FileTimeMatcher {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FileAgeRangeMatcher {
|
||||
minutes: ComparableValue,
|
||||
file_time_type: FileTimeType,
|
||||
}
|
||||
|
||||
impl Matcher for FileAgeRangeMatcher {
|
||||
fn matches(&self, file_info: &DirEntry, matcher_io: &mut MatcherIO) -> bool {
|
||||
match self.matches_impl(file_info, matcher_io.now()) {
|
||||
Err(e) => {
|
||||
writeln!(
|
||||
&mut stderr(),
|
||||
"Error getting {:?} time for {}: {}",
|
||||
self.file_time_type,
|
||||
file_info.path().to_string_lossy(),
|
||||
e
|
||||
)
|
||||
.unwrap();
|
||||
false
|
||||
}
|
||||
Ok(t) => t,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FileAgeRangeMatcher {
|
||||
fn matches_impl(&self, file_info: &DirEntry, now: SystemTime) -> Result<bool, Box<dyn Error>> {
|
||||
let this_time = self.file_time_type.get_file_time(file_info.metadata()?)?;
|
||||
let mut is_negative = false;
|
||||
let age = match now.duration_since(this_time) {
|
||||
Ok(duration) => duration,
|
||||
Err(e) => {
|
||||
is_negative = true;
|
||||
e.duration()
|
||||
}
|
||||
};
|
||||
let age_in_seconds: i64 = age.as_secs() as i64 * if is_negative { -1 } else { 1 };
|
||||
let age_in_minutes = age_in_seconds / 60 + if is_negative { -1 } else { 0 };
|
||||
Ok(self.minutes.imatches(age_in_minutes))
|
||||
}
|
||||
|
||||
pub fn new(file_time_type: FileTimeType, minutes: ComparableValue) -> Self {
|
||||
Self {
|
||||
minutes,
|
||||
file_time_type,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::fs;
|
||||
@@ -340,7 +391,7 @@ mod tests {
|
||||
|
||||
// set "now" to 2 days after the file was modified.
|
||||
let mut deps = FakeDependencies::new();
|
||||
deps.set_time(files_mtime + Duration::new(2 * super::SECONDS_PER_DAY as u64, 0));
|
||||
deps.set_time(files_mtime + Duration::new(2 * SECONDS_PER_DAY as u64, 0));
|
||||
assert!(
|
||||
!exactly_one_day_matcher.matches(&file, &mut deps.new_matcher_io()),
|
||||
"2 day old file shouldn't match exactly 1 day old"
|
||||
@@ -359,7 +410,7 @@ mod tests {
|
||||
);
|
||||
|
||||
// set "now" to 1 day after the file was modified.
|
||||
deps.set_time(files_mtime + Duration::new((3 * super::SECONDS_PER_DAY / 2) as u64, 0));
|
||||
deps.set_time(files_mtime + Duration::new((3 * SECONDS_PER_DAY / 2) as u64, 0));
|
||||
assert!(
|
||||
exactly_one_day_matcher.matches(&file, &mut deps.new_matcher_io()),
|
||||
"1 day old file should match exactly 1 day old"
|
||||
@@ -506,16 +557,12 @@ mod tests {
|
||||
#[test]
|
||||
fn newer_option_matcher() {
|
||||
#[cfg(target_os = "linux")]
|
||||
let x_options = ["a", "c", "m"];
|
||||
let options = ["a", "c", "m"];
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
let x_options = ["a", "B", "c", "m"];
|
||||
#[cfg(target_os = "linux")]
|
||||
let y_options = ["a", "c", "m"];
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
let y_options = ["a", "B", "c", "m"];
|
||||
let options = ["a", "B", "c", "m"];
|
||||
|
||||
for x_option in &x_options {
|
||||
for y_option in &y_options {
|
||||
for x_option in options {
|
||||
for y_option in options {
|
||||
let temp_dir = Builder::new().prefix("example").tempdir().unwrap();
|
||||
let temp_dir_path = temp_dir.path().to_string_lossy();
|
||||
let new_file_name = "newFile";
|
||||
@@ -540,18 +587,27 @@ mod tests {
|
||||
|
||||
// After the file is deleted, DirEntry will point to an empty file location,
|
||||
// thus causing the Matcher to generate an IO error after matching.
|
||||
//
|
||||
// Note: This test is nondeterministic on Windows,
|
||||
// because fs::remove_file may not actually remove the file from
|
||||
// the file system even if it returns Ok.
|
||||
// Therefore, this test will only be performed on Linux/Unix.
|
||||
let _ = fs::remove_file(&*new_file.path().to_string_lossy());
|
||||
let matcher = NewerOptionMatcher::new(
|
||||
x_option.to_string(),
|
||||
y_option.to_string(),
|
||||
&old_file.path().to_string_lossy(),
|
||||
);
|
||||
assert!(
|
||||
!matcher
|
||||
.unwrap()
|
||||
.matches(&new_file, &mut deps.new_matcher_io()),
|
||||
"The correct situation is that the file reading here cannot be successful."
|
||||
);
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
let matcher = NewerOptionMatcher::new(
|
||||
x_option.to_string(),
|
||||
y_option.to_string(),
|
||||
&old_file.path().to_string_lossy(),
|
||||
);
|
||||
assert!(
|
||||
!matcher
|
||||
.unwrap()
|
||||
.matches(&new_file, &mut deps.new_matcher_io()),
|
||||
"The correct situation is that the file reading here cannot be successful."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -600,15 +656,14 @@ mod tests {
|
||||
|
||||
// modified time test
|
||||
let modified_matcher = NewerTimeMatcher::new(NewerOptionType::Modified, time);
|
||||
let mut buffer = [0; 10];
|
||||
{
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open(&foo_path)
|
||||
.expect("open temp file");
|
||||
let _ = file.write(&mut buffer);
|
||||
}
|
||||
let buffer = [0; 10];
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open(&foo_path)
|
||||
.expect("open temp file");
|
||||
let _ = file.write(&buffer);
|
||||
|
||||
assert!(
|
||||
modified_matcher.matches(&file_info, &mut deps.new_matcher_io()),
|
||||
"file modified time should after 'time'"
|
||||
@@ -631,20 +686,103 @@ mod tests {
|
||||
|
||||
// After the file is deleted, DirEntry will point to an empty file location,
|
||||
// thus causing the Matcher to generate an IO error after matching.
|
||||
//
|
||||
// Note: This test is nondeterministic on Windows,
|
||||
// because fs::remove_file may not actually remove the file from
|
||||
// the file system even if it returns Ok.
|
||||
// Therefore, this test will only be performed on Linux/Unix.
|
||||
let _ = fs::remove_file(&*file_info.path().to_string_lossy());
|
||||
|
||||
let matchers = [
|
||||
&created_matcher,
|
||||
&accessed_matcher,
|
||||
&modified_matcher,
|
||||
&inode_changed_matcher,
|
||||
];
|
||||
#[cfg(unix)]
|
||||
{
|
||||
let matchers = [
|
||||
&created_matcher,
|
||||
&accessed_matcher,
|
||||
&modified_matcher,
|
||||
&inode_changed_matcher,
|
||||
];
|
||||
|
||||
for matcher in &matchers {
|
||||
assert!(
|
||||
!matcher.matches(&file_info, &mut deps.new_matcher_io()),
|
||||
"The correct situation is that the file reading here cannot be successful."
|
||||
);
|
||||
for matcher in &matchers {
|
||||
assert!(
|
||||
!matcher.matches(&file_info, &mut deps.new_matcher_io()),
|
||||
"The correct situation is that the file reading here cannot be successful."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn file_age_range_matcher() {
|
||||
let temp_dir = Builder::new().prefix("example").tempdir().unwrap();
|
||||
let temp_dir_path = temp_dir.path().to_string_lossy();
|
||||
let new_file_name = "newFile";
|
||||
// this has just been created, so should be newer
|
||||
File::create(temp_dir.path().join(new_file_name)).expect("create temp file");
|
||||
let new_file = get_dir_entry_for(&temp_dir_path, new_file_name);
|
||||
|
||||
// more test
|
||||
// mocks:
|
||||
// - find test_data/simple -amin +1
|
||||
// - find test_data/simple -cmin +1
|
||||
// - find test_data/simple -mmin +1
|
||||
// Means to find files accessed / modified more than 1 minute ago.
|
||||
[
|
||||
FileTimeType::Accessed,
|
||||
FileTimeType::Created,
|
||||
FileTimeType::Modified,
|
||||
]
|
||||
.iter()
|
||||
.for_each(|time_type| {
|
||||
let more_matcher = FileAgeRangeMatcher::new(*time_type, ComparableValue::MoreThan(1));
|
||||
assert!(
|
||||
!more_matcher.matches(&new_file, &mut FakeDependencies::new().new_matcher_io()),
|
||||
"{}",
|
||||
format!(
|
||||
"more minutes old file should match more than 1 minute old in {} test.",
|
||||
match *time_type {
|
||||
FileTimeType::Accessed => "accessed",
|
||||
FileTimeType::Created => "created",
|
||||
FileTimeType::Modified => "modified",
|
||||
}
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
// less test
|
||||
// mocks:
|
||||
// - find test_data/simple -amin -1
|
||||
// - find test_data/simple -cmin -1
|
||||
// - find test_data/simple -mmin -1
|
||||
// Means to find files accessed / modified less than 1 minute ago.
|
||||
[
|
||||
FileTimeType::Accessed,
|
||||
FileTimeType::Created,
|
||||
FileTimeType::Modified,
|
||||
]
|
||||
.iter()
|
||||
.for_each(|time_type| {
|
||||
let less_matcher = FileAgeRangeMatcher::new(*time_type, ComparableValue::LessThan(1));
|
||||
assert!(
|
||||
less_matcher.matches(&new_file, &mut FakeDependencies::new().new_matcher_io()),
|
||||
"{}",
|
||||
format!(
|
||||
"less minutes old file should not match less than 1 minute old in {} test.",
|
||||
match *time_type {
|
||||
FileTimeType::Accessed => "accessed",
|
||||
FileTimeType::Created => "created",
|
||||
FileTimeType::Modified => "modified",
|
||||
}
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
// catch file error
|
||||
let _ = fs::remove_file(&*new_file.path().to_string_lossy());
|
||||
let matcher =
|
||||
FileAgeRangeMatcher::new(FileTimeType::Modified, ComparableValue::MoreThan(1));
|
||||
assert!(
|
||||
!matcher.matches(&new_file, &mut FakeDependencies::new().new_matcher_io()),
|
||||
"The correct situation is that the file reading here cannot be successful."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
// This file is part of the uutils findutils package.
|
||||
//
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
use super::Matcher;
|
||||
|
||||
#[cfg(unix)]
|
||||
use nix::unistd::User;
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
|
||||
pub struct UserMatcher {
|
||||
uid: Option<u32>,
|
||||
}
|
||||
|
||||
impl UserMatcher {
|
||||
#[cfg(unix)]
|
||||
pub fn from_user_name(user: &str) -> UserMatcher {
|
||||
// get uid from user name
|
||||
let Ok(user) = User::from_name(user) else {
|
||||
return UserMatcher { uid: None };
|
||||
};
|
||||
|
||||
let Some(user) = user else {
|
||||
// This if branch is to determine whether a certain user exists in the system.
|
||||
// If a certain user does not exist in the system,
|
||||
// the result will need to be returned according to
|
||||
// the flag bit of whether to invert the result.
|
||||
return UserMatcher { uid: None };
|
||||
};
|
||||
|
||||
UserMatcher {
|
||||
uid: Some(user.uid.as_raw()),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub fn from_uid(uid: u32) -> UserMatcher {
|
||||
UserMatcher { uid: Some(uid) }
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn from_user_name(_user: &str) -> UserMatcher {
|
||||
UserMatcher { uid: None }
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn from_uid(_uid: u32) -> UserMatcher {
|
||||
UserMatcher { uid: None }
|
||||
}
|
||||
|
||||
pub fn uid(&self) -> &Option<u32> {
|
||||
&self.uid
|
||||
}
|
||||
}
|
||||
|
||||
impl Matcher for UserMatcher {
|
||||
#[cfg(unix)]
|
||||
fn matches(&self, file_info: &walkdir::DirEntry, _: &mut super::MatcherIO) -> bool {
|
||||
let Ok(metadata) = file_info.path().metadata() else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let file_uid = metadata.uid();
|
||||
|
||||
// When matching the -user parameter in find/matcher/mod.rs,
|
||||
// it has been judged that the user does not exist and an error is returned.
|
||||
// So use unwarp() directly here.
|
||||
self.uid.unwrap() == file_uid
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn matches(&self, _file_info: &walkdir::DirEntry, _: &mut super::MatcherIO) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NoUserMatcher {}
|
||||
|
||||
impl Matcher for NoUserMatcher {
|
||||
#[cfg(unix)]
|
||||
fn matches(&self, file_info: &walkdir::DirEntry, _: &mut super::MatcherIO) -> bool {
|
||||
use nix::unistd::Uid;
|
||||
|
||||
if file_info.path().is_symlink() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let Ok(metadata) = file_info.path().metadata() else {
|
||||
return true;
|
||||
};
|
||||
|
||||
let Ok(uid) = User::from_uid(Uid::from_raw(metadata.uid())) else {
|
||||
return true;
|
||||
};
|
||||
|
||||
let Some(_user) = uid else {
|
||||
return true;
|
||||
};
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn matches(&self, _file_info: &walkdir::DirEntry, _: &mut super::MatcherIO) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_user_matcher() {
|
||||
use crate::find::matchers::{tests::get_dir_entry_for, user::UserMatcher, Matcher};
|
||||
use crate::find::tests::FakeDependencies;
|
||||
use chrono::Local;
|
||||
use nix::unistd::{Uid, User};
|
||||
use std::fs::File;
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
use tempfile::Builder;
|
||||
|
||||
let deps = FakeDependencies::new();
|
||||
let mut matcher_io = deps.new_matcher_io();
|
||||
|
||||
let temp_dir = Builder::new().prefix("user_matcher").tempdir().unwrap();
|
||||
let foo_path = temp_dir.path().join("foo");
|
||||
let _ = File::create(foo_path).expect("create temp file");
|
||||
let file_info = get_dir_entry_for(&temp_dir.path().to_string_lossy(), "foo");
|
||||
let file_uid = file_info.path().metadata().unwrap().uid();
|
||||
let file_user = User::from_uid(Uid::from_raw(file_uid))
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.name;
|
||||
|
||||
let matcher = UserMatcher::from_user_name(file_user.as_str());
|
||||
assert!(
|
||||
matcher.matches(&file_info, &mut matcher_io),
|
||||
"user should be the same"
|
||||
);
|
||||
|
||||
// Testing a non-existent group name
|
||||
let time_string = Local::now().format("%Y%m%d%H%M%S").to_string();
|
||||
let matcher = UserMatcher::from_user_name(time_string.as_str());
|
||||
assert!(
|
||||
matcher.uid().is_none(),
|
||||
"user {} should not be the same",
|
||||
time_string
|
||||
);
|
||||
|
||||
// Testing user id
|
||||
let matcher = UserMatcher::from_uid(file_uid);
|
||||
assert!(matcher.uid().is_some(), "user id {} should exist", file_uid);
|
||||
assert!(
|
||||
matcher.matches(&file_info, &mut matcher_io),
|
||||
"user id should match"
|
||||
);
|
||||
}
|
||||
}
|
||||
+261
-28
@@ -29,7 +29,7 @@ impl Default for Config {
|
||||
same_file_system: false,
|
||||
depth_first: false,
|
||||
min_depth: 0,
|
||||
max_depth: usize::max_value(),
|
||||
max_depth: usize::MAX,
|
||||
sorted_output: false,
|
||||
help_requested: false,
|
||||
version_requested: false,
|
||||
@@ -150,7 +150,10 @@ fn process_dir<'a>(
|
||||
let mut it = walkdir.into_iter();
|
||||
while let Some(result) = it.next() {
|
||||
match result {
|
||||
Err(err) => writeln!(&mut stderr(), "Error: {dir}: {err}").unwrap(),
|
||||
Err(err) => {
|
||||
uucore::error::set_exit_code(1);
|
||||
writeln!(&mut stderr(), "Error: {dir}: {err}").unwrap()
|
||||
}
|
||||
Ok(entry) => {
|
||||
let mut matcher_io = matchers::MatcherIO::new(deps);
|
||||
if matcher.matches(&entry, &mut matcher_io) {
|
||||
@@ -254,7 +257,7 @@ fn print_version() {
|
||||
/// the name of the executable.
|
||||
pub fn find_main<'a>(args: &[&str], deps: &'a dyn Dependencies<'a>) -> i32 {
|
||||
match do_find(&args[1..], deps) {
|
||||
Ok(_) => 0,
|
||||
Ok(_) => uucore::error::get_exit_code(),
|
||||
Err(e) => {
|
||||
writeln!(&mut stderr(), "Error: {e}").unwrap();
|
||||
1
|
||||
@@ -274,7 +277,7 @@ mod tests {
|
||||
use std::os::unix::fs::symlink;
|
||||
|
||||
#[cfg(windows)]
|
||||
use std::os::windows::fs::{symlink_dir, symlink_file};
|
||||
use std::os::windows::fs::symlink_file;
|
||||
|
||||
use crate::find::matchers::MatcherIO;
|
||||
|
||||
@@ -733,6 +736,31 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
// Because the time when files exist locally is different
|
||||
// from the time when Github Actions pulls them,
|
||||
// it is difficult to write tests that limit a certain time period.
|
||||
//
|
||||
// For example, a Github Action may pull files from a new git commit within a few minutes,
|
||||
// causing the file time to be refreshed to the pull time.
|
||||
// and The files on the local branch may be several days old.
|
||||
//
|
||||
// So this test may not be too accurate and can only ensure that
|
||||
// the function can be correctly identified.
|
||||
#[test]
|
||||
fn find_amin_cmin_mmin() {
|
||||
let args = ["-amin", "-cmin", "-mmin"];
|
||||
let times = ["-60", "-120", "-240", "+60", "+120", "+240"];
|
||||
|
||||
for arg in args {
|
||||
for time in times {
|
||||
let deps = FakeDependencies::new();
|
||||
let rc = find_main(&["find", "./test_data/simple/subdir", arg, time], &deps);
|
||||
|
||||
assert_eq!(rc, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_size() {
|
||||
let deps = FakeDependencies::new();
|
||||
@@ -869,9 +897,9 @@ mod tests {
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
let y_options = ["a", "B", "c", "m"];
|
||||
|
||||
x_options.iter().for_each(|&x| {
|
||||
y_options.iter().for_each(|&y| {
|
||||
let arg = &format!("-newer{}{}", x, y).to_string();
|
||||
for &x in x_options.iter() {
|
||||
for &y in &y_options {
|
||||
let arg = &format!("-newer{x}{y}").to_string();
|
||||
let deps = FakeDependencies::new();
|
||||
let rc = find_main(
|
||||
&[
|
||||
@@ -884,16 +912,16 @@ mod tests {
|
||||
);
|
||||
|
||||
assert_eq!(rc, 0);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_find_newer_xy_have_not_birthed_time_filesystem() {
|
||||
let y_options = ["a", "c", "m"];
|
||||
y_options.iter().for_each(|&y| {
|
||||
let arg = &format!("-newerB{}", y).to_string();
|
||||
for &y in &y_options {
|
||||
let arg = &format!("-newerB{y}").to_string();
|
||||
let deps = FakeDependencies::new();
|
||||
let rc = find_main(
|
||||
&[
|
||||
@@ -906,7 +934,7 @@ mod tests {
|
||||
);
|
||||
|
||||
assert_eq!(rc, 1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -918,15 +946,17 @@ mod tests {
|
||||
let args = ["-newerat", "-newerBt", "-newerct", "-newermt"];
|
||||
let times = ["jan 01, 2000", "jan 01, 2000 00:00:00"];
|
||||
|
||||
for (arg, time) in args.iter().zip(times.iter()) {
|
||||
let deps = FakeDependencies::new();
|
||||
let rc = find_main(&["find", "./test_data/simple/subdir", arg, time], &deps);
|
||||
for arg in args {
|
||||
for time in times {
|
||||
let deps = FakeDependencies::new();
|
||||
let rc = find_main(&["find", "./test_data/simple/subdir", arg, time], &deps);
|
||||
|
||||
assert_eq!(rc, 0);
|
||||
assert_eq!(
|
||||
deps.get_output_as_string(),
|
||||
fix_up_slashes("./test_data/simple/subdir\n./test_data/simple/subdir/ABBBC\n"),
|
||||
);
|
||||
assert_eq!(rc, 0);
|
||||
assert!(deps
|
||||
.get_output_as_string()
|
||||
.contains("./test_data/simple/subdir"));
|
||||
assert!(deps.get_output_as_string().contains("ABBBC"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -939,12 +969,14 @@ mod tests {
|
||||
let args = ["-newerat", "-newerBt", "-newerct", "-newermt"];
|
||||
let times = ["jan 01, 2037", "jan 01, 2037 00:00:00"];
|
||||
|
||||
for (arg, time) in args.iter().zip(times.iter()) {
|
||||
let deps = FakeDependencies::new();
|
||||
let rc = find_main(&["find", "./test_data/simple/subdir", arg, time], &deps);
|
||||
for arg in args {
|
||||
for time in times {
|
||||
let deps = FakeDependencies::new();
|
||||
let rc = find_main(&["find", "./test_data/simple/subdir", arg, time], &deps);
|
||||
|
||||
assert_eq!(rc, 0);
|
||||
assert_eq!(deps.get_output_as_string(), "");
|
||||
assert_eq!(rc, 0);
|
||||
assert_eq!(deps.get_output_as_string(), "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -960,14 +992,14 @@ mod tests {
|
||||
let args = ["-newerat", "-newerBt", "-newerct", "-newermt"];
|
||||
let time = "";
|
||||
|
||||
args.iter().for_each(|&arg| {
|
||||
for &arg in &args {
|
||||
let deps = FakeDependencies::new();
|
||||
let rc = find_main(&["find", "./test_data/simple/subdir", arg, time], &deps);
|
||||
|
||||
assert_eq!(rc, 0);
|
||||
// Output comparison has been temporarily removed to account for the possibility that
|
||||
// migration out of the repository started before 00:00 and testing was completed after 00:00.
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -979,11 +1011,212 @@ mod tests {
|
||||
let args = ["-newerat", "-newerBt", "-newerct", "-newermt"];
|
||||
let time = "2037, jan 01";
|
||||
|
||||
args.iter().for_each(|&arg| {
|
||||
for &arg in &args {
|
||||
let deps = FakeDependencies::new();
|
||||
let rc = find_main(&["find", "./test_data/simple/subdir", arg, time], &deps);
|
||||
|
||||
assert_eq!(rc, 1);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_no_permission_file_error() {
|
||||
use std::{path::Path, process::Command};
|
||||
|
||||
let path = Path::new("./test_data/no_permission");
|
||||
let _result = fs::create_dir(path);
|
||||
// Generate files without permissions.
|
||||
// std::fs cannot change file permissions to 000 in normal user state,
|
||||
// so use chmod via Command to change permissions.
|
||||
let _output = Command::new("chmod")
|
||||
.arg("-rwx")
|
||||
.arg("./test_data/no_permission")
|
||||
.output()
|
||||
.expect("cannot set file permission");
|
||||
|
||||
let deps = FakeDependencies::new();
|
||||
let rc = find_main(&["find", "./test_data/no_permission"], &deps);
|
||||
|
||||
assert_eq!(rc, 1);
|
||||
|
||||
// Reset the exit code global variable in case we run another test after this one
|
||||
// See https://github.com/uutils/coreutils/issues/5777
|
||||
uucore::error::set_exit_code(0);
|
||||
|
||||
if path.exists() {
|
||||
let _result = fs::create_dir(path);
|
||||
// Remove the unreadable and writable status of the file to avoid affecting other tests.
|
||||
let _output = Command::new("chmod")
|
||||
.arg("+rwx")
|
||||
.arg("./test_data/no_permission")
|
||||
.output()
|
||||
.expect("cannot set file permission");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_user_predicate() {
|
||||
use std::{os::unix::fs::MetadataExt, path::Path};
|
||||
|
||||
use nix::unistd::{Uid, User};
|
||||
|
||||
let path = Path::new("./test_data/simple/subdir");
|
||||
let uid = path.metadata().unwrap().uid();
|
||||
let user = User::from_uid(Uid::from_raw(uid)).unwrap().unwrap().name;
|
||||
|
||||
let deps = FakeDependencies::new();
|
||||
let rc = find_main(
|
||||
&["find", "./test_data/simple/subdir", "-user", &user],
|
||||
&deps,
|
||||
);
|
||||
|
||||
assert_eq!(rc, 0);
|
||||
assert_eq!(
|
||||
deps.get_output_as_string(),
|
||||
"./test_data/simple/subdir\n./test_data/simple/subdir/ABBBC\n"
|
||||
);
|
||||
|
||||
// test uid
|
||||
let deps = FakeDependencies::new();
|
||||
let rc = find_main(
|
||||
&[
|
||||
"find",
|
||||
"./test_data/simple/subdir",
|
||||
"-uid",
|
||||
&uid.to_string(),
|
||||
],
|
||||
&deps,
|
||||
);
|
||||
assert_eq!(rc, 0);
|
||||
|
||||
// test empty uid
|
||||
let deps = FakeDependencies::new();
|
||||
let rc = find_main(&["find", "./test_data/simple/subdir", "-uid", ""], &deps);
|
||||
assert_eq!(rc, 1);
|
||||
|
||||
// test not a number
|
||||
let deps = FakeDependencies::new();
|
||||
let rc = find_main(&["find", "./test_data/simple/subdir", "-uid", "a"], &deps);
|
||||
assert_eq!(rc, 1);
|
||||
|
||||
// test empty user name
|
||||
["-user", "-nouser"].iter().for_each(|&arg| {
|
||||
let deps = FakeDependencies::new();
|
||||
let rc = find_main(&["find", "./test_data/simple/subdir", arg, ""], &deps);
|
||||
|
||||
assert_eq!(rc, 1);
|
||||
|
||||
let deps = FakeDependencies::new();
|
||||
let rc = find_main(&["find", "./test_data/simple/subdir", arg, " "], &deps);
|
||||
|
||||
assert_eq!(rc, 1);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_nouser_predicate() {
|
||||
let deps = FakeDependencies::new();
|
||||
let rc = find_main(&["find", "./test_data/simple/subdir", "-nouser"], &deps);
|
||||
|
||||
assert_eq!(rc, 0);
|
||||
assert_eq!(deps.get_output_as_string(), "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_group_predicate() {
|
||||
use std::{os::unix::fs::MetadataExt, path::Path};
|
||||
|
||||
use nix::unistd::{Gid, Group};
|
||||
|
||||
let path = Path::new("./test_data/simple/subdir");
|
||||
let gid = path.metadata().unwrap().gid();
|
||||
let group = Group::from_gid(Gid::from_raw(gid)).unwrap().unwrap().name;
|
||||
|
||||
let deps = FakeDependencies::new();
|
||||
let rc = find_main(
|
||||
&["find", "./test_data/simple/subdir", "-group", &group],
|
||||
&deps,
|
||||
);
|
||||
|
||||
assert_eq!(rc, 0);
|
||||
assert_eq!(
|
||||
deps.get_output_as_string(),
|
||||
"./test_data/simple/subdir\n./test_data/simple/subdir/ABBBC\n"
|
||||
);
|
||||
|
||||
// test gid
|
||||
let deps = FakeDependencies::new();
|
||||
let rc = find_main(
|
||||
&[
|
||||
"find",
|
||||
"./test_data/simple/subdir",
|
||||
"-gid",
|
||||
gid.to_string().as_str(),
|
||||
],
|
||||
&deps,
|
||||
);
|
||||
assert_eq!(rc, 0);
|
||||
|
||||
// test empty gid
|
||||
let deps = FakeDependencies::new();
|
||||
let rc = find_main(&["find", "./test_data/simple/subdir", "-gid", ""], &deps);
|
||||
assert_eq!(rc, 1);
|
||||
|
||||
// test not a number
|
||||
let deps = FakeDependencies::new();
|
||||
let rc = find_main(&["find", "./test_data/simple/subdir", "-gid", "a"], &deps);
|
||||
assert_eq!(rc, 1);
|
||||
|
||||
// test empty user name and group name
|
||||
["-group", "-nogroup"].iter().for_each(|&arg| {
|
||||
let deps = FakeDependencies::new();
|
||||
let rc = find_main(&["find", "./test_data/simple/subdir", arg, ""], &deps);
|
||||
|
||||
assert_eq!(rc, 1);
|
||||
|
||||
let deps = FakeDependencies::new();
|
||||
let rc = find_main(&["find", "./test_data/simple/subdir", arg, " "], &deps);
|
||||
|
||||
assert_eq!(rc, 1);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_nogroup_predicate() {
|
||||
let deps = FakeDependencies::new();
|
||||
let rc = find_main(&["find", "./test_data/simple/subdir", "-nogroup"], &deps);
|
||||
|
||||
assert_eq!(rc, 0);
|
||||
assert_eq!(deps.get_output_as_string(), "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn test_fs_matcher() {
|
||||
use crate::find::tests::FakeDependencies;
|
||||
use matchers::fs::get_file_system_type;
|
||||
use std::path::Path;
|
||||
|
||||
let path = Path::new("./test_data/simple/subdir");
|
||||
let target_fs_type = get_file_system_type(path).unwrap();
|
||||
|
||||
// should match fs type
|
||||
let deps = FakeDependencies::new();
|
||||
let rc = find_main(
|
||||
&[
|
||||
"find",
|
||||
"./test_data/simple/subdir",
|
||||
"-fstype",
|
||||
&target_fs_type,
|
||||
],
|
||||
&deps,
|
||||
);
|
||||
|
||||
assert_eq!(rc, 0);
|
||||
}
|
||||
}
|
||||
|
||||
+116
-55
@@ -306,6 +306,7 @@ impl CommandResult {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)] // `Killed` variant is never constructed on Windows
|
||||
#[derive(Debug)]
|
||||
enum CommandExecutionError {
|
||||
// exit code 255
|
||||
@@ -405,14 +406,9 @@ impl CommandBuilder<'_> {
|
||||
let mut command = Command::new(entry_point);
|
||||
|
||||
if let Some(replace_str) = &self.options.replace {
|
||||
// we replace the first instance of the replacement string with
|
||||
// the extra args, and then replace all instances of the replacement
|
||||
let replacement = self
|
||||
.extra_args
|
||||
.iter()
|
||||
.map(|s| s.to_string_lossy())
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
// Replace all occurrences in initial args with the extra arg,
|
||||
// Thanks to `MaxArgsCommandSizeLimiter`, we only process a single extra arg here.
|
||||
let replacement = self.extra_args[0].to_string_lossy();
|
||||
let initial_args: Vec<OsString> = initial_args
|
||||
.iter()
|
||||
.map(|arg| {
|
||||
@@ -686,10 +682,33 @@ impl From<io::Error> for XargsError {
|
||||
}
|
||||
}
|
||||
|
||||
struct InputProcessOptions {
|
||||
exit_if_pass_char_limit: bool,
|
||||
max_args: Option<usize>,
|
||||
max_lines: Option<usize>,
|
||||
no_run_if_empty: bool,
|
||||
}
|
||||
|
||||
impl InputProcessOptions {
|
||||
fn new(
|
||||
exit_if_pass_char_limit: bool,
|
||||
max_args: Option<usize>,
|
||||
max_lines: Option<usize>,
|
||||
no_run_if_empty: bool,
|
||||
) -> Self {
|
||||
InputProcessOptions {
|
||||
exit_if_pass_char_limit,
|
||||
max_args,
|
||||
max_lines,
|
||||
no_run_if_empty,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn process_input(
|
||||
builder_options: CommandBuilderOptions,
|
||||
mut args: Box<dyn ArgumentReader>,
|
||||
options: &Options,
|
||||
options: &InputProcessOptions,
|
||||
) -> Result<CommandResult, XargsError> {
|
||||
let mut current_builder = CommandBuilder::new(&builder_options);
|
||||
let mut have_pending_command = false;
|
||||
@@ -756,6 +775,66 @@ fn validate_positive_usize(s: &str) -> Result<usize, String> {
|
||||
}
|
||||
}
|
||||
|
||||
fn normalize_options<'a>(
|
||||
options: &'a Options,
|
||||
matches: &'a clap::ArgMatches,
|
||||
) -> (Option<usize>, Option<usize>, &'a Option<String>, Option<u8>) {
|
||||
let (max_args, max_lines, replace) =
|
||||
match (options.max_args, options.max_lines, &options.replace) {
|
||||
// These 3 options are mutually exclusive.
|
||||
// But `max_args=1` and `replace` do not actually conflict, so no warning.
|
||||
(None | Some(1), None, Some(_)) => {
|
||||
// If `replace`, all matches in initial args should be replaced with extra args read from stdin.
|
||||
// It is possible to have multiple matches and multiple extra args, and the Cartesian product is desired.
|
||||
// To be specific, we process extra args one by one, and replace all matches with the same extra arg in each time.
|
||||
(Some(1), None, &options.replace)
|
||||
}
|
||||
(Some(_), None, None) | (None, Some(_), None) | (None, None, None) => {
|
||||
(options.max_args, options.max_lines, &None)
|
||||
}
|
||||
_ => {
|
||||
eprintln!(
|
||||
"WARNING: -L, -n and -I/-i are mutually exclusive, but more than one were given; \
|
||||
only the last option will be used"
|
||||
);
|
||||
let lines_index = matches
|
||||
.indices_of(options::MAX_LINES)
|
||||
.and_then(|v| v.last());
|
||||
let args_index = matches.indices_of(options::MAX_ARGS).and_then(|v| v.last());
|
||||
let replace_index = [options::REPLACE, options::REPLACE_I]
|
||||
.iter()
|
||||
.flat_map(|o| matches.indices_of(o).and_then(|v| v.last()))
|
||||
.max();
|
||||
if lines_index > args_index && lines_index > replace_index {
|
||||
(None, options.max_lines, &None)
|
||||
} else if args_index > lines_index && args_index > replace_index {
|
||||
(options.max_args, None, &None)
|
||||
} else {
|
||||
(Some(1), None, &options.replace)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let delimiter = match (options.delimiter, options.null) {
|
||||
(Some(delimiter), true) => {
|
||||
if matches.indices_of(options::NULL).unwrap().last()
|
||||
> matches.indices_of(options::DELIMITER).unwrap().last()
|
||||
{
|
||||
Some(b'\0')
|
||||
} else {
|
||||
Some(delimiter)
|
||||
}
|
||||
}
|
||||
(Some(delimiter), false) => Some(delimiter),
|
||||
(None, true) => Some(b'\0'),
|
||||
// If `replace` and no delimiter specified, each line of stdin turns into a line of stdout,
|
||||
// so the input should be split at newlines only.
|
||||
(None, false) => replace.as_ref().map(|_| b'\n'),
|
||||
};
|
||||
|
||||
(max_args, max_lines, replace, delimiter)
|
||||
}
|
||||
|
||||
fn do_xargs(args: &[&str]) -> Result<CommandResult, XargsError> {
|
||||
let matches = clap::Command::new("xargs")
|
||||
.version(crate_version!())
|
||||
@@ -796,7 +875,7 @@ fn do_xargs(args: &[&str]) -> Result<CommandResult, XargsError> {
|
||||
.long(options::MAX_ARGS)
|
||||
.help(
|
||||
"Set the max number of arguments read from stdin to be passed \
|
||||
to each command invocation (mutually exclusive with -L)",
|
||||
to each command invocation (mutually exclusive with -L and -I/-i)",
|
||||
)
|
||||
.value_parser(validate_positive_usize),
|
||||
)
|
||||
@@ -806,7 +885,7 @@ fn do_xargs(args: &[&str]) -> Result<CommandResult, XargsError> {
|
||||
.long(options::MAX_LINES)
|
||||
.help(
|
||||
"Set the max number of lines from stdin to be passed to each \
|
||||
command invocation (mutually exclusive with -n)",
|
||||
command invocation (mutually exclusive with -n and -I/-i)",
|
||||
)
|
||||
.value_parser(validate_positive_usize),
|
||||
)
|
||||
@@ -856,17 +935,18 @@ fn do_xargs(args: &[&str]) -> Result<CommandResult, XargsError> {
|
||||
.require_equals(true)
|
||||
.value_parser(clap::value_parser!(String))
|
||||
.value_name("R")
|
||||
.help(
|
||||
"Replace R in INITIAL-ARGS with names read from standard input; \
|
||||
if R is unspecified, assume {}",
|
||||
),
|
||||
.help("If R is specified, the same as -I R; otherwise, the same as -I {}"),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(options::REPLACE_I)
|
||||
.short('I')
|
||||
.num_args(1)
|
||||
.help("same as --replace=R")
|
||||
.value_name("R")
|
||||
.help(
|
||||
"Replace R in initial arguments with names read from standard input; \
|
||||
also, the input is split at newlines only
|
||||
(mutually exclusive with -L and -n)",
|
||||
)
|
||||
.overrides_with(options::REPLACE)
|
||||
.value_parser(clap::value_parser!(String)),
|
||||
)
|
||||
@@ -900,20 +980,7 @@ fn do_xargs(args: &[&str]) -> Result<CommandResult, XargsError> {
|
||||
verbose: matches.get_flag(options::VERBOSE),
|
||||
};
|
||||
|
||||
let delimiter = match (options.delimiter, options.null) {
|
||||
(Some(delimiter), true) => {
|
||||
if matches.indices_of(options::NULL).unwrap().last()
|
||||
> matches.indices_of(options::DELIMITER).unwrap().last()
|
||||
{
|
||||
Some(b'\0')
|
||||
} else {
|
||||
Some(delimiter)
|
||||
}
|
||||
}
|
||||
(Some(delimiter), false) => Some(delimiter),
|
||||
(None, true) => Some(b'\0'),
|
||||
(None, false) => None,
|
||||
};
|
||||
let (max_args, max_lines, replace, delimiter) = normalize_options(&options, &matches);
|
||||
|
||||
let action = match matches.get_many::<OsString>(options::COMMAND) {
|
||||
Some(args) if args.len() > 0 => {
|
||||
@@ -924,36 +991,21 @@ fn do_xargs(args: &[&str]) -> Result<CommandResult, XargsError> {
|
||||
let env = std::env::vars_os().collect();
|
||||
|
||||
let mut limiters = LimiterCollection::new();
|
||||
|
||||
match (options.max_args, options.max_lines) {
|
||||
(Some(max_args), Some(max_lines)) => {
|
||||
eprintln!(
|
||||
"WARNING: Both --{} and -L were given; last option will be used",
|
||||
options::MAX_ARGS,
|
||||
);
|
||||
if matches.indices_of(options::MAX_LINES).unwrap().last()
|
||||
> matches.indices_of(options::MAX_ARGS).unwrap().last()
|
||||
{
|
||||
limiters.add(MaxLinesCommandSizeLimiter::new(max_lines));
|
||||
} else {
|
||||
limiters.add(MaxArgsCommandSizeLimiter::new(max_args));
|
||||
}
|
||||
}
|
||||
(Some(max_args), None) => limiters.add(MaxArgsCommandSizeLimiter::new(max_args)),
|
||||
(None, Some(max_lines)) => limiters.add(MaxLinesCommandSizeLimiter::new(max_lines)),
|
||||
(None, None) => (),
|
||||
if let Some(max_args) = max_args {
|
||||
limiters.add(MaxArgsCommandSizeLimiter::new(max_args));
|
||||
}
|
||||
if let Some(max_lines) = max_lines {
|
||||
limiters.add(MaxLinesCommandSizeLimiter::new(max_lines));
|
||||
}
|
||||
|
||||
if let Some(max_chars) = options.max_chars {
|
||||
limiters.add(MaxCharsCommandSizeLimiter::new(max_chars));
|
||||
}
|
||||
|
||||
limiters.add(MaxCharsCommandSizeLimiter::new_system(&env));
|
||||
|
||||
let mut builder_options =
|
||||
CommandBuilderOptions::new(action, env, limiters, options.replace.clone()).map_err(
|
||||
|_| "Base command and environment are too large to fit into one command execution",
|
||||
)?;
|
||||
let mut builder_options = CommandBuilderOptions::new(action, env, limiters, replace.clone())
|
||||
.map_err(|_| {
|
||||
"Base command and environment are too large to fit into one command execution"
|
||||
})?;
|
||||
|
||||
builder_options.verbose = options.verbose;
|
||||
builder_options.close_stdin = options.arg_file.is_none();
|
||||
@@ -970,7 +1022,16 @@ fn do_xargs(args: &[&str]) -> Result<CommandResult, XargsError> {
|
||||
Box::new(WhitespaceDelimitedArgumentReader::new(args_file))
|
||||
};
|
||||
|
||||
let result = process_input(builder_options, args, &options)?;
|
||||
let result = process_input(
|
||||
builder_options,
|
||||
args,
|
||||
&InputProcessOptions::new(
|
||||
options.exit_if_pass_char_limit,
|
||||
max_args,
|
||||
max_lines,
|
||||
options.no_run_if_empty,
|
||||
),
|
||||
)?;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
|
||||
@@ -441,6 +441,18 @@ fn find_inum() {
|
||||
.stdout(predicate::str::contains("abbbc"));
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
#[test]
|
||||
fn find_inum() {
|
||||
Command::cargo_bin("find")
|
||||
.expect("found binary")
|
||||
.args(["test_data", "-inum", "1"])
|
||||
.assert()
|
||||
.failure()
|
||||
.stderr(predicate::str::contains("not available on this platform"))
|
||||
.stdout(predicate::str::is_empty());
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[serial(working_dir)]
|
||||
#[test]
|
||||
@@ -454,6 +466,18 @@ fn find_links() {
|
||||
.stdout(predicate::str::contains("abbbc"));
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
#[test]
|
||||
fn find_links() {
|
||||
Command::cargo_bin("find")
|
||||
.expect("found binary")
|
||||
.args(["test_data", "-links", "1"])
|
||||
.assert()
|
||||
.failure()
|
||||
.stderr(predicate::str::contains("not available on this platform"))
|
||||
.stdout(predicate::str::is_empty());
|
||||
}
|
||||
|
||||
#[serial(working_dir)]
|
||||
#[test]
|
||||
fn find_mount_xdev() {
|
||||
@@ -505,3 +529,355 @@ fn find_accessible() {
|
||||
.stderr(predicate::str::is_empty())
|
||||
.stdout(predicate::str::contains("abbbc").not());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_time() {
|
||||
let args = ["1", "+1", "-1"];
|
||||
let exception_args = ["1%2", "1%2%3", "1a2", "1%2a", "abc", "-", "+", "%"];
|
||||
|
||||
["-ctime", "-atime", "-mtime"].iter().for_each(|flag| {
|
||||
args.iter().for_each(|arg| {
|
||||
Command::cargo_bin("find")
|
||||
.expect("found binary")
|
||||
.args([".", flag, arg])
|
||||
.assert()
|
||||
.success()
|
||||
.stderr(predicate::str::is_empty());
|
||||
});
|
||||
|
||||
exception_args.iter().for_each(|arg| {
|
||||
Command::cargo_bin("find")
|
||||
.expect("found binary")
|
||||
.args([".", flag, arg])
|
||||
.assert()
|
||||
.failure()
|
||||
.stdout(predicate::str::is_empty());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expression_empty_parentheses() {
|
||||
Command::cargo_bin("find")
|
||||
.expect("found binary")
|
||||
.args(["-true", "(", ")"])
|
||||
.assert()
|
||||
.failure()
|
||||
.stderr(predicate::str::contains(
|
||||
"empty parentheses are not allowed",
|
||||
))
|
||||
.stdout(predicate::str::is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
#[serial(working_dir)]
|
||||
fn find_with_user_predicate() {
|
||||
// Considering the different test environments,
|
||||
// the test code can only use a specific default user to perform the test,
|
||||
// such as the root user on Linux.
|
||||
Command::cargo_bin("find")
|
||||
.expect("found binary")
|
||||
.args(["test_data", "-user", "root"])
|
||||
.assert()
|
||||
.success()
|
||||
.stderr(predicate::str::is_empty());
|
||||
|
||||
Command::cargo_bin("find")
|
||||
.expect("found binary")
|
||||
.args(["test_data", "-user", ""])
|
||||
.assert()
|
||||
.failure()
|
||||
.stderr(predicate::str::contains("empty"))
|
||||
.stdout(predicate::str::is_empty());
|
||||
|
||||
Command::cargo_bin("find")
|
||||
.expect("found binary")
|
||||
.args(["test_data", "-user", " "])
|
||||
.assert()
|
||||
.failure()
|
||||
.stderr(predicate::str::contains("is not the name of a known user"))
|
||||
.stdout(predicate::str::is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial(working_dir)]
|
||||
fn find_with_nouser_predicate() {
|
||||
Command::cargo_bin("find")
|
||||
.expect("found binary")
|
||||
.args(["test_data", "-nouser"])
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::is_empty())
|
||||
.stderr(predicate::str::is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
#[serial(working_dir)]
|
||||
fn find_with_uid_predicate() {
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
use std::path::Path;
|
||||
|
||||
let path = Path::new("./test_data");
|
||||
let uid = path.metadata().unwrap().uid();
|
||||
|
||||
Command::cargo_bin("find")
|
||||
.expect("found binary")
|
||||
.args(["test_data", "-uid", &uid.to_string()])
|
||||
.assert()
|
||||
.success()
|
||||
.stderr(predicate::str::is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial(working_dir)]
|
||||
fn find_with_group_predicate() {
|
||||
// Considering the different test environments,
|
||||
// the test code can only use a specific default user group for the test,
|
||||
// such as the root user group on Linux.
|
||||
#[cfg(target_os = "linux")]
|
||||
Command::cargo_bin("find")
|
||||
.expect("found binary")
|
||||
.args(["test_data", "-group", "root"])
|
||||
.assert()
|
||||
.success()
|
||||
.stderr(predicate::str::is_empty());
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
Command::cargo_bin("find")
|
||||
.expect("found binary")
|
||||
.args(["test_data", "-group", "staff"])
|
||||
.assert()
|
||||
.success()
|
||||
.stderr(predicate::str::is_empty());
|
||||
|
||||
Command::cargo_bin("find")
|
||||
.expect("found binary")
|
||||
.args(["test_data", "-group", ""])
|
||||
.assert()
|
||||
.failure()
|
||||
.stderr(predicate::str::contains("empty"))
|
||||
.stdout(predicate::str::is_empty());
|
||||
|
||||
Command::cargo_bin("find")
|
||||
.expect("found binary")
|
||||
.args(["test_data", "-group", " "])
|
||||
.assert()
|
||||
.failure()
|
||||
.stderr(predicate::str::contains(
|
||||
"is not the name of an existing group",
|
||||
))
|
||||
.stdout(predicate::str::is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial(working_dir)]
|
||||
fn find_with_nogroup_predicate() {
|
||||
Command::cargo_bin("find")
|
||||
.expect("found binary")
|
||||
.args(["test_data", "-nogroup"])
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::is_empty())
|
||||
.stderr(predicate::str::is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
#[serial(working_dir)]
|
||||
fn find_with_gid_predicate() {
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
use std::path::Path;
|
||||
|
||||
let path = Path::new("./test_data");
|
||||
let gid = path.metadata().unwrap().gid();
|
||||
|
||||
Command::cargo_bin("find")
|
||||
.expect("found binary")
|
||||
.args(["test_data", "-gid", &gid.to_string()])
|
||||
.assert()
|
||||
.success()
|
||||
.stderr(predicate::str::is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial(working_dir)]
|
||||
fn find_newer_xy() {
|
||||
#[cfg(target_os = "linux")]
|
||||
let options = ["a", "c", "m"];
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
let options = ["a", "B", "c", "m"];
|
||||
|
||||
for x in options {
|
||||
for y in options {
|
||||
let arg = &format!("-newer{x}{y}");
|
||||
Command::cargo_bin("find")
|
||||
.expect("found binary")
|
||||
.args([
|
||||
"./test_data/simple/subdir",
|
||||
arg,
|
||||
"./test_data/simple/subdir/ABBBC",
|
||||
])
|
||||
.assert()
|
||||
.success()
|
||||
.stderr(predicate::str::is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
let args = ["-newerat", "-newerct", "-newermt"];
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
let args = ["-newerat", "-newerBt", "-newerct", "-newermt"];
|
||||
let times = ["jan 01, 2000", "jan 01, 2000 00:00:00"];
|
||||
|
||||
for arg in args {
|
||||
for time in times {
|
||||
let arg = &format!("{arg}{time}");
|
||||
Command::cargo_bin("find")
|
||||
.expect("found binary")
|
||||
.args(["./test_data/simple/subdir", arg, time])
|
||||
.assert()
|
||||
.success()
|
||||
.stderr(predicate::str::is_empty());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial(working_dir)]
|
||||
fn find_age_range() {
|
||||
let args = ["-amin", "-cmin", "-mmin"];
|
||||
let times = ["-60", "-120", "-240", "+60", "+120", "+240"];
|
||||
let time_strings = [
|
||||
"\"-60\"", "\"-120\"", "\"-240\"", "\"-60\"", "\"-120\"", "\"-240\"",
|
||||
];
|
||||
|
||||
for arg in args {
|
||||
for time in times {
|
||||
Command::cargo_bin("find")
|
||||
.expect("the time should match")
|
||||
.args(["test_data/simple", arg, time])
|
||||
.assert()
|
||||
.success()
|
||||
.code(0);
|
||||
}
|
||||
}
|
||||
|
||||
for arg in args {
|
||||
for time_string in time_strings {
|
||||
Command::cargo_bin("find")
|
||||
.expect("the time should not match")
|
||||
.args(["test_data/simple", arg, time_string])
|
||||
.assert()
|
||||
.failure()
|
||||
.code(1)
|
||||
.stderr(predicate::str::contains(
|
||||
"Error: Expected a decimal integer (with optional + or - prefix) argument to",
|
||||
))
|
||||
.stdout(predicate::str::is_empty());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
#[serial(working_dir)]
|
||||
fn find_fs() {
|
||||
use findutils::find::matchers::fs::get_file_system_type;
|
||||
use std::path::Path;
|
||||
|
||||
let path = Path::new("./test_data/simple/subdir");
|
||||
let target_fs_type = get_file_system_type(path).unwrap();
|
||||
|
||||
// match fs type
|
||||
Command::cargo_bin("find")
|
||||
.expect("found binary")
|
||||
.args(["./test_data/simple/subdir", "-fstype", &target_fs_type])
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::contains("./test_data/simple/subdir"))
|
||||
.stderr(predicate::str::is_empty());
|
||||
|
||||
// not match fs type
|
||||
Command::cargo_bin("find")
|
||||
.expect("found binary")
|
||||
.args([
|
||||
"./test_data/simple/subdir",
|
||||
"-fstype",
|
||||
format!("{} foo", target_fs_type).as_str(),
|
||||
])
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::is_empty())
|
||||
.stderr(predicate::str::is_empty());
|
||||
|
||||
// not contain fstype text.
|
||||
Command::cargo_bin("find")
|
||||
.expect("found binary")
|
||||
.args(["./test_data/simple/subdir", "-fstype"])
|
||||
.assert()
|
||||
.failure()
|
||||
.stdout(predicate::str::is_empty());
|
||||
|
||||
// void fstype
|
||||
Command::cargo_bin("find")
|
||||
.expect("found binary")
|
||||
.args(["./test_data/simple/subdir", "-fstype", " "])
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::is_empty())
|
||||
.stderr(predicate::str::is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial(working_dir)]
|
||||
fn find_samefile() {
|
||||
use std::fs;
|
||||
|
||||
// remove file if hard link file exist.
|
||||
// But you can't delete a file that doesn't exist,
|
||||
// so ignore the error returned here.
|
||||
let _ = fs::remove_file("test_data/links/hard_link");
|
||||
fs::hard_link("test_data/links/abbbc", "test_data/links/hard_link").unwrap();
|
||||
|
||||
Command::cargo_bin("find")
|
||||
.expect("found binary")
|
||||
.args([
|
||||
"./test_data/links/abbbc",
|
||||
"-samefile",
|
||||
"./test_data/links/hard_link",
|
||||
])
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::contains("./test_data/links/abbbc"))
|
||||
.stderr(predicate::str::is_empty());
|
||||
|
||||
// test . path
|
||||
Command::cargo_bin("find")
|
||||
.expect("found binary")
|
||||
.args([".", "-samefile", "."])
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::contains("."))
|
||||
.stderr(predicate::str::is_empty());
|
||||
|
||||
Command::cargo_bin("find")
|
||||
.expect("found binary")
|
||||
.args([".", "-samefile", "./test_data/links/abbbc"])
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::contains(fix_up_slashes(
|
||||
"./test_data/links/abbbc",
|
||||
)))
|
||||
.stderr(predicate::str::is_empty());
|
||||
|
||||
// test not exist file
|
||||
Command::cargo_bin("find")
|
||||
.expect("found binary")
|
||||
.args([".", "-samefile", "./test_data/links/not-exist-file"])
|
||||
.assert()
|
||||
.failure()
|
||||
.stdout(predicate::str::is_empty())
|
||||
.stderr(predicate::str::contains("not-exist-file"));
|
||||
}
|
||||
|
||||
@@ -139,6 +139,16 @@ fn xargs_max_args_lines_conflict() {
|
||||
.stderr(predicate::str::contains("WARNING"))
|
||||
.stdout(predicate::str::diff("ab cd\nef gh\ni\n"));
|
||||
|
||||
Command::cargo_bin("xargs")
|
||||
.expect("found binary")
|
||||
// -n2 is last, so it should be given priority.
|
||||
.args(["-I=_", "-n2", "echo", "_"])
|
||||
.write_stdin("ab cd ef\ngh i\njkl")
|
||||
.assert()
|
||||
.success()
|
||||
.stderr(predicate::str::contains("WARNING"))
|
||||
.stdout(predicate::str::diff("_ ab cd\n_ ef gh\n_ i jkl\n"));
|
||||
|
||||
Command::cargo_bin("xargs")
|
||||
.expect("found binary")
|
||||
// -L2 is last, so it should be given priority.
|
||||
@@ -148,6 +158,28 @@ fn xargs_max_args_lines_conflict() {
|
||||
.success()
|
||||
.stderr(predicate::str::contains("WARNING"))
|
||||
.stdout(predicate::str::diff("ab cd ef\ngh i jkl\n"));
|
||||
|
||||
Command::cargo_bin("xargs")
|
||||
.expect("found binary")
|
||||
// -L2 is last, so it should be given priority.
|
||||
.args(["-I=_", "-L2", "echo", "_"])
|
||||
.write_stdin("ab cd\nef\ngh i\n\njkl\n")
|
||||
.assert()
|
||||
.success()
|
||||
.stderr(predicate::str::contains("WARNING"))
|
||||
.stdout(predicate::str::diff("_ ab cd ef\n_ gh i jkl\n"));
|
||||
|
||||
for redundant_arg in ["-L2", "-n2"] {
|
||||
Command::cargo_bin("xargs")
|
||||
.expect("found binary")
|
||||
// -I={} is last, so it should be given priority.
|
||||
.args([redundant_arg, "-I={}", "echo", "{} bar"])
|
||||
.write_stdin("ab cd ef\ngh i\njkl")
|
||||
.assert()
|
||||
.success()
|
||||
.stderr(predicate::str::contains("WARNING"))
|
||||
.stdout(predicate::str::diff("ab cd ef bar\ngh i bar\njkl bar\n"));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -502,3 +534,33 @@ fn xargs_replace() {
|
||||
.failure()
|
||||
.stderr(predicate::str::contains("Error: Command not found"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xargs_replace_multiple_lines() {
|
||||
Command::cargo_bin("xargs")
|
||||
.expect("found binary")
|
||||
.args(["-I", "_", "echo", "[_]"])
|
||||
.write_stdin("ab c\nd ef\ng")
|
||||
.assert()
|
||||
.success()
|
||||
.stderr(predicate::str::is_empty())
|
||||
.stdout(predicate::str::diff("[ab c]\n[d ef]\n[g]\n"));
|
||||
|
||||
Command::cargo_bin("xargs")
|
||||
.expect("found binary")
|
||||
.args(["-I", "{}", "echo", "{} {} foo"])
|
||||
.write_stdin("bar\nbaz")
|
||||
.assert()
|
||||
.success()
|
||||
.stderr(predicate::str::is_empty())
|
||||
.stdout(predicate::str::diff("bar bar foo\nbaz baz foo\n"));
|
||||
|
||||
Command::cargo_bin("xargs")
|
||||
.expect("found binary")
|
||||
.args(["-I", "non-exist", "echo"])
|
||||
.write_stdin("abc\ndef\ng")
|
||||
.assert()
|
||||
.success()
|
||||
.stderr(predicate::str::is_empty())
|
||||
.stdout(predicate::str::diff("\n\n\n"));
|
||||
}
|
||||
|
||||
@@ -21,6 +21,10 @@ fail_d = int(current["fail"]) - int(last["fail"])
|
||||
# Get an annotation to highlight changes
|
||||
print(f"::warning ::Changes from main: PASS {pass_d:+d} / SKIP {skip_d:+d} / FAIL {fail_d:+d}")
|
||||
|
||||
# Check if there are no changes.
|
||||
if pass_d == 0:
|
||||
print("::warning ::BFS tests No changes")
|
||||
|
||||
# If results are worse fail the job to draw attention
|
||||
if pass_d < 0:
|
||||
sys.exit(1)
|
||||
|
||||
@@ -24,6 +24,10 @@ print(
|
||||
f"::warning ::Changes from main: PASS {pass_d:+d} / FAIL {fail_d:+d} / ERROR {error_d:+d} / SKIP {skip_d:+d} "
|
||||
)
|
||||
|
||||
# Check if there are no changes.
|
||||
if pass_d == 0:
|
||||
print("::warning ::Gnu tests No changes")
|
||||
|
||||
# If results are worse fail the job to draw attention
|
||||
if pass_d < 0:
|
||||
sys.exit(1)
|
||||
|
||||
Reference in New Issue
Block a user