2 Commits

Author SHA1 Message Date
Sylvestre Ledru 8cff2df69a release: version 0.5.0 2024-04-04 00:17:25 +02:00
Sylvestre Ledru d35249ffab chore: wow shiny new cargo-dist CI 2024-04-04 00:08:20 +02:00
19 changed files with 424 additions and 2140 deletions
+5 -5
View File
@@ -53,9 +53,9 @@ jobs:
- run: echo "LIBCLANG_PATH=$((gcm clang).source -replace "clang.exe")" >> $env:GITHUB_ENV
if: matrix.os == 'windows-latest'
- name: Test
- name: Check
run: |
cargo test
cargo check
fmt:
name: cargo fmt --all -- --check
@@ -86,7 +86,7 @@ jobs:
- run: rustup component add clippy
- name: cargo clippy
run: |
cargo clippy --all-targets -- -D warnings
cargo clippy -- -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
-76
View File
@@ -1,76 +0,0 @@
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'
});
+136 -187
View File
@@ -1,200 +1,149 @@
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@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
- 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
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@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
- 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
Generated
+70 -82
View File
@@ -4,9 +4,9 @@ version = 3
[[package]]
name = "aho-corasick"
version = "1.1.3"
version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
dependencies = [
"memchr",
]
@@ -109,11 +109,12 @@ checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
[[package]]
name = "bstr"
version = "1.9.1"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706"
checksum = "fca0852af221f458706eb0725c03e4ed6c46af9ac98e6a689d5e634215d594dd"
dependencies = [
"memchr",
"once_cell",
"regex-automata",
"serde",
]
@@ -124,6 +125,12 @@ 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"
@@ -142,17 +149,11 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
[[package]]
name = "cfg_aliases"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "chrono"
version = "0.4.38"
version = "0.4.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
checksum = "8a0d04d43504c61aa6c7531f1871dd0d418d91130162063b789da00fd7057a5e"
dependencies = [
"android-tzdata",
"iana-time-zone",
@@ -164,18 +165,18 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.7"
version = "4.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f"
checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0"
dependencies = [
"clap_builder",
]
[[package]]
name = "clap_builder"
version = "4.5.7"
version = "4.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f"
checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4"
dependencies = [
"anstream",
"anstyle",
@@ -202,6 +203,18 @@ 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"
@@ -267,14 +280,14 @@ dependencies = [
[[package]]
name = "findutils"
version = "0.6.0"
version = "0.5.0"
dependencies = [
"assert_cmd",
"chrono",
"clap",
"faccess",
"filetime",
"nix 0.29.0",
"nix",
"once_cell",
"onig",
"predicates",
@@ -378,6 +391,12 @@ 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"
@@ -402,10 +421,16 @@ dependencies = [
]
[[package]]
name = "libc"
version = "0.2.155"
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.153"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
[[package]]
name = "linux-raw-sys"
@@ -434,9 +459,9 @@ dependencies = [
[[package]]
name = "memchr"
version = "2.7.2"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc"
[[package]]
name = "nix"
@@ -446,19 +471,7 @@ checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4"
dependencies = [
"bitflags 2.4.1",
"cfg-if",
"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",
"cfg_aliases",
"libc",
]
@@ -477,12 +490,6 @@ 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"
@@ -630,32 +637,29 @@ dependencies = [
[[package]]
name = "regex"
version = "1.10.5"
version = "1.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f"
checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.7"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df"
checksum = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
"byteorder",
]
[[package]]
name = "regex-syntax"
version = "0.8.4"
version = "0.6.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
[[package]]
name = "rustix"
@@ -679,27 +683,12 @@ 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"
@@ -708,23 +697,23 @@ checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965"
[[package]]
name = "serial_test"
version = "3.1.1"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b4b487fe2acf240a021cf57c6b2b4903b1e78ca0ecd862a71b71d2a51fed77d"
checksum = "953ad9342b3aaca7cb43c45c097dd008d4907070394bd0751a0aa8817e5a018d"
dependencies = [
"dashmap",
"futures",
"lazy_static",
"log",
"once_cell",
"parking_lot",
"scc",
"serial_test_derive",
]
[[package]]
name = "serial_test_derive"
version = "3.1.1"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82fe9db325bcef1fbcde82e078a5cc4efdf787e96b3b9cf45b50b529f2083d67"
checksum = "b93fb4adc70021ac1b47f7d45e8cc4169baaa7ea58483bc5b721d19a26202212"
dependencies = [
"proc-macro2",
"quote",
@@ -808,16 +797,15 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "uucore"
version = "0.0.27"
version = "0.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b54aad02cf7e96f5fafabb6b836efa73eef934783b17530095a29ffd4fdc154"
checksum = "23994a722acb43dbc56877e271c9723f167ae42c4c089f909b2d7dd106c3a9b4"
dependencies = [
"clap",
"dunce",
"glob",
"libc",
"nix 0.28.0",
"number_prefix",
"nix",
"once_cell",
"os_display",
"uucore_procs",
@@ -918,9 +906,9 @@ checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838"
[[package]]
name = "wild"
version = "2.2.1"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3131afc8c575281e1e80f36ed6a092aa502c08b18ed7524e86fbbb12bb410e1"
checksum = "10d01931a94d5a115a53f95292f51d316856b68a035618eb831bbba593a30b67"
dependencies = [
"glob",
]
@@ -943,11 +931,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.8"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b"
checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
dependencies = [
"windows-sys 0.52.0",
"winapi",
]
[[package]]
+7 -13
View File
@@ -1,6 +1,6 @@
[package]
name = "findutils"
version = "0.6.0"
version = "0.5.0"
homepage = "https://github.com/uutils/findutils"
repository = "https://github.com/uutils/findutils"
edition = "2021"
@@ -10,22 +10,21 @@ description = "Rust implementation of GNU findutils"
authors = ["uutils developers"]
[dependencies]
chrono = "0.4.38"
chrono = "0.4.37"
clap = "4.5"
faccess = "0.2.4"
walkdir = "2.5"
regex = "1.10"
regex = "1.7"
once_cell = "1.19"
onig = { version = "6.4", default-features = false }
uucore = { version = "0.0.27", features = ["entries", "fs", "fsext", "mode"] }
nix = { version = "0.29", features = ["fs", "user"] }
uucore = { version = "0.0.25", features = ["entries", "fs", "fsext", "mode"] }
[dev-dependencies]
assert_cmd = "2"
filetime = "0.2"
nix = { version = "0.29", features = ["fs"] }
nix = { version = "0.28", features = ["fs"] }
predicates = "3"
serial_test = "3.1"
serial_test = "3.0"
tempfile = "3"
pretty_assertions = "1.4.0"
@@ -55,11 +54,6 @@ 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
View File
@@ -5,11 +5,6 @@
// 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();
-128
View File
@@ -1,128 +0,0 @@
// 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
);
}
}
-166
View File
@@ -1,166 +0,0 @@
// 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"
);
}
}
+39 -242
View File
File diff suppressed because it is too large Load Diff
-59
View File
@@ -1,59 +0,0 @@
// 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()));
}
}
+38 -10
View File
@@ -4,8 +4,10 @@
// 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};
@@ -16,18 +18,32 @@ pub struct InodeMatcher {
}
impl InodeMatcher {
pub fn new(ino: ComparableValue) -> Self {
Self { ino }
#[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",
))
}
}
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.
@@ -36,18 +52,30 @@ pub struct LinksMatcher {
}
impl LinksMatcher {
pub fn new(nlink: ComparableValue) -> Self {
Self { nlink }
#[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"))
}
}
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)]
@@ -64,19 +92,19 @@ mod tests {
let metadata = file_info.metadata().unwrap();
let deps = FakeDependencies::new();
let matcher = InodeMatcher::new(ComparableValue::EqualTo(metadata.ino()));
let matcher = InodeMatcher::new(ComparableValue::EqualTo(metadata.ino())).unwrap();
assert!(
matcher.matches(&file_info, &mut deps.new_matcher_io()),
"inode number should match"
);
let matcher = InodeMatcher::new(ComparableValue::LessThan(metadata.ino()));
let matcher = InodeMatcher::new(ComparableValue::LessThan(metadata.ino())).unwrap();
assert!(
!matcher.matches(&file_info, &mut deps.new_matcher_io()),
"inode number should not match"
);
let matcher = InodeMatcher::new(ComparableValue::MoreThan(metadata.ino()));
let matcher = InodeMatcher::new(ComparableValue::MoreThan(metadata.ino())).unwrap();
assert!(
!matcher.matches(&file_info, &mut deps.new_matcher_io()),
"inode number should not match"
@@ -88,19 +116,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));
let matcher = LinksMatcher::new(ComparableValue::EqualTo(1)).unwrap();
assert!(
matcher.matches(&file_info, &mut deps.new_matcher_io()),
"link count should match"
);
let matcher = LinksMatcher::new(ComparableValue::LessThan(1));
let matcher = LinksMatcher::new(ComparableValue::LessThan(1)).unwrap();
assert!(
!matcher.matches(&file_info, &mut deps.new_matcher_io()),
"link count should not match"
);
let matcher = LinksMatcher::new(ComparableValue::MoreThan(1));
let matcher = LinksMatcher::new(ComparableValue::MoreThan(1)).unwrap();
assert!(
!matcher.matches(&file_info, &mut deps.new_matcher_io()),
"link count should not match"
+46 -184
View File
@@ -171,17 +171,14 @@ 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 = this_time
.duration_since(UNIX_EPOCH)
.unwrap_or_else(|e| e.duration());
let timestamp = match this_time.duration_since(UNIX_EPOCH) {
Ok(duration) => duration,
Err(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()
.expect("timestamp memory implications"))
Ok(self.time <= timestamp.as_millis().try_into().unwrap())
}
}
@@ -280,54 +277,6 @@ 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;
@@ -391,7 +340,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 * SECONDS_PER_DAY as u64, 0));
deps.set_time(files_mtime + Duration::new(2 * super::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"
@@ -410,7 +359,7 @@ mod tests {
);
// set "now" to 1 day after the file was modified.
deps.set_time(files_mtime + Duration::new((3 * SECONDS_PER_DAY / 2) as u64, 0));
deps.set_time(files_mtime + Duration::new((3 * super::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"
@@ -557,12 +506,16 @@ mod tests {
#[test]
fn newer_option_matcher() {
#[cfg(target_os = "linux")]
let options = ["a", "c", "m"];
let x_options = ["a", "c", "m"];
#[cfg(not(target_os = "linux"))]
let options = ["a", "B", "c", "m"];
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"];
for x_option in options {
for y_option in options {
for x_option in &x_options {
for y_option in &y_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";
@@ -587,27 +540,18 @@ 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());
#[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."
);
}
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."
);
}
}
}
@@ -656,14 +600,15 @@ mod tests {
// modified time test
let modified_matcher = NewerTimeMatcher::new(NewerOptionType::Modified, time);
let buffer = [0; 10];
let mut file = OpenOptions::new()
.read(true)
.write(true)
.open(&foo_path)
.expect("open temp file");
let _ = file.write(&buffer);
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);
}
assert!(
modified_matcher.matches(&file_info, &mut deps.new_matcher_io()),
"file modified time should after 'time'"
@@ -686,103 +631,20 @@ 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());
#[cfg(unix)]
{
let matchers = [
&created_matcher,
&accessed_matcher,
&modified_matcher,
&inode_changed_matcher,
];
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."
);
}
}
-160
View File
@@ -1,160 +0,0 @@
// 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"
);
}
}
+28 -261
View File
@@ -29,7 +29,7 @@ impl Default for Config {
same_file_system: false,
depth_first: false,
min_depth: 0,
max_depth: usize::MAX,
max_depth: usize::max_value(),
sorted_output: false,
help_requested: false,
version_requested: false,
@@ -150,10 +150,7 @@ fn process_dir<'a>(
let mut it = walkdir.into_iter();
while let Some(result) = it.next() {
match result {
Err(err) => {
uucore::error::set_exit_code(1);
writeln!(&mut stderr(), "Error: {dir}: {err}").unwrap()
}
Err(err) => writeln!(&mut stderr(), "Error: {dir}: {err}").unwrap(),
Ok(entry) => {
let mut matcher_io = matchers::MatcherIO::new(deps);
if matcher.matches(&entry, &mut matcher_io) {
@@ -257,7 +254,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(_) => uucore::error::get_exit_code(),
Ok(_) => 0,
Err(e) => {
writeln!(&mut stderr(), "Error: {e}").unwrap();
1
@@ -277,7 +274,7 @@ mod tests {
use std::os::unix::fs::symlink;
#[cfg(windows)]
use std::os::windows::fs::symlink_file;
use std::os::windows::fs::{symlink_dir, symlink_file};
use crate::find::matchers::MatcherIO;
@@ -736,31 +733,6 @@ 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();
@@ -897,9 +869,9 @@ mod tests {
#[cfg(not(target_os = "linux"))]
let y_options = ["a", "B", "c", "m"];
for &x in x_options.iter() {
for &y in &y_options {
let arg = &format!("-newer{x}{y}").to_string();
x_options.iter().for_each(|&x| {
y_options.iter().for_each(|&y| {
let arg = &format!("-newer{}{}", x, y).to_string();
let deps = FakeDependencies::new();
let rc = find_main(
&[
@@ -912,16 +884,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"];
for &y in &y_options {
let arg = &format!("-newerB{y}").to_string();
y_options.iter().for_each(|&y| {
let arg = &format!("-newerB{}", y).to_string();
let deps = FakeDependencies::new();
let rc = find_main(
&[
@@ -934,7 +906,7 @@ mod tests {
);
assert_eq!(rc, 1);
}
});
}
#[test]
@@ -946,17 +918,15 @@ mod tests {
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 deps = FakeDependencies::new();
let rc = find_main(&["find", "./test_data/simple/subdir", arg, time], &deps);
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);
assert_eq!(rc, 0);
assert!(deps
.get_output_as_string()
.contains("./test_data/simple/subdir"));
assert!(deps.get_output_as_string().contains("ABBBC"));
}
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"),
);
}
}
@@ -969,14 +939,12 @@ mod tests {
let args = ["-newerat", "-newerBt", "-newerct", "-newermt"];
let times = ["jan 01, 2037", "jan 01, 2037 00:00:00"];
for arg in args {
for time in times {
let deps = FakeDependencies::new();
let rc = find_main(&["find", "./test_data/simple/subdir", arg, time], &deps);
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);
assert_eq!(rc, 0);
assert_eq!(deps.get_output_as_string(), "");
}
assert_eq!(rc, 0);
assert_eq!(deps.get_output_as_string(), "");
}
}
@@ -992,14 +960,14 @@ mod tests {
let args = ["-newerat", "-newerBt", "-newerct", "-newermt"];
let time = "";
for &arg in &args {
args.iter().for_each(|&arg| {
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]
@@ -1011,212 +979,11 @@ mod tests {
let args = ["-newerat", "-newerBt", "-newerct", "-newermt"];
let time = "2037, jan 01";
for &arg in &args {
args.iter().for_each(|&arg| {
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);
}
}
+55 -116
View File
@@ -306,7 +306,6 @@ impl CommandResult {
}
}
#[allow(dead_code)] // `Killed` variant is never constructed on Windows
#[derive(Debug)]
enum CommandExecutionError {
// exit code 255
@@ -406,9 +405,14 @@ impl CommandBuilder<'_> {
let mut command = Command::new(entry_point);
if let Some(replace_str) = &self.options.replace {
// 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();
// 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(" ");
let initial_args: Vec<OsString> = initial_args
.iter()
.map(|arg| {
@@ -682,33 +686,10 @@ 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: &InputProcessOptions,
options: &Options,
) -> Result<CommandResult, XargsError> {
let mut current_builder = CommandBuilder::new(&builder_options);
let mut have_pending_command = false;
@@ -775,66 +756,6 @@ 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!())
@@ -875,7 +796,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 and -I/-i)",
to each command invocation (mutually exclusive with -L)",
)
.value_parser(validate_positive_usize),
)
@@ -885,7 +806,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 and -I/-i)",
command invocation (mutually exclusive with -n)",
)
.value_parser(validate_positive_usize),
)
@@ -935,18 +856,17 @@ fn do_xargs(args: &[&str]) -> Result<CommandResult, XargsError> {
.require_equals(true)
.value_parser(clap::value_parser!(String))
.value_name("R")
.help("If R is specified, the same as -I R; otherwise, the same as -I {}"),
.help(
"Replace R in INITIAL-ARGS with names read from standard input; \
if R is unspecified, assume {}",
),
)
.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)),
)
@@ -980,7 +900,20 @@ fn do_xargs(args: &[&str]) -> Result<CommandResult, XargsError> {
verbose: matches.get_flag(options::VERBOSE),
};
let (max_args, max_lines, replace, delimiter) = normalize_options(&options, &matches);
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 action = match matches.get_many::<OsString>(options::COMMAND) {
Some(args) if args.len() > 0 => {
@@ -991,21 +924,36 @@ fn do_xargs(args: &[&str]) -> Result<CommandResult, XargsError> {
let env = std::env::vars_os().collect();
let mut limiters = LimiterCollection::new();
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));
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_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, 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, options.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();
@@ -1022,16 +970,7 @@ fn do_xargs(args: &[&str]) -> Result<CommandResult, XargsError> {
Box::new(WhitespaceDelimitedArgumentReader::new(args_file))
};
let result = process_input(
builder_options,
args,
&InputProcessOptions::new(
options.exit_if_pass_char_limit,
max_args,
max_lines,
options.no_run_if_empty,
),
)?;
let result = process_input(builder_options, args, &options)?;
Ok(result)
}
-376
View File
@@ -441,18 +441,6 @@ 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]
@@ -466,18 +454,6 @@ 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() {
@@ -529,355 +505,3 @@ 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"));
}
-62
View File
@@ -139,16 +139,6 @@ 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.
@@ -158,28 +148,6 @@ 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]
@@ -534,33 +502,3 @@ 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"));
}
-4
View File
@@ -21,10 +21,6 @@ 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)
-4
View File
@@ -24,10 +24,6 @@ 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)