mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 03:27:44 +00:00
Merge branch 'main' into dd-seconds-precision-3
This commit is contained in:
commit
59d34ce667
47 changed files with 1068 additions and 649 deletions
129
.github/workflows/CICD.yml
vendored
129
.github/workflows/CICD.yml
vendored
|
@ -37,8 +37,6 @@ jobs:
|
||||||
## ToDO: [2021-11-10; rivy] 'Style/deps' needs more informative output and better integration of results into the GHA dashboard
|
## ToDO: [2021-11-10; rivy] 'Style/deps' needs more informative output and better integration of results into the GHA dashboard
|
||||||
name: Style/deps
|
name: Style/deps
|
||||||
runs-on: ${{ matrix.job.os }}
|
runs-on: ${{ matrix.job.os }}
|
||||||
# env:
|
|
||||||
# STYLE_FAIL_ON_FAULT: false # overrides workflow default
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
|
@ -73,7 +71,6 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
rustup toolchain install nightly --no-self-update --profile minimal
|
rustup toolchain install nightly --no-self-update --profile minimal
|
||||||
rustup default nightly
|
rustup default nightly
|
||||||
- uses: Swatinem/rust-cache@v2
|
|
||||||
- name: Install `cargo-udeps`
|
- name: Install `cargo-udeps`
|
||||||
run: cargo install cargo-udeps
|
run: cargo install cargo-udeps
|
||||||
env:
|
env:
|
||||||
|
@ -93,8 +90,6 @@ jobs:
|
||||||
style_format:
|
style_format:
|
||||||
name: Style/format
|
name: Style/format
|
||||||
runs-on: ${{ matrix.job.os }}
|
runs-on: ${{ matrix.job.os }}
|
||||||
# env:
|
|
||||||
# STYLE_FAIL_ON_FAULT: false # overrides workflow default
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
|
@ -124,7 +119,6 @@ jobs:
|
||||||
## Install `rust` toolchain
|
## Install `rust` toolchain
|
||||||
rustup toolchain install stable --no-self-update -c rustfmt --profile minimal
|
rustup toolchain install stable --no-self-update -c rustfmt --profile minimal
|
||||||
rustup default stable
|
rustup default stable
|
||||||
- uses: Swatinem/rust-cache@v2
|
|
||||||
- name: "`cargo fmt` testing"
|
- name: "`cargo fmt` testing"
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
|
@ -151,7 +145,7 @@ jobs:
|
||||||
- name: Install `cargo-fuzz`
|
- name: Install `cargo-fuzz`
|
||||||
run: cargo install cargo-fuzz
|
run: cargo install cargo-fuzz
|
||||||
- name: Run fuzz_date for XX seconds
|
- name: Run fuzz_date for XX seconds
|
||||||
# TODO: fix the issues
|
# TODO: fix https://github.com/uutils/coreutils/issues/4494
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
|
@ -159,8 +153,6 @@ jobs:
|
||||||
cd fuzz
|
cd fuzz
|
||||||
cargo +nightly fuzz run fuzz_date -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0
|
cargo +nightly fuzz run fuzz_date -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0
|
||||||
- name: Run fuzz_parse_glob for XX seconds
|
- name: Run fuzz_parse_glob for XX seconds
|
||||||
# TODO: fix the issues
|
|
||||||
continue-on-error: true
|
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
## Run it
|
## Run it
|
||||||
|
@ -173,8 +165,6 @@ jobs:
|
||||||
cd fuzz
|
cd fuzz
|
||||||
cargo +nightly fuzz run fuzz_parse_size -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0
|
cargo +nightly fuzz run fuzz_parse_size -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0
|
||||||
- name: Run fuzz_parse_time for XX seconds
|
- name: Run fuzz_parse_time for XX seconds
|
||||||
# TODO: fix the issues
|
|
||||||
continue-on-error: true
|
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
## Run it
|
## Run it
|
||||||
|
@ -184,8 +174,9 @@ jobs:
|
||||||
style_lint:
|
style_lint:
|
||||||
name: Style/lint
|
name: Style/lint
|
||||||
runs-on: ${{ matrix.job.os }}
|
runs-on: ${{ matrix.job.os }}
|
||||||
# env:
|
env:
|
||||||
# STYLE_FAIL_ON_FAULT: false # overrides workflow default
|
SCCACHE_GHA_ENABLED: "true"
|
||||||
|
RUSTC_WRAPPER: "sccache"
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
|
@ -195,6 +186,11 @@ jobs:
|
||||||
- { os: windows-latest , features: feat_os_windows }
|
- { os: windows-latest , features: feat_os_windows }
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
- name: Run sccache-cache
|
||||||
|
uses: mozilla-actions/sccache-action@v0.0.2
|
||||||
|
with:
|
||||||
|
version: "v0.4.0-pre.11"
|
||||||
- name: Initialize workflow variables
|
- name: Initialize workflow variables
|
||||||
id: vars
|
id: vars
|
||||||
shell: bash
|
shell: bash
|
||||||
|
@ -229,7 +225,6 @@ jobs:
|
||||||
## Install `rust` toolchain
|
## Install `rust` toolchain
|
||||||
rustup toolchain install stable --no-self-update -c clippy --profile minimal
|
rustup toolchain install stable --no-self-update -c clippy --profile minimal
|
||||||
rustup default stable
|
rustup default stable
|
||||||
- uses: Swatinem/rust-cache@v2
|
|
||||||
- name: "`cargo clippy` lint testing"
|
- name: "`cargo clippy` lint testing"
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
|
@ -244,15 +239,12 @@ jobs:
|
||||||
style_spellcheck:
|
style_spellcheck:
|
||||||
name: Style/spelling
|
name: Style/spelling
|
||||||
runs-on: ${{ matrix.job.os }}
|
runs-on: ${{ matrix.job.os }}
|
||||||
# env:
|
|
||||||
# STYLE_FAIL_ON_FAULT: false # overrides workflow default
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
job:
|
job:
|
||||||
- { os: ubuntu-latest , features: feat_os_unix }
|
- { os: ubuntu-latest , features: feat_os_unix }
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: Swatinem/rust-cache@v2
|
|
||||||
- name: Initialize workflow variables
|
- name: Initialize workflow variables
|
||||||
id: vars
|
id: vars
|
||||||
shell: bash
|
shell: bash
|
||||||
|
@ -292,6 +284,9 @@ jobs:
|
||||||
doc_warnings:
|
doc_warnings:
|
||||||
name: Documentation/warnings
|
name: Documentation/warnings
|
||||||
runs-on: ${{ matrix.job.os }}
|
runs-on: ${{ matrix.job.os }}
|
||||||
|
env:
|
||||||
|
SCCACHE_GHA_ENABLED: "true"
|
||||||
|
RUSTC_WRAPPER: "sccache"
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
|
@ -304,6 +299,11 @@ jobs:
|
||||||
# - { os: windows-latest , features: feat_os_windows }
|
# - { os: windows-latest , features: feat_os_windows }
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
- name: Run sccache-cache
|
||||||
|
uses: mozilla-actions/sccache-action@v0.0.2
|
||||||
|
with:
|
||||||
|
version: "v0.4.0-pre.11"
|
||||||
- name: Initialize workflow variables
|
- name: Initialize workflow variables
|
||||||
id: vars
|
id: vars
|
||||||
shell: bash
|
shell: bash
|
||||||
|
@ -331,7 +331,6 @@ jobs:
|
||||||
## Install `rust` toolchain
|
## Install `rust` toolchain
|
||||||
rustup toolchain install stable --no-self-update -c clippy --profile minimal
|
rustup toolchain install stable --no-self-update -c clippy --profile minimal
|
||||||
rustup default stable
|
rustup default stable
|
||||||
- uses: Swatinem/rust-cache@v2
|
|
||||||
- name: "`cargo doc` with warnings"
|
- name: "`cargo doc` with warnings"
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
|
@ -347,12 +346,20 @@ jobs:
|
||||||
min_version:
|
min_version:
|
||||||
name: MinRustV # Minimum supported rust version (aka, MinSRV or MSRV)
|
name: MinRustV # Minimum supported rust version (aka, MinSRV or MSRV)
|
||||||
runs-on: ${{ matrix.job.os }}
|
runs-on: ${{ matrix.job.os }}
|
||||||
|
env:
|
||||||
|
SCCACHE_GHA_ENABLED: "true"
|
||||||
|
RUSTC_WRAPPER: "sccache"
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
job:
|
job:
|
||||||
- { os: ubuntu-latest , features: feat_os_unix }
|
- { os: ubuntu-latest , features: feat_os_unix }
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
- name: Run sccache-cache
|
||||||
|
uses: mozilla-actions/sccache-action@v0.0.2
|
||||||
|
with:
|
||||||
|
version: "v0.4.0-pre.11"
|
||||||
- name: Initialize workflow variables
|
- name: Initialize workflow variables
|
||||||
id: vars
|
id: vars
|
||||||
shell: bash
|
shell: bash
|
||||||
|
@ -369,7 +376,6 @@ jobs:
|
||||||
## Install `rust` toolchain (v${{ env.RUST_MIN_SRV }})
|
## Install `rust` toolchain (v${{ env.RUST_MIN_SRV }})
|
||||||
rustup toolchain install --no-self-update ${{ env.RUST_MIN_SRV }} --profile minimal
|
rustup toolchain install --no-self-update ${{ env.RUST_MIN_SRV }} --profile minimal
|
||||||
rustup default ${{ env.RUST_MIN_SRV }}
|
rustup default ${{ env.RUST_MIN_SRV }}
|
||||||
- uses: Swatinem/rust-cache@v2
|
|
||||||
- name: Confirm MinSRV compatible 'Cargo.lock'
|
- name: Confirm MinSRV compatible 'Cargo.lock'
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
|
@ -424,7 +430,6 @@ jobs:
|
||||||
## Install `rust` toolchain
|
## Install `rust` toolchain
|
||||||
rustup toolchain install stable --no-self-update --profile minimal
|
rustup toolchain install stable --no-self-update --profile minimal
|
||||||
rustup default stable
|
rustup default stable
|
||||||
- uses: Swatinem/rust-cache@v2
|
|
||||||
- name: "`cargo update` testing"
|
- name: "`cargo update` testing"
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
|
@ -436,6 +441,9 @@ jobs:
|
||||||
name: Build/Makefile
|
name: Build/Makefile
|
||||||
needs: [ min_version, deps ]
|
needs: [ min_version, deps ]
|
||||||
runs-on: ${{ matrix.job.os }}
|
runs-on: ${{ matrix.job.os }}
|
||||||
|
env:
|
||||||
|
SCCACHE_GHA_ENABLED: "true"
|
||||||
|
RUSTC_WRAPPER: "sccache"
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
|
@ -443,12 +451,16 @@ jobs:
|
||||||
- { os: ubuntu-latest , features: feat_os_unix }
|
- { os: ubuntu-latest , features: feat_os_unix }
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
- name: Run sccache-cache
|
||||||
|
uses: mozilla-actions/sccache-action@v0.0.2
|
||||||
|
with:
|
||||||
|
version: "v0.4.0-pre.11"
|
||||||
- name: Install `rust` toolchain
|
- name: Install `rust` toolchain
|
||||||
run: |
|
run: |
|
||||||
## Install `rust` toolchain
|
## Install `rust` toolchain
|
||||||
rustup toolchain install stable --no-self-update --profile minimal
|
rustup toolchain install stable --no-self-update --profile minimal
|
||||||
rustup default stable
|
rustup default stable
|
||||||
- uses: Swatinem/rust-cache@v2
|
|
||||||
- name: "`make build`"
|
- name: "`make build`"
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
|
@ -470,12 +482,14 @@ jobs:
|
||||||
env:
|
env:
|
||||||
RUST_BACKTRACE: "1"
|
RUST_BACKTRACE: "1"
|
||||||
|
|
||||||
|
|
||||||
build_rust_stable:
|
build_rust_stable:
|
||||||
name: Build/stable
|
name: Build/stable
|
||||||
needs: [ min_version, deps ]
|
needs: [ min_version, deps ]
|
||||||
runs-on: ${{ matrix.job.os }}
|
runs-on: ${{ matrix.job.os }}
|
||||||
timeout-minutes: 90
|
timeout-minutes: 90
|
||||||
|
env:
|
||||||
|
SCCACHE_GHA_ENABLED: "true"
|
||||||
|
RUSTC_WRAPPER: "sccache"
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
|
@ -485,12 +499,16 @@ jobs:
|
||||||
- { os: windows-latest , features: feat_os_windows }
|
- { os: windows-latest , features: feat_os_windows }
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
- name: Run sccache-cache
|
||||||
|
uses: mozilla-actions/sccache-action@v0.0.2
|
||||||
|
with:
|
||||||
|
version: "v0.4.0-pre.11"
|
||||||
- name: Install `rust` toolchain
|
- name: Install `rust` toolchain
|
||||||
run: |
|
run: |
|
||||||
## Install `rust` toolchain
|
## Install `rust` toolchain
|
||||||
rustup toolchain install stable --no-self-update --profile minimal
|
rustup toolchain install stable --no-self-update --profile minimal
|
||||||
rustup default stable
|
rustup default stable
|
||||||
- uses: Swatinem/rust-cache@v2
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: cargo test ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }}
|
run: cargo test ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }}
|
||||||
env:
|
env:
|
||||||
|
@ -501,6 +519,9 @@ jobs:
|
||||||
needs: [ min_version, deps ]
|
needs: [ min_version, deps ]
|
||||||
runs-on: ${{ matrix.job.os }}
|
runs-on: ${{ matrix.job.os }}
|
||||||
timeout-minutes: 90
|
timeout-minutes: 90
|
||||||
|
env:
|
||||||
|
SCCACHE_GHA_ENABLED: "true"
|
||||||
|
RUSTC_WRAPPER: "sccache"
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
|
@ -510,12 +531,16 @@ jobs:
|
||||||
- { os: windows-latest , features: feat_os_windows }
|
- { os: windows-latest , features: feat_os_windows }
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
- name: Run sccache-cache
|
||||||
|
uses: mozilla-actions/sccache-action@v0.0.2
|
||||||
|
with:
|
||||||
|
version: "v0.4.0-pre.11"
|
||||||
- name: Install `rust` toolchain
|
- name: Install `rust` toolchain
|
||||||
run: |
|
run: |
|
||||||
## Install `rust` toolchain
|
## Install `rust` toolchain
|
||||||
rustup toolchain install nightly --no-self-update --profile minimal
|
rustup toolchain install nightly --no-self-update --profile minimal
|
||||||
rustup default nightly
|
rustup default nightly
|
||||||
- uses: Swatinem/rust-cache@v2
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: cargo test ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }}
|
run: cargo test ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }}
|
||||||
env:
|
env:
|
||||||
|
@ -525,6 +550,9 @@ jobs:
|
||||||
name: Binary sizes
|
name: Binary sizes
|
||||||
needs: [ min_version, deps ]
|
needs: [ min_version, deps ]
|
||||||
runs-on: ${{ matrix.job.os }}
|
runs-on: ${{ matrix.job.os }}
|
||||||
|
env:
|
||||||
|
SCCACHE_GHA_ENABLED: "true"
|
||||||
|
RUSTC_WRAPPER: "sccache"
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
|
@ -532,6 +560,11 @@ jobs:
|
||||||
- { os: ubuntu-latest , features: feat_os_unix }
|
- { os: ubuntu-latest , features: feat_os_unix }
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
- name: Run sccache-cache
|
||||||
|
uses: mozilla-actions/sccache-action@v0.0.2
|
||||||
|
with:
|
||||||
|
version: "v0.4.0-pre.11"
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
|
@ -543,7 +576,6 @@ jobs:
|
||||||
## Install `rust` toolchain
|
## Install `rust` toolchain
|
||||||
rustup toolchain install stable --no-self-update --profile minimal
|
rustup toolchain install stable --no-self-update --profile minimal
|
||||||
rustup default stable
|
rustup default stable
|
||||||
- uses: Swatinem/rust-cache@v2
|
|
||||||
- name: "`make install`"
|
- name: "`make install`"
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
|
@ -579,6 +611,8 @@ jobs:
|
||||||
timeout-minutes: 90
|
timeout-minutes: 90
|
||||||
env:
|
env:
|
||||||
DOCKER_OPTS: '--volume /etc/passwd:/etc/passwd --volume /etc/group:/etc/group'
|
DOCKER_OPTS: '--volume /etc/passwd:/etc/passwd --volume /etc/group:/etc/group'
|
||||||
|
SCCACHE_GHA_ENABLED: "true"
|
||||||
|
RUSTC_WRAPPER: "sccache"
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
|
@ -605,6 +639,11 @@ jobs:
|
||||||
- { os: windows-latest , target: x86_64-pc-windows-msvc , features: feat_os_windows }
|
- { os: windows-latest , target: x86_64-pc-windows-msvc , features: feat_os_windows }
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
- name: Run sccache-cache
|
||||||
|
uses: mozilla-actions/sccache-action@v0.0.2
|
||||||
|
with:
|
||||||
|
version: "v0.4.0-pre.11"
|
||||||
- name: Initialize workflow variables
|
- name: Initialize workflow variables
|
||||||
id: vars
|
id: vars
|
||||||
shell: bash
|
shell: bash
|
||||||
|
@ -732,9 +771,6 @@ jobs:
|
||||||
## rust toolchain ~ install
|
## rust toolchain ~ install
|
||||||
rustup toolchain install --no-self-update ${{ env.RUST_MIN_SRV }} -t ${{ matrix.job.target }} --profile minimal
|
rustup toolchain install --no-self-update ${{ env.RUST_MIN_SRV }} -t ${{ matrix.job.target }} --profile minimal
|
||||||
rustup default ${{ env.RUST_MIN_SRV }}
|
rustup default ${{ env.RUST_MIN_SRV }}
|
||||||
- uses: Swatinem/rust-cache@v2
|
|
||||||
with:
|
|
||||||
key: ${{ matrix.job.os }}-${{ matrix.job.target }}
|
|
||||||
- name: Initialize toolchain-dependent workflow variables
|
- name: Initialize toolchain-dependent workflow variables
|
||||||
id: dep_vars
|
id: dep_vars
|
||||||
shell: bash
|
shell: bash
|
||||||
|
@ -844,6 +880,9 @@ jobs:
|
||||||
name: Tests/BusyBox test suite
|
name: Tests/BusyBox test suite
|
||||||
needs: [ min_version, deps ]
|
needs: [ min_version, deps ]
|
||||||
runs-on: ${{ matrix.job.os }}
|
runs-on: ${{ matrix.job.os }}
|
||||||
|
env:
|
||||||
|
SCCACHE_GHA_ENABLED: "true"
|
||||||
|
RUSTC_WRAPPER: "sccache"
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
|
@ -858,6 +897,10 @@ jobs:
|
||||||
echo "TEST_SUMMARY_FILE=busybox-result.json" >> $GITHUB_OUTPUT
|
echo "TEST_SUMMARY_FILE=busybox-result.json" >> $GITHUB_OUTPUT
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
- name: Run sccache-cache
|
||||||
|
uses: mozilla-actions/sccache-action@v0.0.2
|
||||||
|
with:
|
||||||
|
version: "v0.4.0-pre.11"
|
||||||
- name: Install/setup prerequisites
|
- name: Install/setup prerequisites
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
|
@ -917,6 +960,9 @@ jobs:
|
||||||
name: Tests/Toybox test suite
|
name: Tests/Toybox test suite
|
||||||
needs: [ min_version, deps ]
|
needs: [ min_version, deps ]
|
||||||
runs-on: ${{ matrix.job.os }}
|
runs-on: ${{ matrix.job.os }}
|
||||||
|
env:
|
||||||
|
SCCACHE_GHA_ENABLED: "true"
|
||||||
|
RUSTC_WRAPPER: "sccache"
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
|
@ -933,6 +979,10 @@ jobs:
|
||||||
outputs TEST_SUMMARY_FILE
|
outputs TEST_SUMMARY_FILE
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
- name: Run sccache-cache
|
||||||
|
uses: mozilla-actions/sccache-action@v0.0.2
|
||||||
|
with:
|
||||||
|
version: "v0.4.0-pre.11"
|
||||||
- name: rust toolchain ~ install
|
- name: rust toolchain ~ install
|
||||||
run: |
|
run: |
|
||||||
## rust toolchain ~ install
|
## rust toolchain ~ install
|
||||||
|
@ -1010,8 +1060,15 @@ jobs:
|
||||||
arch: [x86] # , arm64-v8a
|
arch: [x86] # , arm64-v8a
|
||||||
env:
|
env:
|
||||||
TERMUX: v0.118.0
|
TERMUX: v0.118.0
|
||||||
|
SCCACHE_GHA_ENABLED: "true"
|
||||||
|
RUSTC_WRAPPER: "sccache"
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
- name: Run sccache-cache
|
||||||
|
uses: mozilla-actions/sccache-action@v0.0.2
|
||||||
|
with:
|
||||||
|
version: "v0.4.0-pre.11"
|
||||||
- name: AVD cache
|
- name: AVD cache
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v3
|
||||||
id: avd-cache
|
id: avd-cache
|
||||||
|
@ -1065,9 +1122,15 @@ jobs:
|
||||||
- { os: macos-12 , features: unix } ## GHA MacOS-11.0 VM won't have VirtualBox; refs: <https://github.com/actions/virtual-environments/issues/4060> , <https://github.com/actions/virtual-environments/pull/4010>
|
- { os: macos-12 , features: unix } ## GHA MacOS-11.0 VM won't have VirtualBox; refs: <https://github.com/actions/virtual-environments/issues/4060> , <https://github.com/actions/virtual-environments/pull/4010>
|
||||||
env:
|
env:
|
||||||
mem: 4096
|
mem: 4096
|
||||||
|
SCCACHE_GHA_ENABLED: "true"
|
||||||
|
RUSTC_WRAPPER: "sccache"
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
- name: Run sccache-cache
|
||||||
|
uses: mozilla-actions/sccache-action@v0.0.2
|
||||||
|
with:
|
||||||
|
version: "v0.4.0-pre.11"
|
||||||
- name: Prepare, build and test
|
- name: Prepare, build and test
|
||||||
## spell-checker:ignore (ToDO) sshfs usesh vmactions
|
## spell-checker:ignore (ToDO) sshfs usesh vmactions
|
||||||
uses: vmactions/freebsd-vm@v0.3.0
|
uses: vmactions/freebsd-vm@v0.3.0
|
||||||
|
@ -1132,6 +1195,9 @@ jobs:
|
||||||
name: Code Coverage
|
name: Code Coverage
|
||||||
runs-on: ${{ matrix.job.os }}
|
runs-on: ${{ matrix.job.os }}
|
||||||
timeout-minutes: 90
|
timeout-minutes: 90
|
||||||
|
env:
|
||||||
|
SCCACHE_GHA_ENABLED: "true"
|
||||||
|
RUSTC_WRAPPER: "sccache"
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
|
@ -1141,6 +1207,11 @@ jobs:
|
||||||
- { os: windows-latest , features: windows }
|
- { os: windows-latest , features: windows }
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
- name: Run sccache-cache
|
||||||
|
uses: mozilla-actions/sccache-action@v0.0.2
|
||||||
|
with:
|
||||||
|
version: "v0.4.0-pre.11"
|
||||||
# - name: Reattach HEAD ## may be needed for accurate code coverage info
|
# - name: Reattach HEAD ## may be needed for accurate code coverage info
|
||||||
# run: git checkout ${{ github.head_ref }}
|
# run: git checkout ${{ github.head_ref }}
|
||||||
- name: Initialize workflow variables
|
- name: Initialize workflow variables
|
||||||
|
@ -1194,7 +1265,6 @@ jobs:
|
||||||
## rust toolchain ~ install
|
## rust toolchain ~ install
|
||||||
rustup toolchain install ${{ steps.vars.outputs.TOOLCHAIN }} --no-self-update --profile minimal
|
rustup toolchain install ${{ steps.vars.outputs.TOOLCHAIN }} --no-self-update --profile minimal
|
||||||
rustup default ${{ steps.vars.outputs.TOOLCHAIN }}
|
rustup default ${{ steps.vars.outputs.TOOLCHAIN }}
|
||||||
- uses: Swatinem/rust-cache@v2
|
|
||||||
- name: Initialize toolchain-dependent workflow variables
|
- name: Initialize toolchain-dependent workflow variables
|
||||||
id: dep_vars
|
id: dep_vars
|
||||||
shell: bash
|
shell: bash
|
||||||
|
@ -1260,3 +1330,4 @@ jobs:
|
||||||
flags: ${{ steps.vars.outputs.CODECOV_FLAGS }}
|
flags: ${{ steps.vars.outputs.CODECOV_FLAGS }}
|
||||||
name: codecov-umbrella
|
name: codecov-umbrella
|
||||||
fail_ci_if_error: false
|
fail_ci_if_error: false
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ exacl
|
||||||
filetime
|
filetime
|
||||||
formatteriteminfo
|
formatteriteminfo
|
||||||
fsext
|
fsext
|
||||||
|
fundu
|
||||||
getopts
|
getopts
|
||||||
getrandom
|
getrandom
|
||||||
globset
|
globset
|
||||||
|
|
288
CONTRIBUTING.md
288
CONTRIBUTING.md
|
@ -1,20 +1,12 @@
|
||||||
|
<!-- spell-checker:ignore reimplementing toybox RUNTEST -->
|
||||||
|
|
||||||
# Contributing to coreutils
|
# Contributing to coreutils
|
||||||
|
|
||||||
Contributions are very welcome, and should target Rust's main branch until the
|
Contributions are very welcome via Pull Requests. If you don't know where to
|
||||||
standard libraries are stabilized. You may *claim* an item on the to-do list by
|
start, take a look at the
|
||||||
following these steps:
|
[`good-first-issues`](https://github.com/uutils/coreutils/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22).
|
||||||
|
If you have any questions, feel free to ask them in the issues or on
|
||||||
1. Open an issue named "Implement [the utility of your choice]", e.g. "Implement
|
[Discord](https://discord.gg/wQVJbvJ).
|
||||||
ls".
|
|
||||||
1. State that you are working on this utility.
|
|
||||||
1. Develop the utility.
|
|
||||||
1. Add integration tests.
|
|
||||||
1. Add the reference to your utility into Cargo.toml and Makefile.
|
|
||||||
1. Remove utility from the to-do list in the README.
|
|
||||||
1. Submit a pull request and close the issue.
|
|
||||||
|
|
||||||
The steps above imply that, before starting to work on a utility, you should
|
|
||||||
search the issues to make sure no one else is working on it.
|
|
||||||
|
|
||||||
## Best practices
|
## Best practices
|
||||||
|
|
||||||
|
@ -38,36 +30,240 @@ search the issues to make sure no one else is working on it.
|
||||||
|
|
||||||
## Platforms
|
## Platforms
|
||||||
|
|
||||||
We take pride in supporting many operating systems and architectures.
|
We take pride in supporting many operating systems and architectures. Any code
|
||||||
|
you contribute must at least compile without warnings for all platforms in the
|
||||||
|
CI. However, you can use `#[cfg(...)]` attributes to create platform dependent features.
|
||||||
|
|
||||||
**Tip:**
|
**Tip:** For Windows, Microsoft provides some images (VMWare, Hyper-V,
|
||||||
For Windows, Microsoft provides some images (VMWare, Hyper-V, VirtualBox and Parallels)
|
VirtualBox and Parallels) for development:
|
||||||
for development:
|
|
||||||
<https://developer.microsoft.com/windows/downloads/virtual-machines/>
|
<https://developer.microsoft.com/windows/downloads/virtual-machines/>
|
||||||
|
|
||||||
|
## Tools
|
||||||
|
|
||||||
|
We have an extensive CI that will check your code before it can be merged. This
|
||||||
|
section explains how to run those checks locally to avoid waiting for the CI.
|
||||||
|
|
||||||
|
### pre-commit hooks
|
||||||
|
|
||||||
|
A configuration for `pre-commit` is provided in the repository. It allows
|
||||||
|
automatically checking every git commit you make to ensure it compiles, and
|
||||||
|
passes `clippy` and `rustfmt` without warnings.
|
||||||
|
|
||||||
|
To use the provided hook:
|
||||||
|
|
||||||
|
1. [Install `pre-commit`](https://pre-commit.com/#install)
|
||||||
|
1. Run `pre-commit install` while in the repository directory
|
||||||
|
|
||||||
|
Your git commits will then automatically be checked. If a check fails, an error
|
||||||
|
message will explain why, and your commit will be canceled. You can then make
|
||||||
|
the suggested changes, and run `git commit ...` again.
|
||||||
|
|
||||||
|
### clippy
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cargo clippy --all-targets --all-features
|
||||||
|
```
|
||||||
|
|
||||||
|
The `msrv` key in the clippy configuration file `clippy.toml` is used to disable
|
||||||
|
lints pertaining to newer features by specifying the minimum supported Rust
|
||||||
|
version (MSRV).
|
||||||
|
|
||||||
|
### rustfmt
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cargo fmt --all
|
||||||
|
```
|
||||||
|
|
||||||
|
### cargo-deny
|
||||||
|
|
||||||
|
This project uses [cargo-deny](https://github.com/EmbarkStudios/cargo-deny/) to
|
||||||
|
detect duplicate dependencies, checks licenses, etc. To run it locally, first
|
||||||
|
install it and then run with:
|
||||||
|
|
||||||
|
```
|
||||||
|
cargo deny --all-features check all
|
||||||
|
```
|
||||||
|
|
||||||
|
### Markdown linter
|
||||||
|
|
||||||
|
We use [markdownlint](https://github.com/DavidAnson/markdownlint) to lint the
|
||||||
|
Markdown files in the repository.
|
||||||
|
|
||||||
|
### Spell checker
|
||||||
|
|
||||||
|
We use `cspell` as spell checker for all files in the project. If you are using
|
||||||
|
VS Code, you can install the
|
||||||
|
[code spell checker](https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker)
|
||||||
|
extension to enable spell checking within your editor. Otherwise, you can
|
||||||
|
install [cspell](https://cspell.org/) separately.
|
||||||
|
|
||||||
|
If you want to make the spell checker ignore a word, you can add
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// spell-checker:ignore word_to_ignore
|
||||||
|
```
|
||||||
|
|
||||||
|
at the top of the file.
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
Testing can be done using either Cargo or `make`.
|
||||||
|
|
||||||
|
### Testing with Cargo
|
||||||
|
|
||||||
|
Just like with building, we follow the standard procedure for testing using
|
||||||
|
Cargo:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cargo test
|
||||||
|
```
|
||||||
|
|
||||||
|
By default, `cargo test` only runs the common programs. To run also platform
|
||||||
|
specific tests, run:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cargo test --features unix
|
||||||
|
```
|
||||||
|
|
||||||
|
If you would prefer to test a select few utilities:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cargo test --features "chmod mv tail" --no-default-features
|
||||||
|
```
|
||||||
|
|
||||||
|
If you also want to test the core utilities:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cargo test -p uucore -p coreutils
|
||||||
|
```
|
||||||
|
|
||||||
|
To debug:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
gdb --args target/debug/coreutils ls
|
||||||
|
(gdb) b ls.rs:79
|
||||||
|
(gdb) run
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing with GNU Make
|
||||||
|
|
||||||
|
To simply test all available utilities:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
make test
|
||||||
|
```
|
||||||
|
|
||||||
|
To test all but a few of the available utilities:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
make SKIP_UTILS='UTILITY_1 UTILITY_2' test
|
||||||
|
```
|
||||||
|
|
||||||
|
To test only a few of the available utilities:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
make UTILS='UTILITY_1 UTILITY_2' test
|
||||||
|
```
|
||||||
|
|
||||||
|
To include tests for unimplemented behavior:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
make UTILS='UTILITY_1 UTILITY_2' SPEC=y test
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run Busybox Tests
|
||||||
|
|
||||||
|
This testing functionality is only available on *nix operating systems and
|
||||||
|
requires `make`.
|
||||||
|
|
||||||
|
To run busybox tests for all utilities for which busybox has tests
|
||||||
|
|
||||||
|
```shell
|
||||||
|
make busytest
|
||||||
|
```
|
||||||
|
|
||||||
|
To run busybox tests for a few of the available utilities
|
||||||
|
|
||||||
|
```shell
|
||||||
|
make UTILS='UTILITY_1 UTILITY_2' busytest
|
||||||
|
```
|
||||||
|
|
||||||
|
To pass an argument like "-v" to the busybox test runtime
|
||||||
|
|
||||||
|
```shell
|
||||||
|
make UTILS='UTILITY_1 UTILITY_2' RUNTEST_ARGS='-v' busytest
|
||||||
|
```
|
||||||
|
|
||||||
|
### Comparing with GNU
|
||||||
|
|
||||||
|
To run uutils against the GNU test suite locally, run the following commands:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
bash util/build-gnu.sh
|
||||||
|
bash util/run-gnu-test.sh
|
||||||
|
# To run a single test:
|
||||||
|
bash util/run-gnu-test.sh tests/touch/not-owner.sh # for example
|
||||||
|
# To run several tests:
|
||||||
|
bash util/run-gnu-test.sh tests/touch/not-owner.sh tests/rm/no-give-up.sh # for example
|
||||||
|
# If this is a perl (.pl) test, to run in debug:
|
||||||
|
DEBUG=1 bash util/run-gnu-test.sh tests/misc/sm3sum.pl
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that it relies on individual utilities (not the multicall binary).
|
||||||
|
|
||||||
|
### Improving the GNU compatibility
|
||||||
|
|
||||||
|
The Python script `./util/remaining-gnu-error.py` shows the list of failing
|
||||||
|
tests in the CI.
|
||||||
|
|
||||||
|
To improve the GNU compatibility, the following process is recommended:
|
||||||
|
|
||||||
|
1. Identify a test (the smaller, the better) on a program that you understand or
|
||||||
|
is easy to understand. You can use the `./util/remaining-gnu-error.py` script
|
||||||
|
to help with this decision.
|
||||||
|
1. Build both the GNU and Rust coreutils using: `bash util/build-gnu.sh`
|
||||||
|
1. Run the test with `bash util/run-gnu-test.sh <your test>`
|
||||||
|
1. Start to modify `<your test>` to understand what is wrong. Examples:
|
||||||
|
1. Add `set -v` to have the bash verbose mode
|
||||||
|
1. Add `echo $?` where needed
|
||||||
|
1. When the variable `fail` is used in the test, `echo $fail` to see when the
|
||||||
|
test started to fail
|
||||||
|
1. Bump the content of the output (ex: `cat err`)
|
||||||
|
1. ...
|
||||||
|
1. Or, if the test is simple, extract the relevant information to create a new
|
||||||
|
test case running both GNU & Rust implementation
|
||||||
|
1. Start to modify the Rust implementation to match the expected behavior
|
||||||
|
1. Add a test to make sure that we don't regress (our test suite is super quick)
|
||||||
|
|
||||||
## Commit messages
|
## Commit messages
|
||||||
|
|
||||||
To help the project maintainers review pull requests from contributors across
|
To help the project maintainers review pull requests from contributors across
|
||||||
numerous utilities, the team has settled on conventions for commit messages.
|
numerous utilities, the team has settled on conventions for commit messages.
|
||||||
|
|
||||||
From <http://git-scm.com/book/ch5-2.html>:
|
From <https://git-scm.com/book/ch5-2.html>:
|
||||||
|
|
||||||
```
|
```
|
||||||
Short (50 chars or less) summary of changes
|
Capitalized, short (50 chars or less) summary
|
||||||
|
|
||||||
More detailed explanatory text, if necessary. Wrap it to about 72
|
More detailed explanatory text, if necessary. Wrap it to about 72
|
||||||
characters or so. In some contexts, the first line is treated as the
|
characters or so. In some contexts, the first line is treated as the
|
||||||
subject of an email and the rest of the text as the body. The blank
|
subject of an email and the rest of the text as the body. The blank
|
||||||
line separating the summary from the body is critical (unless you omit
|
line separating the summary from the body is critical (unless you omit
|
||||||
the body entirely); tools like rebase can get confused if you run the
|
the body entirely); tools like rebase will confuse you if you run the
|
||||||
two together.
|
two together.
|
||||||
|
|
||||||
|
Write your commit message in the imperative: "Fix bug" and not "Fixed bug"
|
||||||
|
or "Fixes bug." This convention matches up with commit messages generated
|
||||||
|
by commands like git merge and git revert.
|
||||||
|
|
||||||
Further paragraphs come after blank lines.
|
Further paragraphs come after blank lines.
|
||||||
|
|
||||||
- Bullet points are okay, too
|
- Bullet points are okay, too
|
||||||
|
|
||||||
- Typically a hyphen or asterisk is used for the bullet, preceded by a
|
- Typically a hyphen or asterisk is used for the bullet, followed by a
|
||||||
single space, with blank lines in between, but conventions vary here
|
single space, with blank lines in between, but conventions vary here
|
||||||
|
|
||||||
|
- Use a hanging indent
|
||||||
```
|
```
|
||||||
|
|
||||||
Furthermore, here are a few examples for a summary line:
|
Furthermore, here are a few examples for a summary line:
|
||||||
|
@ -103,15 +299,49 @@ uutils: add new utility
|
||||||
gitignore: add temporary files
|
gitignore: add temporary files
|
||||||
```
|
```
|
||||||
|
|
||||||
## cargo-deny
|
## Code coverage
|
||||||
|
|
||||||
This project uses [cargo-deny](https://github.com/EmbarkStudios/cargo-deny/) to
|
<!-- spell-checker:ignore (flags) Ccodegen Coverflow Cpanic Zinstrument Zpanic -->
|
||||||
detect duplicate dependencies, checks licenses, etc. To run it locally, first
|
|
||||||
install it and then run with:
|
|
||||||
|
|
||||||
|
Code coverage report can be generated using [grcov](https://github.com/mozilla/grcov).
|
||||||
|
|
||||||
|
### Using Nightly Rust
|
||||||
|
|
||||||
|
To generate [gcov-based](https://github.com/mozilla/grcov#example-how-to-generate-gcda-files-for-a-rust-project) coverage report
|
||||||
|
|
||||||
|
```shell
|
||||||
|
export CARGO_INCREMENTAL=0
|
||||||
|
export RUSTFLAGS="-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort"
|
||||||
|
export RUSTDOCFLAGS="-Cpanic=abort"
|
||||||
|
cargo build <options...> # e.g., --features feat_os_unix
|
||||||
|
cargo test <options...> # e.g., --features feat_os_unix test_pathchk
|
||||||
|
grcov . -s . --binary-path ./target/debug/ -t html --branch --ignore-not-existing --ignore build.rs --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?\#\[derive\()" -o ./target/debug/coverage/
|
||||||
|
# open target/debug/coverage/index.html in browser
|
||||||
```
|
```
|
||||||
cargo deny --all-features check all
|
|
||||||
```
|
if changes are not reflected in the report then run `cargo clean` and run the above commands.
|
||||||
|
|
||||||
|
### Using Stable Rust
|
||||||
|
|
||||||
|
If you are using stable version of Rust that doesn't enable code coverage instrumentation by default
|
||||||
|
then add `-Z-Zinstrument-coverage` flag to `RUSTFLAGS` env variable specified above.
|
||||||
|
|
||||||
|
|
||||||
|
## Other implementations
|
||||||
|
|
||||||
|
The Coreutils have different implementations, with different levels of completions:
|
||||||
|
|
||||||
|
* [GNU's](https://git.savannah.gnu.org/gitweb/?p=coreutils.git)
|
||||||
|
* [OpenBSD](https://github.com/openbsd/src/tree/master/bin)
|
||||||
|
* [Busybox](https://github.com/mirror/busybox/tree/master/coreutils)
|
||||||
|
* [Toybox (Android)](https://github.com/landley/toybox/tree/master/toys/posix)
|
||||||
|
* [V lang](https://github.com/vlang/coreutils)
|
||||||
|
* [SerenityOS](https://github.com/SerenityOS/serenity/tree/master/Userland/Utilities)
|
||||||
|
* [Initial Unix](https://github.com/dspinellis/unix-history-repo)
|
||||||
|
|
||||||
|
However, when reimplementing the tools/options in Rust, don't read their source codes
|
||||||
|
when they are using reciprocal licenses (ex: GNU GPL, GNU LGPL, etc).
|
||||||
|
|
||||||
|
|
||||||
## Licensing
|
## Licensing
|
||||||
|
|
||||||
|
|
67
Cargo.lock
generated
67
Cargo.lock
generated
|
@ -89,9 +89,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bindgen"
|
name = "bindgen"
|
||||||
version = "0.62.0"
|
version = "0.63.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c6720a8b7b2d39dd533285ed438d458f65b31b5c257e6ac7bb3d7e82844dd722"
|
checksum = "36d860121800b2a9a94f9b5604b332d5cffb234ce17609ea479d723dbc9d3885"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"cexpr",
|
"cexpr",
|
||||||
|
@ -870,9 +870,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fts-sys"
|
name = "fts-sys"
|
||||||
version = "0.2.3"
|
version = "0.2.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "32bd98333d10742c0b048272ebf4cb05336d415423b853961c92ccb398966a03"
|
checksum = "9a66c0a21e344f20c87b4ca12643cf4f40a7018f132c98d344e989b959f49dd1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bindgen",
|
"bindgen",
|
||||||
"libc",
|
"libc",
|
||||||
|
@ -880,9 +880,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fundu"
|
name = "fundu"
|
||||||
version = "0.3.0"
|
version = "0.4.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "925250bc259498d4008ee072bf16586083ab2c491aa4b06b3c4d0a6556cebd74"
|
checksum = "da58c38fe7b706cead98429d8a8535261addbe55fd531c7d7c7d770346464010"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures"
|
name = "futures"
|
||||||
|
@ -1139,12 +1139,6 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "io-lifetimes"
|
|
||||||
version = "0.7.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "59ce5ef949d49ee85593fc4d3f3f95ad61657076395cbbce23e2121fc5542074"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "io-lifetimes"
|
name = "io-lifetimes"
|
||||||
version = "1.0.5"
|
version = "1.0.5"
|
||||||
|
@ -1162,8 +1156,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "22e18b0a45d56fe973d6db23972bf5bc46f988a4a2385deac9cc29572f09daef"
|
checksum = "22e18b0a45d56fe973d6db23972bf5bc46f988a4a2385deac9cc29572f09daef"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hermit-abi 0.3.1",
|
"hermit-abi 0.3.1",
|
||||||
"io-lifetimes 1.0.5",
|
"io-lifetimes",
|
||||||
"rustix 0.36.8",
|
"rustix",
|
||||||
"windows-sys 0.45.0",
|
"windows-sys 0.45.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1257,12 +1251,6 @@ dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "linux-raw-sys"
|
|
||||||
version = "0.0.46"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d4d2456c373231a208ad294c33dc5bff30051eafd954cd4caae83a712b12854d"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
version = "0.1.4"
|
version = "0.1.4"
|
||||||
|
@ -1719,15 +1707,15 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "procfs"
|
name = "procfs"
|
||||||
version = "0.14.1"
|
version = "0.15.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2dfb6451c91904606a1abe93e83a8ec851f45827fa84273f256ade45dc095818"
|
checksum = "943ca7f9f29bab5844ecd8fdb3992c5969b6622bb9609b9502fef9b4310e3f1f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"hex",
|
"hex",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"rustix 0.35.13",
|
"rustix",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1921,20 +1909,6 @@ dependencies = [
|
||||||
"semver",
|
"semver",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rustix"
|
|
||||||
version = "0.35.13"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "727a1a6d65f786ec22df8a81ca3121107f235970dc1705ed681d3e6e8b9cd5f9"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags",
|
|
||||||
"errno",
|
|
||||||
"io-lifetimes 0.7.5",
|
|
||||||
"libc",
|
|
||||||
"linux-raw-sys 0.0.46",
|
|
||||||
"windows-sys 0.42.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "0.36.8"
|
version = "0.36.8"
|
||||||
|
@ -1943,9 +1917,9 @@ checksum = "f43abb88211988493c1abb44a70efa56ff0ce98f233b7b276146f1f3f7ba9644"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"errno",
|
"errno",
|
||||||
"io-lifetimes 1.0.5",
|
"io-lifetimes",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys 0.1.4",
|
"linux-raw-sys",
|
||||||
"windows-sys 0.45.0",
|
"windows-sys 0.45.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1992,9 +1966,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "selinux-sys"
|
name = "selinux-sys"
|
||||||
version = "0.6.1"
|
version = "0.6.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e4c02c5c6e2db8a78b3ffffc666f75fcda5bbd7068ba3c0f560e5504f4d88443"
|
checksum = "806d381649bb85347189d2350728817418138d11d738e2482cb644ec7f3c755d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bindgen",
|
"bindgen",
|
||||||
"cc",
|
"cc",
|
||||||
|
@ -2185,7 +2159,7 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"fastrand",
|
"fastrand",
|
||||||
"redox_syscall",
|
"redox_syscall",
|
||||||
"rustix 0.36.8",
|
"rustix",
|
||||||
"windows-sys 0.42.0",
|
"windows-sys 0.42.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -2209,12 +2183,12 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "terminal_size"
|
name = "terminal_size"
|
||||||
version = "0.2.2"
|
version = "0.2.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "40ca90c434fd12083d1a6bdcbe9f92a14f96c8a1ba600ba451734ac334521f7a"
|
checksum = "4c9afddd2cec1c0909f06b00ef33f94ab2cc0578c4a610aa208ddfec8aa2b43a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rustix 0.35.13",
|
"rustix",
|
||||||
"windows-sys 0.42.0",
|
"windows-sys 0.45.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -3041,6 +3015,7 @@ name = "uu_sleep"
|
||||||
version = "0.0.17"
|
version = "0.0.17"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
|
"fundu",
|
||||||
"uucore",
|
"uucore",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -282,7 +282,7 @@ filetime = "0.2"
|
||||||
fnv = "1.0.7"
|
fnv = "1.0.7"
|
||||||
fs_extra = "1.1.0"
|
fs_extra = "1.1.0"
|
||||||
fts-sys = "0.2"
|
fts-sys = "0.2"
|
||||||
fundu = "0.3.0"
|
fundu = "0.4.2"
|
||||||
gcd = "2.2"
|
gcd = "2.2"
|
||||||
glob = "0.3.0"
|
glob = "0.3.0"
|
||||||
half = "2.1"
|
half = "2.1"
|
||||||
|
@ -320,7 +320,7 @@ strum = "0.24.1"
|
||||||
strum_macros = "0.24.2"
|
strum_macros = "0.24.2"
|
||||||
tempfile = "3.4.0"
|
tempfile = "3.4.0"
|
||||||
term_grid = "0.1.5"
|
term_grid = "0.1.5"
|
||||||
terminal_size = "0.2.2"
|
terminal_size = "0.2.5"
|
||||||
textwrap = { version="0.16.0", features=["terminal_size"] }
|
textwrap = { version="0.16.0", features=["terminal_size"] }
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
time = { version="0.3" }
|
time = { version="0.3" }
|
||||||
|
@ -493,7 +493,7 @@ hex-literal = "0.3.1"
|
||||||
rstest = "0.16.0"
|
rstest = "0.16.0"
|
||||||
|
|
||||||
[target.'cfg(any(target_os = "linux", target_os = "android"))'.dev-dependencies]
|
[target.'cfg(any(target_os = "linux", target_os = "android"))'.dev-dependencies]
|
||||||
procfs = { version = "0.14", default-features = false }
|
procfs = { version = "0.15", default-features = false }
|
||||||
rlimit = "0.9.1"
|
rlimit = "0.9.1"
|
||||||
|
|
||||||
[target.'cfg(unix)'.dev-dependencies]
|
[target.'cfg(unix)'.dev-dependencies]
|
||||||
|
|
|
@ -1,68 +0,0 @@
|
||||||
# Documentation
|
|
||||||
|
|
||||||
The source of the documentation is available on:
|
|
||||||
|
|
||||||
<https://uutils.github.io/dev/coreutils/>
|
|
||||||
|
|
||||||
The documentation is updated everyday on this repository:
|
|
||||||
|
|
||||||
<https://github.com/uutils/uutils.github.io/>
|
|
||||||
|
|
||||||
## Running GNU tests
|
|
||||||
|
|
||||||
<!-- spell-checker:ignore gnulib -->
|
|
||||||
|
|
||||||
- Check out <https://github.com/coreutils/coreutils> next to your fork as gnu
|
|
||||||
- Check out <https://github.com/coreutils/gnulib> next to your fork as gnulib
|
|
||||||
- Rename the checkout of your fork to uutils
|
|
||||||
|
|
||||||
At the end you should have uutils, gnu and gnulib checked out next to each other.
|
|
||||||
|
|
||||||
- Run `cd uutils && ./util/build-gnu.sh && cd ..` to get everything ready (this may take a while)
|
|
||||||
- Finally, you can run tests with `bash uutils/util/run-gnu-test.sh <tests>`. Instead of `<tests>` insert the tests you want to run, e.g. `tests/misc/wc-proc.sh`.
|
|
||||||
|
|
||||||
## Code Coverage Report Generation
|
|
||||||
|
|
||||||
<!-- spell-checker:ignore (flags) Ccodegen Coverflow Cpanic Zinstrument Zpanic -->
|
|
||||||
|
|
||||||
Code coverage report can be generated using [grcov](https://github.com/mozilla/grcov).
|
|
||||||
|
|
||||||
### Using Nightly Rust
|
|
||||||
|
|
||||||
To generate [gcov-based](https://github.com/mozilla/grcov#example-how-to-generate-gcda-files-for-a-rust-project) coverage report
|
|
||||||
|
|
||||||
```shell
|
|
||||||
export CARGO_INCREMENTAL=0
|
|
||||||
export RUSTFLAGS="-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort"
|
|
||||||
export RUSTDOCFLAGS="-Cpanic=abort"
|
|
||||||
cargo build <options...> # e.g., --features feat_os_unix
|
|
||||||
cargo test <options...> # e.g., --features feat_os_unix test_pathchk
|
|
||||||
grcov . -s . --binary-path ./target/debug/ -t html --branch --ignore-not-existing --ignore build.rs --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?\#\[derive\()" -o ./target/debug/coverage/
|
|
||||||
# open target/debug/coverage/index.html in browser
|
|
||||||
```
|
|
||||||
|
|
||||||
if changes are not reflected in the report then run `cargo clean` and run the above commands.
|
|
||||||
|
|
||||||
### Using Stable Rust
|
|
||||||
|
|
||||||
If you are using stable version of Rust that doesn't enable code coverage instrumentation by default
|
|
||||||
then add `-Z-Zinstrument-coverage` flag to `RUSTFLAGS` env variable specified above.
|
|
||||||
|
|
||||||
## pre-commit hooks
|
|
||||||
|
|
||||||
A configuration for `pre-commit` is provided in the repository. It allows automatically checking every git commit you make to ensure it compiles, and passes `clippy` and `rustfmt` without warnings.
|
|
||||||
|
|
||||||
To use the provided hook:
|
|
||||||
|
|
||||||
1. [Install `pre-commit`](https://pre-commit.com/#install)
|
|
||||||
1. Run `pre-commit install` while in the repository directory
|
|
||||||
|
|
||||||
Your git commits will then automatically be checked. If a check fails, an error message will explain why, and your commit will be canceled. You can then make the suggested changes, and run `git commit ...` again.
|
|
||||||
|
|
||||||
## Using Clippy
|
|
||||||
|
|
||||||
The `msrv` key in the clippy configuration file `clippy.toml` is used to disable lints pertaining to newer features by specifying the minimum supported Rust version (MSRV). However, this key is only supported on `nightly`. To invoke clippy without errors, use `cargo +nightly clippy`. In order to also check tests and non-default crate features, use `cargo +nightly clippy --all-targets --all-features`.
|
|
||||||
|
|
||||||
## Markdown linter
|
|
||||||
|
|
||||||
We use <https://github.com/DavidAnson/markdownlint> to lint the Markdown files.
|
|
329
README.md
329
README.md
|
@ -1,3 +1,10 @@
|
||||||
|
<!-- markdownlint-disable MD033 MD041 MD002 -->
|
||||||
|
<!-- markdownlint-disable commands-show-output no-duplicate-heading -->
|
||||||
|
<!-- spell-checker:ignore markdownlint ; (options) DESTDIR UTILNAME manpages reimplementation -->
|
||||||
|
<div align="center">
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
# uutils coreutils
|
# uutils coreutils
|
||||||
|
|
||||||
[](https://crates.io/crates/coreutils)
|
[](https://crates.io/crates/coreutils)
|
||||||
|
@ -9,15 +16,14 @@
|
||||||
[](https://codecov.io/gh/uutils/coreutils)
|
[](https://codecov.io/gh/uutils/coreutils)
|
||||||

|

|
||||||
|
|
||||||
-----------------------------------------------
|
</div>
|
||||||
|
|
||||||
<!-- markdownlint-disable commands-show-output no-duplicate-heading -->
|
---
|
||||||
<!-- spell-checker:ignore markdownlint ; (options) DESTDIR RUNTEST UTILNAME manpages -->
|
|
||||||
|
|
||||||
uutils is an attempt at writing universal (as in cross-platform) CLI
|
|
||||||
utilities in [Rust](http://www.rust-lang.org).
|
uutils coreutils is a cross-platform reimplementation of the GNU coreutils in
|
||||||
While all programs have been implemented, some options might be missing
|
[Rust](http://www.rust-lang.org). While all programs have been implemented, some
|
||||||
or different behavior might be experienced.
|
options might be missing or different behavior might be experienced.
|
||||||
|
|
||||||
To install it:
|
To install it:
|
||||||
|
|
||||||
|
@ -27,13 +33,15 @@ cargo install coreutils
|
||||||
```
|
```
|
||||||
|
|
||||||
<!-- markdownlint-disable-next-line MD026 -->
|
<!-- markdownlint-disable-next-line MD026 -->
|
||||||
## Why?
|
|
||||||
|
|
||||||
uutils aims to work on as many platforms as possible, to be able to use the
|
## Goals
|
||||||
same utils on Linux, Mac, Windows and other platforms. This ensures, for
|
|
||||||
example, that scripts can be easily transferred between platforms. Rust was
|
uutils aims to be a drop-in replacement for the GNU utils. Differences with GNU
|
||||||
chosen not only because it is fast and safe, but is also excellent for
|
are treated as bugs.
|
||||||
writing cross-platform code.
|
|
||||||
|
uutils aims to work on as many platforms as possible, to be able to use the same
|
||||||
|
utils on Linux, Mac, Windows and other platforms. This ensures, for example,
|
||||||
|
that scripts can be easily transferred between platforms.
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
|
@ -42,10 +50,11 @@ uutils has both user and developer documentation available:
|
||||||
- [User Manual](https://uutils.github.io/user/)
|
- [User Manual](https://uutils.github.io/user/)
|
||||||
- [Developer Documentation](https://uutils.github.io/dev/coreutils/)
|
- [Developer Documentation](https://uutils.github.io/dev/coreutils/)
|
||||||
|
|
||||||
Both can also be generated locally, the instructions for that can be found in the
|
Both can also be generated locally, the instructions for that can be found in
|
||||||
[coreutils docs](https://github.com/uutils/uutils.github.io) repository.
|
the [coreutils docs](https://github.com/uutils/uutils.github.io) repository.
|
||||||
|
|
||||||
<!-- ANCHOR: build (this mark is needed for mdbook) -->
|
<!-- ANCHOR: build (this mark is needed for mdbook) -->
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- Rust (`cargo`, `rustc`)
|
- Rust (`cargo`, `rustc`)
|
||||||
|
@ -53,13 +62,13 @@ Both can also be generated locally, the instructions for that can be found in th
|
||||||
|
|
||||||
### Rust Version
|
### Rust Version
|
||||||
|
|
||||||
uutils follows Rust's release channels and is tested against stable, beta and nightly.
|
uutils follows Rust's release channels and is tested against stable, beta and
|
||||||
The current Minimum Supported Rust Version (MSRV) is `1.64.0`.
|
nightly. The current Minimum Supported Rust Version (MSRV) is `1.64.0`.
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
There are currently two methods to build the uutils binaries: either Cargo
|
There are currently two methods to build the uutils binaries: either Cargo or
|
||||||
or GNU Make.
|
GNU Make.
|
||||||
|
|
||||||
> Building the full package, including all documentation, requires both Cargo
|
> Building the full package, including all documentation, requires both Cargo
|
||||||
> and Gnu Make on a Unix platform.
|
> and Gnu Make on a Unix platform.
|
||||||
|
@ -73,8 +82,8 @@ cd coreutils
|
||||||
|
|
||||||
### Cargo
|
### Cargo
|
||||||
|
|
||||||
Building uutils using Cargo is easy because the process is the same as for
|
Building uutils using Cargo is easy because the process is the same as for every
|
||||||
every other Rust program:
|
other Rust program:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
cargo build --release
|
cargo build --release
|
||||||
|
@ -83,9 +92,9 @@ cargo build --release
|
||||||
This command builds the most portable common core set of uutils into a multicall
|
This command builds the most portable common core set of uutils into a multicall
|
||||||
(BusyBox-type) binary, named 'coreutils', on most Rust-supported platforms.
|
(BusyBox-type) binary, named 'coreutils', on most Rust-supported platforms.
|
||||||
|
|
||||||
Additional platform-specific uutils are often available. Building these
|
Additional platform-specific uutils are often available. Building these expanded
|
||||||
expanded sets of uutils for a platform (on that platform) is as simple as
|
sets of uutils for a platform (on that platform) is as simple as specifying it
|
||||||
specifying it as a feature:
|
as a feature:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
cargo build --release --features macos
|
cargo build --release --features macos
|
||||||
|
@ -96,18 +105,18 @@ cargo build --release --features unix
|
||||||
```
|
```
|
||||||
|
|
||||||
If you don't want to build every utility available on your platform into the
|
If you don't want to build every utility available on your platform into the
|
||||||
final binary, you can also specify which ones you want to build manually.
|
final binary, you can also specify which ones you want to build manually. For
|
||||||
For example:
|
example:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
cargo build --features "base32 cat echo rm" --no-default-features
|
cargo build --features "base32 cat echo rm" --no-default-features
|
||||||
```
|
```
|
||||||
|
|
||||||
If you don't want to build the multicall binary and would prefer to build
|
If you don't want to build the multicall binary and would prefer to build the
|
||||||
the utilities as individual binaries, that is also possible. Each utility
|
utilities as individual binaries, that is also possible. Each utility is
|
||||||
is contained in its own package within the main repository, named
|
contained in its own package within the main repository, named "uu_UTILNAME". To
|
||||||
"uu_UTILNAME". To build individual utilities, use cargo to build just the
|
build individual utilities, use cargo to build just the specific packages (using
|
||||||
specific packages (using the `--package` [aka `-p`] option). For example:
|
the `--package` [aka `-p`] option). For example:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
cargo build -p uu_base32 -p uu_cat -p uu_echo -p uu_rm
|
cargo build -p uu_base32 -p uu_cat -p uu_echo -p uu_rm
|
||||||
|
@ -148,13 +157,15 @@ make UTILS='UTILITY_1 UTILITY_2'
|
||||||
Likewise, installing can simply be done using:
|
Likewise, installing can simply be done using:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
cargo install --path .
|
cargo install --path . --locked
|
||||||
```
|
```
|
||||||
|
|
||||||
This command will install uutils into Cargo's *bin* folder (*e.g.* `$HOME/.cargo/bin`).
|
This command will install uutils into Cargo's _bin_ folder (_e.g._
|
||||||
|
`$HOME/.cargo/bin`).
|
||||||
|
|
||||||
This does not install files necessary for shell completion or manpages.
|
This does not install files necessary for shell completion or manpages. For
|
||||||
For manpages or shell completion to work, use `GNU Make` or see `Manually install shell completions`/`Manually install manpages`.
|
manpages or shell completion to work, use `GNU Make` or see
|
||||||
|
`Manually install shell completions`/`Manually install manpages`.
|
||||||
|
|
||||||
### Install with GNU Make
|
### Install with GNU Make
|
||||||
|
|
||||||
|
@ -207,8 +218,8 @@ be generated; See `Manually install shell completions`.
|
||||||
|
|
||||||
### Manually install shell completions
|
### Manually install shell completions
|
||||||
|
|
||||||
The `coreutils` binary can generate completions for the `bash`, `elvish`, `fish`, `powershell`
|
The `coreutils` binary can generate completions for the `bash`, `elvish`,
|
||||||
and `zsh` shells. It prints the result to stdout.
|
`fish`, `powershell` and `zsh` shells. It prints the result to stdout.
|
||||||
|
|
||||||
The syntax is:
|
The syntax is:
|
||||||
|
|
||||||
|
@ -216,8 +227,8 @@ The syntax is:
|
||||||
cargo run completion <utility> <shell>
|
cargo run completion <utility> <shell>
|
||||||
```
|
```
|
||||||
|
|
||||||
So, to install completions for `ls` on `bash` to `/usr/local/share/bash-completion/completions/ls`,
|
So, to install completions for `ls` on `bash` to
|
||||||
run:
|
`/usr/local/share/bash-completion/completions/ls`, run:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
cargo run completion ls bash > /usr/local/share/bash-completion/completions/ls
|
cargo run completion ls bash > /usr/local/share/bash-completion/completions/ls
|
||||||
|
@ -226,12 +237,12 @@ cargo run completion ls bash > /usr/local/share/bash-completion/completions/ls
|
||||||
### Manually install manpages
|
### Manually install manpages
|
||||||
|
|
||||||
To generate manpages, the syntax is:
|
To generate manpages, the syntax is:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo run manpage <utility>
|
cargo run manpage <utility>
|
||||||
```
|
```
|
||||||
|
|
||||||
So, to install the manpage for `ls` to `/usr/local/share/man/man1/ls.1`
|
So, to install the manpage for `ls` to `/usr/local/share/man/man1/ls.1` run:
|
||||||
run:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo run manpage ls > /usr/local/share/man/man1/ls.1
|
cargo run manpage ls > /usr/local/share/man/man1/ls.1
|
||||||
|
@ -239,8 +250,8 @@ cargo run manpage ls > /usr/local/share/man/man1/ls.1
|
||||||
|
|
||||||
## Un-installation
|
## Un-installation
|
||||||
|
|
||||||
Un-installation differs depending on how you have installed uutils. If you used
|
Un-installation differs depending on how you have installed uutils. If you used
|
||||||
Cargo to install, use Cargo to uninstall. If you used GNU Make to install, use
|
Cargo to install, use Cargo to uninstall. If you used GNU Make to install, use
|
||||||
Make to uninstall.
|
Make to uninstall.
|
||||||
|
|
||||||
### Uninstall with Cargo
|
### Uninstall with Cargo
|
||||||
|
@ -280,245 +291,21 @@ make PREFIX=/my/path uninstall
|
||||||
|
|
||||||
<!-- ANCHOR_END: build (this mark is needed for mdbook) -->
|
<!-- ANCHOR_END: build (this mark is needed for mdbook) -->
|
||||||
|
|
||||||
## Testing
|
## GNU test suite compatibility
|
||||||
|
|
||||||
Testing can be done using either Cargo or `make`.
|
|
||||||
|
|
||||||
### Testing with Cargo
|
|
||||||
|
|
||||||
Just like with building, we follow the standard procedure for testing using
|
|
||||||
Cargo:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
cargo test
|
|
||||||
```
|
|
||||||
|
|
||||||
By default, `cargo test` only runs the common programs. To run also platform
|
|
||||||
specific tests, run:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
cargo test --features unix
|
|
||||||
```
|
|
||||||
|
|
||||||
If you would prefer to test a select few utilities:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
cargo test --features "chmod mv tail" --no-default-features
|
|
||||||
```
|
|
||||||
|
|
||||||
If you also want to test the core utilities:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
cargo test -p uucore -p coreutils
|
|
||||||
```
|
|
||||||
|
|
||||||
To debug:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
gdb --args target/debug/coreutils ls
|
|
||||||
(gdb) b ls.rs:79
|
|
||||||
(gdb) run
|
|
||||||
```
|
|
||||||
|
|
||||||
### Testing with GNU Make
|
|
||||||
|
|
||||||
To simply test all available utilities:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
make test
|
|
||||||
```
|
|
||||||
|
|
||||||
To test all but a few of the available utilities:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
make SKIP_UTILS='UTILITY_1 UTILITY_2' test
|
|
||||||
```
|
|
||||||
|
|
||||||
To test only a few of the available utilities:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
make UTILS='UTILITY_1 UTILITY_2' test
|
|
||||||
```
|
|
||||||
|
|
||||||
To include tests for unimplemented behavior:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
make UTILS='UTILITY_1 UTILITY_2' SPEC=y test
|
|
||||||
```
|
|
||||||
|
|
||||||
### Run Busybox Tests
|
|
||||||
|
|
||||||
This testing functionality is only available on *nix operating systems and
|
|
||||||
requires `make`.
|
|
||||||
|
|
||||||
To run busybox tests for all utilities for which busybox has tests
|
|
||||||
|
|
||||||
```shell
|
|
||||||
make busytest
|
|
||||||
```
|
|
||||||
|
|
||||||
To run busybox tests for a few of the available utilities
|
|
||||||
|
|
||||||
```shell
|
|
||||||
make UTILS='UTILITY_1 UTILITY_2' busytest
|
|
||||||
```
|
|
||||||
|
|
||||||
To pass an argument like "-v" to the busybox test runtime
|
|
||||||
|
|
||||||
```shell
|
|
||||||
make UTILS='UTILITY_1 UTILITY_2' RUNTEST_ARGS='-v' busytest
|
|
||||||
```
|
|
||||||
|
|
||||||
### Comparing with GNU
|
|
||||||
|
|
||||||
Below is the evolution of how many GNU tests uutils passes. A more detailed
|
Below is the evolution of how many GNU tests uutils passes. A more detailed
|
||||||
breakdown of the GNU test results of the main branch can be found
|
breakdown of the GNU test results of the main branch can be found
|
||||||
[in the user manual](https://uutils.github.io/user/test_coverage.html).
|
[in the user manual](https://uutils.github.io/user/test_coverage.html).
|
||||||
|
|
||||||
|
See <https://github.com/uutils/coreutils/issues/3336> for the main meta bugs
|
||||||
|
(many are missing).
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
To run locally:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
bash util/build-gnu.sh
|
|
||||||
bash util/run-gnu-test.sh
|
|
||||||
# To run a single test:
|
|
||||||
bash util/run-gnu-test.sh tests/touch/not-owner.sh # for example
|
|
||||||
# To run several tests:
|
|
||||||
bash util/run-gnu-test.sh tests/touch/not-owner.sh tests/rm/no-give-up.sh # for example
|
|
||||||
# If this is a perl (.pl) test, to run in debug:
|
|
||||||
DEBUG=1 bash util/run-gnu-test.sh tests/misc/sm3sum.pl
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that it relies on individual utilities (not the multicall binary).
|
|
||||||
|
|
||||||
### Improving the GNU compatibility
|
|
||||||
|
|
||||||
The Python script `./util/remaining-gnu-error.py` shows the list of failing tests in the CI.
|
|
||||||
|
|
||||||
To improve the GNU compatibility, the following process is recommended:
|
|
||||||
|
|
||||||
1. Identify a test (the smaller, the better) on a program that you understand or is easy to understand. You can use the `./util/remaining-gnu-error.py` script to help with this decision.
|
|
||||||
1. Build both the GNU and Rust coreutils using: `bash util/build-gnu.sh`
|
|
||||||
1. Run the test with `bash util/run-gnu-test.sh <your test>`
|
|
||||||
1. Start to modify `<your test>` to understand what is wrong. Examples:
|
|
||||||
1. Add `set -v` to have the bash verbose mode
|
|
||||||
1. Add `echo $?` where needed
|
|
||||||
1. When the variable `fail` is used in the test, `echo $fail` to see when the test started to fail
|
|
||||||
1. Bump the content of the output (ex: `cat err`)
|
|
||||||
1. ...
|
|
||||||
1. Or, if the test is simple, extract the relevant information to create a new test case running both GNU & Rust implementation
|
|
||||||
1. Start to modify the Rust implementation to match the expected behavior
|
|
||||||
1. Add a test to make sure that we don't regress (our test suite is super quick)
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
To contribute to uutils, please see [CONTRIBUTING](CONTRIBUTING.md).
|
To contribute to uutils, please see [CONTRIBUTING](CONTRIBUTING.md).
|
||||||
|
|
||||||
## Utilities
|
|
||||||
|
|
||||||
Please note that this is not fully accurate:
|
|
||||||
|
|
||||||
- Some new options can be added / removed in the GNU implementation;
|
|
||||||
- Some error management might be missing;
|
|
||||||
- Some behaviors might be different.
|
|
||||||
|
|
||||||
See <https://github.com/uutils/coreutils/issues/3336> for the main meta bugs
|
|
||||||
(many are missing).
|
|
||||||
|
|
||||||
| Done | WIP |
|
|
||||||
|-----------|-----------|
|
|
||||||
| arch | cp |
|
|
||||||
| base32 | date |
|
|
||||||
| base64 | dd |
|
|
||||||
| basename | df |
|
|
||||||
| basenc | expr |
|
|
||||||
| cat | install |
|
|
||||||
| chcon | ls |
|
|
||||||
| chgrp | more |
|
|
||||||
| chmod | numfmt |
|
|
||||||
| chown | od (`--strings` and 128-bit data types missing) |
|
|
||||||
| chroot | pr |
|
|
||||||
| cksum | printf |
|
|
||||||
| comm | sort |
|
|
||||||
| csplit | split |
|
|
||||||
| cut | tac |
|
|
||||||
| dircolors | test |
|
|
||||||
| dirname | dir |
|
|
||||||
| du | vdir |
|
|
||||||
| echo | stty |
|
|
||||||
| env | |
|
|
||||||
| expand | |
|
|
||||||
| factor | |
|
|
||||||
| false | |
|
|
||||||
| fmt | |
|
|
||||||
| fold | |
|
|
||||||
| groups | |
|
|
||||||
| hashsum | |
|
|
||||||
| head | |
|
|
||||||
| hostid | |
|
|
||||||
| hostname | |
|
|
||||||
| id | |
|
|
||||||
| join | |
|
|
||||||
| kill | |
|
|
||||||
| link | |
|
|
||||||
| ln | |
|
|
||||||
| logname | |
|
|
||||||
| ~~md5sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/main/src/uu/hashsum/src/hashsum.rs)) | |
|
|
||||||
| ~~sha1sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/main/src/uu/hashsum/src/hashsum.rs)) | |
|
|
||||||
| ~~sha224sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/main/src/uu/hashsum/src/hashsum.rs)) | |
|
|
||||||
| ~~sha256sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/main/src/uu/hashsum/src/hashsum.rs)) | |
|
|
||||||
| ~~sha384sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/main/src/uu/hashsum/src/hashsum.rs)) | |
|
|
||||||
| ~~sha512sum~~ (replaced by [hashsum](https://github.com/uutils/coreutils/blob/main/src/uu/hashsum/src/hashsum.rs)) | |
|
|
||||||
| mkdir | |
|
|
||||||
| mkfifo | |
|
|
||||||
| mknod | |
|
|
||||||
| mktemp | |
|
|
||||||
| mv | |
|
|
||||||
| nice | |
|
|
||||||
| nl | |
|
|
||||||
| nohup | |
|
|
||||||
| nproc | |
|
|
||||||
| paste | |
|
|
||||||
| pathchk | |
|
|
||||||
| pinky | |
|
|
||||||
| printenv | |
|
|
||||||
| ptx | |
|
|
||||||
| pwd | |
|
|
||||||
| readlink | |
|
|
||||||
| realpath | |
|
|
||||||
| relpath | |
|
|
||||||
| rm | |
|
|
||||||
| rmdir | |
|
|
||||||
| runcon | |
|
|
||||||
| seq | |
|
|
||||||
| shred | |
|
|
||||||
| shuf | |
|
|
||||||
| sleep | |
|
|
||||||
| stat | |
|
|
||||||
| stdbuf | |
|
|
||||||
| sum | |
|
|
||||||
| sync | |
|
|
||||||
| tail | |
|
|
||||||
| tee | |
|
|
||||||
| timeout | |
|
|
||||||
| touch | |
|
|
||||||
| tr | |
|
|
||||||
| true | |
|
|
||||||
| truncate | |
|
|
||||||
| tsort | |
|
|
||||||
| tty | |
|
|
||||||
| uname | |
|
|
||||||
| unexpand | |
|
|
||||||
| uniq | |
|
|
||||||
| unlink | |
|
|
||||||
| uptime | |
|
|
||||||
| users | |
|
|
||||||
| wc | |
|
|
||||||
| who | |
|
|
||||||
| whoami | |
|
|
||||||
| yes | |
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
uutils is licensed under the MIT License - see the `LICENSE` file for details
|
uutils is licensed under the MIT License - see the `LICENSE` file for details
|
||||||
|
|
2
build.rs
2
build.rs
|
@ -20,6 +20,8 @@ pub fn main() {
|
||||||
for (key, val) in env::vars() {
|
for (key, val) in env::vars() {
|
||||||
if val == "1" && key.starts_with(ENV_FEATURE_PREFIX) {
|
if val == "1" && key.starts_with(ENV_FEATURE_PREFIX) {
|
||||||
let krate = key[ENV_FEATURE_PREFIX.len()..].to_lowercase();
|
let krate = key[ENV_FEATURE_PREFIX.len()..].to_lowercase();
|
||||||
|
// Allow this as we have a bunch of info in the comments
|
||||||
|
#[allow(clippy::match_same_arms)]
|
||||||
match krate.as_ref() {
|
match krate.as_ref() {
|
||||||
"default" | "macos" | "unix" | "windows" | "selinux" | "zip" => continue, // common/standard feature names
|
"default" | "macos" | "unix" | "windows" | "selinux" | "zip" => continue, // common/standard feature names
|
||||||
"nightly" | "test_unimplemented" => continue, // crate-local custom features
|
"nightly" | "test_unimplemented" => continue, // crate-local custom features
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
// spell-checker:ignore (ToDO) Chmoder cmode fmode fperm fref ugoa RFILE RFILE's
|
// spell-checker:ignore (ToDO) Chmoder cmode fmode fperm fref ugoa RFILE RFILE's
|
||||||
|
|
||||||
use clap::{crate_version, Arg, ArgAction, Command};
|
use clap::{crate_version, Arg, ArgAction, Command};
|
||||||
|
use std::ffi::OsString;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::os::unix::fs::{MetadataExt, PermissionsExt};
|
use std::os::unix::fs::{MetadataExt, PermissionsExt};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
@ -35,14 +36,64 @@ mod options {
|
||||||
pub const FILE: &str = "FILE";
|
pub const FILE: &str = "FILE";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extract negative modes (starting with '-') from the rest of the arguments.
|
||||||
|
///
|
||||||
|
/// This is mainly required for GNU compatibility, where "non-positional negative" modes are used
|
||||||
|
/// as the actual positional MODE. Some examples of these cases are:
|
||||||
|
/// * "chmod -w -r file", which is the same as "chmod -w,-r file"
|
||||||
|
/// * "chmod -w file -r", which is the same as "chmod -w,-r file"
|
||||||
|
///
|
||||||
|
/// These can currently not be handled by clap.
|
||||||
|
/// Therefore it might be possible that a pseudo MODE is inserted to pass clap parsing.
|
||||||
|
/// The pseudo MODE is later replaced by the extracted (and joined) negative modes.
|
||||||
|
fn extract_negative_modes(mut args: impl uucore::Args) -> (Option<String>, Vec<OsString>) {
|
||||||
|
// we look up the args until "--" is found
|
||||||
|
// "-mode" will be extracted into parsed_cmode_vec
|
||||||
|
let (parsed_cmode_vec, pre_double_hyphen_args): (Vec<OsString>, Vec<OsString>) =
|
||||||
|
args.by_ref().take_while(|a| a != "--").partition(|arg| {
|
||||||
|
let arg = if let Some(arg) = arg.to_str() {
|
||||||
|
arg.to_string()
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
arg.len() >= 2
|
||||||
|
&& arg.starts_with('-')
|
||||||
|
&& matches!(
|
||||||
|
arg.chars().nth(1).unwrap(),
|
||||||
|
'r' | 'w' | 'x' | 'X' | 's' | 't' | 'u' | 'g' | 'o' | '0'..='7'
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut clean_args = Vec::new();
|
||||||
|
if !parsed_cmode_vec.is_empty() {
|
||||||
|
// we need a pseudo cmode for clap, which won't be used later.
|
||||||
|
// this is required because clap needs the default "chmod MODE FILE" scheme.
|
||||||
|
clean_args.push("w".into());
|
||||||
|
}
|
||||||
|
clean_args.extend(pre_double_hyphen_args);
|
||||||
|
|
||||||
|
if let Some(arg) = args.next() {
|
||||||
|
// as there is still something left in the iterator, we previously consumed the "--"
|
||||||
|
// -> add it to the args again
|
||||||
|
clean_args.push("--".into());
|
||||||
|
clean_args.push(arg);
|
||||||
|
}
|
||||||
|
clean_args.extend(args);
|
||||||
|
|
||||||
|
let parsed_cmode = Some(
|
||||||
|
parsed_cmode_vec
|
||||||
|
.iter()
|
||||||
|
.map(|s| s.to_str().unwrap())
|
||||||
|
.collect::<Vec<&str>>()
|
||||||
|
.join(","),
|
||||||
|
)
|
||||||
|
.filter(|s| !s.is_empty());
|
||||||
|
(parsed_cmode, clean_args)
|
||||||
|
}
|
||||||
|
|
||||||
#[uucore::main]
|
#[uucore::main]
|
||||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
let mut args = args.collect_lossy();
|
let (parsed_cmode, args) = extract_negative_modes(args.skip(1)); // skip binary name
|
||||||
|
|
||||||
// Before we can parse 'args' with clap (and previously getopts),
|
|
||||||
// a possible MODE prefix '-' needs to be removed (e.g. "chmod -x FILE").
|
|
||||||
let mode_had_minus_prefix = mode::strip_minus_from_mode(&mut args);
|
|
||||||
|
|
||||||
let matches = uu_app().after_help(LONG_USAGE).try_get_matches_from(args)?;
|
let matches = uu_app().after_help(LONG_USAGE).try_get_matches_from(args)?;
|
||||||
|
|
||||||
let changes = matches.get_flag(options::CHANGES);
|
let changes = matches.get_flag(options::CHANGES);
|
||||||
|
@ -62,13 +113,14 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
},
|
},
|
||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
let modes = matches.get_one::<String>(options::MODE).unwrap(); // should always be Some because required
|
|
||||||
let cmode = if mode_had_minus_prefix {
|
let modes = matches.get_one::<String>(options::MODE);
|
||||||
// clap parsing is finished, now put prefix back
|
let cmode = if let Some(parsed_cmode) = parsed_cmode {
|
||||||
format!("-{modes}")
|
parsed_cmode
|
||||||
} else {
|
} else {
|
||||||
modes.to_string()
|
modes.unwrap().to_string() // modes is required
|
||||||
};
|
};
|
||||||
|
// FIXME: enable non-utf8 paths
|
||||||
let mut files: Vec<String> = matches
|
let mut files: Vec<String> = matches
|
||||||
.get_many::<String>(options::FILE)
|
.get_many::<String>(options::FILE)
|
||||||
.map(|v| v.map(ToString::to_string).collect())
|
.map(|v| v.map(ToString::to_string).collect())
|
||||||
|
@ -107,6 +159,7 @@ pub fn uu_app() -> Command {
|
||||||
.override_usage(format_usage(USAGE))
|
.override_usage(format_usage(USAGE))
|
||||||
.args_override_self(true)
|
.args_override_self(true)
|
||||||
.infer_long_args(true)
|
.infer_long_args(true)
|
||||||
|
.no_binary_name(true)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::new(options::CHANGES)
|
Arg::new(options::CHANGES)
|
||||||
.long(options::CHANGES)
|
.long(options::CHANGES)
|
||||||
|
@ -376,3 +429,34 @@ impl Chmoder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_extract_negative_modes() {
|
||||||
|
// "chmod -w -r file" becomes "chmod -w,-r file". clap does not accept "-w,-r" as MODE.
|
||||||
|
// Therefore, "w" is added as pseudo mode to pass clap.
|
||||||
|
let (c, a) = extract_negative_modes(vec!["-w", "-r", "file"].iter().map(OsString::from));
|
||||||
|
assert_eq!(c, Some("-w,-r".to_string()));
|
||||||
|
assert_eq!(a, vec!["w", "file"]);
|
||||||
|
|
||||||
|
// "chmod -w file -r" becomes "chmod -w,-r file". clap does not accept "-w,-r" as MODE.
|
||||||
|
// Therefore, "w" is added as pseudo mode to pass clap.
|
||||||
|
let (c, a) = extract_negative_modes(vec!["-w", "file", "-r"].iter().map(OsString::from));
|
||||||
|
assert_eq!(c, Some("-w,-r".to_string()));
|
||||||
|
assert_eq!(a, vec!["w", "file"]);
|
||||||
|
|
||||||
|
// "chmod -w -- -r file" becomes "chmod -w -r file", where "-r" is interpreted as file.
|
||||||
|
// Again, "w" is needed as pseudo mode.
|
||||||
|
let (c, a) = extract_negative_modes(vec!["-w", "--", "-r", "f"].iter().map(OsString::from));
|
||||||
|
assert_eq!(c, Some("-w".to_string()));
|
||||||
|
assert_eq!(a, vec!["w", "--", "-r", "f"]);
|
||||||
|
|
||||||
|
// "chmod -- -r file" becomes "chmod -r file".
|
||||||
|
let (c, a) = extract_negative_modes(vec!["--", "-r", "file"].iter().map(OsString::from));
|
||||||
|
assert_eq!(c, None);
|
||||||
|
assert_eq!(a, vec!["--", "-r", "file"]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
23
src/uu/cksum/cksum.md
Normal file
23
src/uu/cksum/cksum.md
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# cksum
|
||||||
|
|
||||||
|
```
|
||||||
|
cksum [OPTIONS] [FILE]...
|
||||||
|
```
|
||||||
|
|
||||||
|
Print CRC and size for each file
|
||||||
|
|
||||||
|
## After Help
|
||||||
|
|
||||||
|
DIGEST determines the digest algorithm and default output format:
|
||||||
|
|
||||||
|
- `-a=sysv`: (equivalent to sum -s)
|
||||||
|
- `-a=bsd`: (equivalent to sum -r)
|
||||||
|
- `-a=crc`: (equivalent to cksum)
|
||||||
|
- `-a=md5`: (equivalent to md5sum)
|
||||||
|
- `-a=sha1`: (equivalent to sha1sum)
|
||||||
|
- `-a=sha224`: (equivalent to sha224sum)
|
||||||
|
- `-a=sha256`: (equivalent to sha256sum)
|
||||||
|
- `-a=sha384`: (equivalent to sha384sum)
|
||||||
|
- `-a=sha512`: (equivalent to sha512sum)
|
||||||
|
- `-a=blake2b`: (equivalent to b2sum)
|
||||||
|
- `-a=sm3`: (only available through cksum)
|
|
@ -15,15 +15,16 @@ use std::iter;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use uucore::{
|
use uucore::{
|
||||||
error::{FromIo, UResult},
|
error::{FromIo, UResult},
|
||||||
format_usage,
|
format_usage, help_about, help_section, help_usage,
|
||||||
sum::{
|
sum::{
|
||||||
div_ceil, Blake2b, Digest, DigestWriter, Md5, Sha1, Sha224, Sha256, Sha384, Sha512, Sm3,
|
div_ceil, Blake2b, Digest, DigestWriter, Md5, Sha1, Sha224, Sha256, Sha384, Sha512, Sm3,
|
||||||
BSD, CRC, SYSV,
|
BSD, CRC, SYSV,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const USAGE: &str = "{} [OPTIONS] [FILE]...";
|
const USAGE: &str = help_usage!("cksum.md");
|
||||||
const ABOUT: &str = "Print CRC and size for each file";
|
const ABOUT: &str = help_about!("cksum.md");
|
||||||
|
const AFTER_HELP: &str = help_section!("after help", "cksum.md");
|
||||||
|
|
||||||
const ALGORITHM_OPTIONS_SYSV: &str = "sysv";
|
const ALGORITHM_OPTIONS_SYSV: &str = "sysv";
|
||||||
const ALGORITHM_OPTIONS_BSD: &str = "bsd";
|
const ALGORITHM_OPTIONS_BSD: &str = "bsd";
|
||||||
|
@ -205,21 +206,6 @@ mod options {
|
||||||
pub static ALGORITHM: &str = "algorithm";
|
pub static ALGORITHM: &str = "algorithm";
|
||||||
}
|
}
|
||||||
|
|
||||||
const ALGORITHM_HELP_DESC: &str =
|
|
||||||
"DIGEST determines the digest algorithm and default output format:\n\
|
|
||||||
\n\
|
|
||||||
-a=sysv: (equivalent to sum -s)\n\
|
|
||||||
-a=bsd: (equivalent to sum -r)\n\
|
|
||||||
-a=crc: (equivalent to cksum)\n\
|
|
||||||
-a=md5: (equivalent to md5sum)\n\
|
|
||||||
-a=sha1: (equivalent to sha1sum)\n\
|
|
||||||
-a=sha224: (equivalent to sha224sum)\n\
|
|
||||||
-a=sha256: (equivalent to sha256sum)\n\
|
|
||||||
-a=sha384: (equivalent to sha384sum)\n\
|
|
||||||
-a=sha512: (equivalent to sha512sum)\n\
|
|
||||||
-a=blake2b: (equivalent to b2sum)\n\
|
|
||||||
-a=sm3: (only available through cksum)\n";
|
|
||||||
|
|
||||||
#[uucore::main]
|
#[uucore::main]
|
||||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
let args = args.collect_ignore();
|
let args = args.collect_ignore();
|
||||||
|
@ -278,5 +264,5 @@ pub fn uu_app() -> Command {
|
||||||
ALGORITHM_OPTIONS_SM3,
|
ALGORITHM_OPTIONS_SM3,
|
||||||
]),
|
]),
|
||||||
)
|
)
|
||||||
.after_help(ALGORITHM_HELP_DESC)
|
.after_help(AFTER_HELP)
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ use uucore::display::Quotable;
|
||||||
#[cfg(not(any(target_os = "macos", target_os = "redox")))]
|
#[cfg(not(any(target_os = "macos", target_os = "redox")))]
|
||||||
use uucore::error::FromIo;
|
use uucore::error::FromIo;
|
||||||
use uucore::error::{UResult, USimpleError};
|
use uucore::error::{UResult, USimpleError};
|
||||||
use uucore::{format_usage, help_about, help_usage, show_error};
|
use uucore::{format_usage, help_about, help_usage, show};
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use windows_sys::Win32::{Foundation::SYSTEMTIME, System::SystemInformation::SetSystemTime};
|
use windows_sys::Win32::{Foundation::SYSTEMTIME, System::SystemInformation::SetSystemTime};
|
||||||
|
|
||||||
|
@ -114,8 +114,8 @@ impl<'a> From<&'a str> for Iso8601Format {
|
||||||
SECONDS | SECOND => Self::Seconds,
|
SECONDS | SECOND => Self::Seconds,
|
||||||
NS => Self::Ns,
|
NS => Self::Ns,
|
||||||
DATE => Self::Date,
|
DATE => Self::Date,
|
||||||
// Should be caught by clap
|
// Note: This is caught by clap via `possible_values`
|
||||||
_ => panic!("Invalid format: {s}"),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -203,9 +203,6 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
|
|
||||||
return set_system_datetime(date);
|
return set_system_datetime(date);
|
||||||
} else {
|
} else {
|
||||||
// Declare a file here because it needs to outlive the `dates` iterator.
|
|
||||||
let file: File;
|
|
||||||
|
|
||||||
// Get the current time, either in the local time zone or UTC.
|
// Get the current time, either in the local time zone or UTC.
|
||||||
let now: DateTime<FixedOffset> = if settings.utc {
|
let now: DateTime<FixedOffset> = if settings.utc {
|
||||||
let now = Utc::now();
|
let now = Utc::now();
|
||||||
|
@ -222,12 +219,19 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
let iter = std::iter::once(date);
|
let iter = std::iter::once(date);
|
||||||
Box::new(iter)
|
Box::new(iter)
|
||||||
}
|
}
|
||||||
DateSource::File(ref path) => {
|
DateSource::File(ref path) => match File::open(path) {
|
||||||
file = File::open(path).unwrap();
|
Ok(file) => {
|
||||||
let lines = BufReader::new(file).lines();
|
let lines = BufReader::new(file).lines();
|
||||||
let iter = lines.filter_map(Result::ok).map(parse_date);
|
let iter = lines.filter_map(Result::ok).map(parse_date);
|
||||||
Box::new(iter)
|
Box::new(iter)
|
||||||
}
|
}
|
||||||
|
Err(_err) => {
|
||||||
|
return Err(USimpleError::new(
|
||||||
|
2,
|
||||||
|
format!("{}: No such file or directory", path.display()),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
},
|
||||||
DateSource::Now => {
|
DateSource::Now => {
|
||||||
let iter = std::iter::once(Ok(now));
|
let iter = std::iter::once(Ok(now));
|
||||||
Box::new(iter)
|
Box::new(iter)
|
||||||
|
@ -257,7 +261,10 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
.replace("%f", "%N");
|
.replace("%f", "%N");
|
||||||
println!("{formatted}");
|
println!("{formatted}");
|
||||||
}
|
}
|
||||||
Err((input, _err)) => show_error!("invalid date {}", input.quote()),
|
Err((input, _err)) => show!(USimpleError::new(
|
||||||
|
1,
|
||||||
|
format!("invalid date {}", input.quote())
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -291,6 +298,9 @@ pub fn uu_app() -> Command {
|
||||||
.short('I')
|
.short('I')
|
||||||
.long(OPT_ISO_8601)
|
.long(OPT_ISO_8601)
|
||||||
.value_name("FMT")
|
.value_name("FMT")
|
||||||
|
.value_parser([DATE, HOUR, HOURS, MINUTE, MINUTES, SECOND, SECONDS, NS])
|
||||||
|
.num_args(0..=1)
|
||||||
|
.default_missing_value(OPT_DATE)
|
||||||
.help(ISO_8601_HELP_STRING),
|
.help(ISO_8601_HELP_STRING),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
|
@ -304,6 +314,7 @@ pub fn uu_app() -> Command {
|
||||||
Arg::new(OPT_RFC_3339)
|
Arg::new(OPT_RFC_3339)
|
||||||
.long(OPT_RFC_3339)
|
.long(OPT_RFC_3339)
|
||||||
.value_name("FMT")
|
.value_name("FMT")
|
||||||
|
.value_parser([DATE, SECOND, SECONDS, NS])
|
||||||
.help(RFC_3339_HELP_STRING),
|
.help(RFC_3339_HELP_STRING),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
|
|
|
@ -27,11 +27,14 @@ use std::cmp;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
use std::fs::{File, OpenOptions};
|
use std::fs::{File, OpenOptions};
|
||||||
use std::io::{self, Read, Seek, SeekFrom, Stdin, Stdout, Write};
|
use std::io::{self, Read, Seek, SeekFrom, Stdout, Write};
|
||||||
#[cfg(unix)]
|
|
||||||
use std::os::unix::fs::FileTypeExt;
|
|
||||||
#[cfg(any(target_os = "linux", target_os = "android"))]
|
#[cfg(any(target_os = "linux", target_os = "android"))]
|
||||||
use std::os::unix::fs::OpenOptionsExt;
|
use std::os::unix::fs::OpenOptionsExt;
|
||||||
|
#[cfg(unix)]
|
||||||
|
use std::os::unix::{
|
||||||
|
fs::FileTypeExt,
|
||||||
|
io::{AsRawFd, FromRawFd},
|
||||||
|
};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
@ -93,21 +96,44 @@ impl Num {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Data sources.
|
/// Data sources.
|
||||||
|
///
|
||||||
|
/// Use [`Source::stdin_as_file`] if available to enable more
|
||||||
|
/// fine-grained access to reading from stdin.
|
||||||
enum Source {
|
enum Source {
|
||||||
/// Input from stdin.
|
/// Input from stdin.
|
||||||
Stdin(Stdin),
|
#[cfg(not(unix))]
|
||||||
|
Stdin(io::Stdin),
|
||||||
|
|
||||||
/// Input from a file.
|
/// Input from a file.
|
||||||
File(File),
|
File(File),
|
||||||
|
|
||||||
|
/// Input from stdin, opened from its file descriptor.
|
||||||
|
#[cfg(unix)]
|
||||||
|
StdinFile(File),
|
||||||
|
|
||||||
/// Input from a named pipe, also known as a FIFO.
|
/// Input from a named pipe, also known as a FIFO.
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
Fifo(File),
|
Fifo(File),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Source {
|
impl Source {
|
||||||
|
/// Create a source from stdin using its raw file descriptor.
|
||||||
|
///
|
||||||
|
/// This returns an instance of the `Source::StdinFile` variant,
|
||||||
|
/// using the raw file descriptor of [`std::io::Stdin`] to create
|
||||||
|
/// the [`std::fs::File`] parameter. You can use this instead of
|
||||||
|
/// `Source::Stdin` to allow reading from stdin without consuming
|
||||||
|
/// the entire contents of stdin when this process terminates.
|
||||||
|
#[cfg(unix)]
|
||||||
|
fn stdin_as_file() -> Self {
|
||||||
|
let fd = io::stdin().as_raw_fd();
|
||||||
|
let f = unsafe { File::from_raw_fd(fd) };
|
||||||
|
Self::StdinFile(f)
|
||||||
|
}
|
||||||
|
|
||||||
fn skip(&mut self, n: u64) -> io::Result<u64> {
|
fn skip(&mut self, n: u64) -> io::Result<u64> {
|
||||||
match self {
|
match self {
|
||||||
|
#[cfg(not(unix))]
|
||||||
Self::Stdin(stdin) => match io::copy(&mut stdin.take(n), &mut io::sink()) {
|
Self::Stdin(stdin) => match io::copy(&mut stdin.take(n), &mut io::sink()) {
|
||||||
Ok(m) if m < n => {
|
Ok(m) if m < n => {
|
||||||
show_error!("'standard input': cannot skip to specified offset");
|
show_error!("'standard input': cannot skip to specified offset");
|
||||||
|
@ -116,6 +142,15 @@ impl Source {
|
||||||
Ok(m) => Ok(m),
|
Ok(m) => Ok(m),
|
||||||
Err(e) => Err(e),
|
Err(e) => Err(e),
|
||||||
},
|
},
|
||||||
|
#[cfg(unix)]
|
||||||
|
Self::StdinFile(f) => match io::copy(&mut f.take(n), &mut io::sink()) {
|
||||||
|
Ok(m) if m < n => {
|
||||||
|
show_error!("'standard input': cannot skip to specified offset");
|
||||||
|
Ok(m)
|
||||||
|
}
|
||||||
|
Ok(m) => Ok(m),
|
||||||
|
Err(e) => Err(e),
|
||||||
|
},
|
||||||
Self::File(f) => f.seek(io::SeekFrom::Start(n)),
|
Self::File(f) => f.seek(io::SeekFrom::Start(n)),
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
Self::Fifo(f) => io::copy(&mut f.take(n), &mut io::sink()),
|
Self::Fifo(f) => io::copy(&mut f.take(n), &mut io::sink()),
|
||||||
|
@ -126,9 +161,12 @@ impl Source {
|
||||||
impl Read for Source {
|
impl Read for Source {
|
||||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
match self {
|
match self {
|
||||||
|
#[cfg(not(unix))]
|
||||||
Self::Stdin(stdin) => stdin.read(buf),
|
Self::Stdin(stdin) => stdin.read(buf),
|
||||||
Self::File(f) => f.read(buf),
|
Self::File(f) => f.read(buf),
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
|
Self::StdinFile(f) => f.read(buf),
|
||||||
|
#[cfg(unix)]
|
||||||
Self::Fifo(f) => f.read(buf),
|
Self::Fifo(f) => f.read(buf),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -151,7 +189,10 @@ struct Input<'a> {
|
||||||
impl<'a> Input<'a> {
|
impl<'a> Input<'a> {
|
||||||
/// Instantiate this struct with stdin as a source.
|
/// Instantiate this struct with stdin as a source.
|
||||||
fn new_stdin(settings: &'a Settings) -> UResult<Self> {
|
fn new_stdin(settings: &'a Settings) -> UResult<Self> {
|
||||||
|
#[cfg(not(unix))]
|
||||||
let mut src = Source::Stdin(io::stdin());
|
let mut src = Source::Stdin(io::stdin());
|
||||||
|
#[cfg(unix)]
|
||||||
|
let mut src = Source::stdin_as_file();
|
||||||
if settings.skip > 0 {
|
if settings.skip > 0 {
|
||||||
src.skip(settings.skip)?;
|
src.skip(settings.skip)?;
|
||||||
}
|
}
|
||||||
|
|
8
src/uu/fold/fold.md
Normal file
8
src/uu/fold/fold.md
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
# fold
|
||||||
|
|
||||||
|
```
|
||||||
|
fold [OPTION]... [FILE]...
|
||||||
|
```
|
||||||
|
|
||||||
|
Writes each file (or standard input if no files are given)
|
||||||
|
to standard output whilst breaking long lines
|
|
@ -13,13 +13,12 @@ use std::io::{stdin, BufRead, BufReader, Read};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use uucore::display::Quotable;
|
use uucore::display::Quotable;
|
||||||
use uucore::error::{FromIo, UResult, USimpleError};
|
use uucore::error::{FromIo, UResult, USimpleError};
|
||||||
use uucore::format_usage;
|
use uucore::{format_usage, help_about, help_usage};
|
||||||
|
|
||||||
const TAB_WIDTH: usize = 8;
|
const TAB_WIDTH: usize = 8;
|
||||||
|
|
||||||
static USAGE: &str = "{} [OPTION]... [FILE]...";
|
const USAGE: &str = help_usage!("fold.md");
|
||||||
static ABOUT: &str = "Writes each file (or standard input if no files are given)
|
const ABOUT: &str = help_about!("fold.md");
|
||||||
to standard output whilst breaking long lines";
|
|
||||||
|
|
||||||
mod options {
|
mod options {
|
||||||
pub const BYTES: &str = "bytes";
|
pub const BYTES: &str = "bytes";
|
||||||
|
|
7
src/uu/mkfifo/mkfifo.md
Normal file
7
src/uu/mkfifo/mkfifo.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# mkfifo
|
||||||
|
|
||||||
|
```
|
||||||
|
mkfifo [OPTION]... NAME...
|
||||||
|
```
|
||||||
|
|
||||||
|
Create a FIFO with the given name.
|
|
@ -10,10 +10,10 @@ use libc::mkfifo;
|
||||||
use std::ffi::CString;
|
use std::ffi::CString;
|
||||||
use uucore::display::Quotable;
|
use uucore::display::Quotable;
|
||||||
use uucore::error::{UResult, USimpleError};
|
use uucore::error::{UResult, USimpleError};
|
||||||
use uucore::{format_usage, show};
|
use uucore::{format_usage, help_about, help_usage, show};
|
||||||
|
|
||||||
static USAGE: &str = "{} [OPTION]... NAME...";
|
static USAGE: &str = help_usage!("mkfifo.md");
|
||||||
static ABOUT: &str = "Create a FIFO with the given name.";
|
static ABOUT: &str = help_about!("mkfifo.md");
|
||||||
|
|
||||||
mod options {
|
mod options {
|
||||||
pub static MODE: &str = "mode";
|
pub static MODE: &str = "mode";
|
||||||
|
|
25
src/uu/mknod/mknod.md
Normal file
25
src/uu/mknod/mknod.md
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
# mknod
|
||||||
|
|
||||||
|
```
|
||||||
|
mknod [OPTION]... NAME TYPE [MAJOR MINOR]
|
||||||
|
```
|
||||||
|
|
||||||
|
Create the special file NAME of the given TYPE.
|
||||||
|
|
||||||
|
## After Help
|
||||||
|
|
||||||
|
Mandatory arguments to long options are mandatory for short options too.
|
||||||
|
`-m`, `--mode=MODE` set file permission bits to `MODE`, not `a=rw - umask`
|
||||||
|
|
||||||
|
Both `MAJOR` and `MINOR` must be specified when `TYPE` is `b`, `c`, or `u`, and they
|
||||||
|
must be omitted when `TYPE` is `p`. If `MAJOR` or `MINOR` begins with `0x` or `0X`,
|
||||||
|
it is interpreted as hexadecimal; otherwise, if it begins with 0, as octal;
|
||||||
|
otherwise, as decimal. `TYPE` may be:
|
||||||
|
|
||||||
|
* `b` create a block (buffered) special file
|
||||||
|
* `c`, `u` create a character (unbuffered) special file
|
||||||
|
* `p` create a FIFO
|
||||||
|
|
||||||
|
NOTE: your shell may have its own version of mknod, which usually supersedes
|
||||||
|
the version described here. Please refer to your shell's documentation
|
||||||
|
for details about the options it supports.
|
|
@ -14,28 +14,11 @@ use std::ffi::CString;
|
||||||
|
|
||||||
use uucore::display::Quotable;
|
use uucore::display::Quotable;
|
||||||
use uucore::error::{set_exit_code, UResult, USimpleError, UUsageError};
|
use uucore::error::{set_exit_code, UResult, USimpleError, UUsageError};
|
||||||
use uucore::format_usage;
|
use uucore::{format_usage, help_about, help_section, help_usage};
|
||||||
|
|
||||||
static ABOUT: &str = "Create the special file NAME of the given TYPE.";
|
const ABOUT: &str = help_about!("mknod.md");
|
||||||
static USAGE: &str = "{} [OPTION]... NAME TYPE [MAJOR MINOR]";
|
const USAGE: &str = help_usage!("mknod.md");
|
||||||
static LONG_HELP: &str = "Mandatory arguments to long options are mandatory for short options too.
|
const AFTER_HELP: &str = help_section!("after help", "mknod.md");
|
||||||
-m, --mode=MODE set file permission bits to MODE, not a=rw - umask
|
|
||||||
--help display this help and exit
|
|
||||||
--version output version information and exit
|
|
||||||
|
|
||||||
Both MAJOR and MINOR must be specified when TYPE is b, c, or u, and they
|
|
||||||
must be omitted when TYPE is p. If MAJOR or MINOR begins with 0x or 0X,
|
|
||||||
it is interpreted as hexadecimal; otherwise, if it begins with 0, as octal;
|
|
||||||
otherwise, as decimal. TYPE may be:
|
|
||||||
|
|
||||||
b create a block (buffered) special file
|
|
||||||
c, u create a character (unbuffered) special file
|
|
||||||
p create a FIFO
|
|
||||||
|
|
||||||
NOTE: your shell may have its own version of mknod, which usually supersedes
|
|
||||||
the version described here. Please refer to your shell's documentation
|
|
||||||
for details about the options it supports.
|
|
||||||
";
|
|
||||||
|
|
||||||
const MODE_RW_UGO: mode_t = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
|
const MODE_RW_UGO: mode_t = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
|
||||||
|
|
||||||
|
@ -142,7 +125,7 @@ pub fn uu_app() -> Command {
|
||||||
Command::new(uucore::util_name())
|
Command::new(uucore::util_name())
|
||||||
.version(crate_version!())
|
.version(crate_version!())
|
||||||
.override_usage(format_usage(USAGE))
|
.override_usage(format_usage(USAGE))
|
||||||
.after_help(LONG_HELP)
|
.after_help(AFTER_HELP)
|
||||||
.about(ABOUT)
|
.about(ABOUT)
|
||||||
.infer_long_args(true)
|
.infer_long_args(true)
|
||||||
.arg(
|
.arg(
|
||||||
|
|
7
src/uu/mktemp/mktemp.md
Normal file
7
src/uu/mktemp/mktemp.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# mktemp
|
||||||
|
|
||||||
|
```
|
||||||
|
mktemp [OPTION]... [TEMPLATE]
|
||||||
|
```
|
||||||
|
|
||||||
|
Create a temporary file or directory.
|
|
@ -11,7 +11,7 @@
|
||||||
use clap::{crate_version, Arg, ArgAction, ArgMatches, Command};
|
use clap::{crate_version, Arg, ArgAction, ArgMatches, Command};
|
||||||
use uucore::display::{println_verbatim, Quotable};
|
use uucore::display::{println_verbatim, Quotable};
|
||||||
use uucore::error::{FromIo, UError, UResult, UUsageError};
|
use uucore::error::{FromIo, UError, UResult, UUsageError};
|
||||||
use uucore::format_usage;
|
use uucore::{format_usage, help_about, help_usage};
|
||||||
|
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
@ -28,8 +28,8 @@ use std::os::unix::prelude::PermissionsExt;
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use tempfile::Builder;
|
use tempfile::Builder;
|
||||||
|
|
||||||
static ABOUT: &str = "Create a temporary file or directory.";
|
const ABOUT: &str = help_about!("mktemp.md");
|
||||||
const USAGE: &str = "{} [OPTION]... [TEMPLATE]";
|
const USAGE: &str = help_usage!("mktemp.md");
|
||||||
|
|
||||||
static DEFAULT_TEMPLATE: &str = "tmp.XXXXXXXXXX";
|
static DEFAULT_TEMPLATE: &str = "tmp.XXXXXXXXXX";
|
||||||
|
|
||||||
|
|
|
@ -15,10 +15,10 @@ use uucore::{format_usage, help_about, help_usage};
|
||||||
use uucore::display::println_verbatim;
|
use uucore::display::println_verbatim;
|
||||||
use uucore::error::{FromIo, UResult};
|
use uucore::error::{FromIo, UResult};
|
||||||
|
|
||||||
static ABOUT: &str = help_about!("pwd.md");
|
const ABOUT: &str = help_about!("pwd.md");
|
||||||
const USAGE: &str = help_usage!("pwd.md");
|
const USAGE: &str = help_usage!("pwd.md");
|
||||||
static OPT_LOGICAL: &str = "logical";
|
const OPT_LOGICAL: &str = "logical";
|
||||||
static OPT_PHYSICAL: &str = "physical";
|
const OPT_PHYSICAL: &str = "physical";
|
||||||
|
|
||||||
fn physical_path() -> io::Result<PathBuf> {
|
fn physical_path() -> io::Result<PathBuf> {
|
||||||
// std::env::current_dir() is a thin wrapper around libc::getcwd().
|
// std::env::current_dir() is a thin wrapper around libc::getcwd().
|
||||||
|
@ -84,36 +84,22 @@ fn logical_path() -> io::Result<PathBuf> {
|
||||||
{
|
{
|
||||||
use std::fs::metadata;
|
use std::fs::metadata;
|
||||||
use std::os::unix::fs::MetadataExt;
|
use std::os::unix::fs::MetadataExt;
|
||||||
let path_info = match metadata(path) {
|
match (metadata(path), metadata(".")) {
|
||||||
Ok(info) => info,
|
(Ok(info1), Ok(info2)) => {
|
||||||
Err(_) => return false,
|
info1.dev() == info2.dev() && info1.ino() == info2.ino()
|
||||||
};
|
}
|
||||||
let real_info = match metadata(".") {
|
_ => false,
|
||||||
Ok(info) => info,
|
|
||||||
Err(_) => return false,
|
|
||||||
};
|
|
||||||
if path_info.dev() != real_info.dev() || path_info.ino() != real_info.ino() {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(unix))]
|
#[cfg(not(unix))]
|
||||||
{
|
{
|
||||||
use std::fs::canonicalize;
|
use std::fs::canonicalize;
|
||||||
let canon_path = match canonicalize(path) {
|
match (canonicalize(path), canonicalize(".")) {
|
||||||
Ok(path) => path,
|
(Ok(path1), Ok(path2)) => path1 == path2,
|
||||||
Err(_) => return false,
|
_ => false,
|
||||||
};
|
|
||||||
let real_path = match canonicalize(".") {
|
|
||||||
Ok(path) => path,
|
|
||||||
Err(_) => return false,
|
|
||||||
};
|
|
||||||
if canon_path != real_path {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
match env::var_os("PWD").map(PathBuf::from) {
|
match env::var_os("PWD").map(PathBuf::from) {
|
||||||
|
|
8
src/uu/relpath/relpath.md
Normal file
8
src/uu/relpath/relpath.md
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
# relpath
|
||||||
|
|
||||||
|
```
|
||||||
|
relpath [-d DIR] TO [FROM]
|
||||||
|
```
|
||||||
|
|
||||||
|
Convert TO destination to the relative path from the FROM dir.
|
||||||
|
If FROM path is omitted, current working dir will be used.
|
|
@ -12,12 +12,11 @@ use std::env;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use uucore::display::println_verbatim;
|
use uucore::display::println_verbatim;
|
||||||
use uucore::error::{FromIo, UResult};
|
use uucore::error::{FromIo, UResult};
|
||||||
use uucore::format_usage;
|
|
||||||
use uucore::fs::{canonicalize, MissingHandling, ResolveMode};
|
use uucore::fs::{canonicalize, MissingHandling, ResolveMode};
|
||||||
|
use uucore::{format_usage, help_about, help_usage};
|
||||||
|
|
||||||
static ABOUT: &str = "Convert TO destination to the relative path from the FROM dir.
|
const USAGE: &str = help_usage!("relpath.md");
|
||||||
If FROM path is omitted, current working dir will be used.";
|
const ABOUT: &str = help_about!("relpath.md");
|
||||||
const USAGE: &str = "{} [-d DIR] TO [FROM]";
|
|
||||||
|
|
||||||
mod options {
|
mod options {
|
||||||
pub const DIR: &str = "DIR";
|
pub const DIR: &str = "DIR";
|
||||||
|
|
|
@ -16,6 +16,7 @@ path = "src/sleep.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { workspace=true }
|
clap = { workspace=true }
|
||||||
|
fundu = { workspace=true }
|
||||||
uucore = { workspace=true }
|
uucore = { workspace=true }
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
|
|
|
@ -14,6 +14,7 @@ use uucore::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use clap::{crate_version, Arg, ArgAction, Command};
|
use clap::{crate_version, Arg, ArgAction, Command};
|
||||||
|
use fundu::{self, DurationParser, ParseError};
|
||||||
|
|
||||||
static ABOUT: &str = help_about!("sleep.md");
|
static ABOUT: &str = help_about!("sleep.md");
|
||||||
const USAGE: &str = help_usage!("sleep.md");
|
const USAGE: &str = help_usage!("sleep.md");
|
||||||
|
@ -61,14 +62,34 @@ pub fn uu_app() -> Command {
|
||||||
|
|
||||||
fn sleep(args: &[&str]) -> UResult<()> {
|
fn sleep(args: &[&str]) -> UResult<()> {
|
||||||
let mut arg_error = false;
|
let mut arg_error = false;
|
||||||
|
|
||||||
|
use fundu::TimeUnit::*;
|
||||||
|
let parser = DurationParser::with_time_units(&[Second, Minute, Hour, Day]);
|
||||||
|
|
||||||
let sleep_dur = args
|
let sleep_dur = args
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|input| {
|
.filter_map(|input| match parser.parse(input.trim()) {
|
||||||
uucore::parse_time::from_str(input.trim()).ok().or_else(|| {
|
Ok(duration) => Some(duration),
|
||||||
|
Err(error) => {
|
||||||
arg_error = true;
|
arg_error = true;
|
||||||
show_error!("invalid time interval '{input}'");
|
|
||||||
|
let reason = match error {
|
||||||
|
ParseError::Empty if input.is_empty() => "Input was empty".to_string(),
|
||||||
|
ParseError::Empty => "Found only whitespace in input".to_string(),
|
||||||
|
ParseError::Syntax(pos, description)
|
||||||
|
| ParseError::TimeUnit(pos, description) => {
|
||||||
|
format!("{description} at position {}", pos.saturating_add(1))
|
||||||
|
}
|
||||||
|
ParseError::NegativeExponentOverflow | ParseError::PositiveExponentOverflow => {
|
||||||
|
"Exponent was out of bounds".to_string()
|
||||||
|
}
|
||||||
|
ParseError::NegativeNumber => "Number was negative".to_string(),
|
||||||
|
error => error.to_string(),
|
||||||
|
};
|
||||||
|
show_error!("invalid time interval '{input}': {reason}");
|
||||||
|
|
||||||
None
|
None
|
||||||
})
|
}
|
||||||
})
|
})
|
||||||
.fold(Duration::ZERO, |acc, n| acc.saturating_add(n));
|
.fold(Duration::ZERO, |acc, n| acc.saturating_add(n));
|
||||||
|
|
||||||
|
|
|
@ -196,6 +196,22 @@ fn report_if_verbose(signal: usize, cmd: &str, verbose: bool) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn send_signal(process: &mut Child, signal: usize, foreground: bool) {
|
||||||
|
// NOTE: GNU timeout doesn't check for errors of signal.
|
||||||
|
// The subprocess might have exited just after the timeout.
|
||||||
|
// Sending a signal now would return "No such process", but we should still try to kill the children.
|
||||||
|
_ = process.send_signal(signal);
|
||||||
|
if !foreground {
|
||||||
|
_ = process.send_signal_group(signal);
|
||||||
|
let kill_signal = signal_by_name_or_value("KILL").unwrap();
|
||||||
|
let continued_signal = signal_by_name_or_value("CONT").unwrap();
|
||||||
|
if signal != kill_signal && signal != continued_signal {
|
||||||
|
_ = process.send_signal(continued_signal);
|
||||||
|
_ = process.send_signal_group(continued_signal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Wait for a child process and send a kill signal if it does not terminate.
|
/// Wait for a child process and send a kill signal if it does not terminate.
|
||||||
///
|
///
|
||||||
/// This function waits for the child `process` for the time period
|
/// This function waits for the child `process` for the time period
|
||||||
|
@ -217,10 +233,11 @@ fn report_if_verbose(signal: usize, cmd: &str, verbose: bool) {
|
||||||
/// If there is a problem sending the `SIGKILL` signal or waiting for
|
/// If there is a problem sending the `SIGKILL` signal or waiting for
|
||||||
/// the process after that signal is sent.
|
/// the process after that signal is sent.
|
||||||
fn wait_or_kill_process(
|
fn wait_or_kill_process(
|
||||||
mut process: Child,
|
process: &mut Child,
|
||||||
cmd: &str,
|
cmd: &str,
|
||||||
duration: Duration,
|
duration: Duration,
|
||||||
preserve_status: bool,
|
preserve_status: bool,
|
||||||
|
foreground: bool,
|
||||||
verbose: bool,
|
verbose: bool,
|
||||||
) -> std::io::Result<i32> {
|
) -> std::io::Result<i32> {
|
||||||
match process.wait_or_timeout(duration) {
|
match process.wait_or_timeout(duration) {
|
||||||
|
@ -234,7 +251,7 @@ fn wait_or_kill_process(
|
||||||
Ok(None) => {
|
Ok(None) => {
|
||||||
let signal = signal_by_name_or_value("KILL").unwrap();
|
let signal = signal_by_name_or_value("KILL").unwrap();
|
||||||
report_if_verbose(signal, cmd, verbose);
|
report_if_verbose(signal, cmd, verbose);
|
||||||
process.send_signal(signal)?;
|
send_signal(process, signal, foreground);
|
||||||
process.wait()?;
|
process.wait()?;
|
||||||
Ok(ExitStatus::SignalSent(signal).into())
|
Ok(ExitStatus::SignalSent(signal).into())
|
||||||
}
|
}
|
||||||
|
@ -300,7 +317,7 @@ fn timeout(
|
||||||
|
|
||||||
enable_pipe_errors()?;
|
enable_pipe_errors()?;
|
||||||
|
|
||||||
let mut process = process::Command::new(&cmd[0])
|
let process = &mut process::Command::new(&cmd[0])
|
||||||
.args(&cmd[1..])
|
.args(&cmd[1..])
|
||||||
.stdin(Stdio::inherit())
|
.stdin(Stdio::inherit())
|
||||||
.stdout(Stdio::inherit())
|
.stdout(Stdio::inherit())
|
||||||
|
@ -335,7 +352,7 @@ fn timeout(
|
||||||
.into()),
|
.into()),
|
||||||
Ok(None) => {
|
Ok(None) => {
|
||||||
report_if_verbose(signal, &cmd[0], verbose);
|
report_if_verbose(signal, &cmd[0], verbose);
|
||||||
process.send_signal(signal)?;
|
send_signal(process, signal, foreground);
|
||||||
match kill_after {
|
match kill_after {
|
||||||
None => {
|
None => {
|
||||||
if preserve_status {
|
if preserve_status {
|
||||||
|
@ -350,6 +367,7 @@ fn timeout(
|
||||||
&cmd[0],
|
&cmd[0],
|
||||||
kill_after,
|
kill_after,
|
||||||
preserve_status,
|
preserve_status,
|
||||||
|
foreground,
|
||||||
verbose,
|
verbose,
|
||||||
) {
|
) {
|
||||||
Ok(status) => Err(status.into()),
|
Ok(status) => Err(status.into()),
|
||||||
|
@ -363,11 +381,8 @@ fn timeout(
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
// We're going to return ERR_EXIT_STATUS regardless of
|
// We're going to return ERR_EXIT_STATUS regardless of
|
||||||
// whether `send_signal()` succeeds or fails, so just
|
// whether `send_signal()` succeeds or fails
|
||||||
// ignore the return value.
|
send_signal(process, signal, foreground);
|
||||||
process
|
|
||||||
.send_signal(signal)
|
|
||||||
.map_err(|e| USimpleError::new(ExitStatus::TimeoutFailed.into(), format!("{e}")))?;
|
|
||||||
Err(ExitStatus::TimeoutFailed.into())
|
Err(ExitStatus::TimeoutFailed.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,17 +11,13 @@
|
||||||
use chrono::{Local, TimeZone, Utc};
|
use chrono::{Local, TimeZone, Utc};
|
||||||
use clap::{crate_version, Arg, ArgAction, Command};
|
use clap::{crate_version, Arg, ArgAction, Command};
|
||||||
|
|
||||||
use uucore::format_usage;
|
|
||||||
// import crate time from utmpx
|
|
||||||
pub use uucore::libc;
|
|
||||||
use uucore::libc::time_t;
|
use uucore::libc::time_t;
|
||||||
|
use uucore::{format_usage, help_about, help_usage};
|
||||||
|
|
||||||
use uucore::error::{UResult, USimpleError};
|
use uucore::error::{UResult, USimpleError};
|
||||||
|
|
||||||
static ABOUT: &str = "Display the current time, the length of time the system has been up,\n\
|
const ABOUT: &str = help_about!("uptime.md");
|
||||||
the number of users on the system, and the average number of jobs\n\
|
const USAGE: &str = help_usage!("uptime.md");
|
||||||
in the run queue over the last 1, 5 and 15 minutes.";
|
|
||||||
const USAGE: &str = "{} [OPTION]...";
|
|
||||||
pub mod options {
|
pub mod options {
|
||||||
pub static SINCE: &str = "since";
|
pub static SINCE: &str = "since";
|
||||||
}
|
}
|
||||||
|
|
9
src/uu/uptime/uptime.md
Normal file
9
src/uu/uptime/uptime.md
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
# uptime
|
||||||
|
|
||||||
|
```
|
||||||
|
uptime [OPTION]...
|
||||||
|
```
|
||||||
|
|
||||||
|
Display the current time, the length of time the system has been up,
|
||||||
|
the number of users on the system, and the average number of jobs
|
||||||
|
in the run queue over the last 1, 5 and 15 minutes.
|
|
@ -18,7 +18,7 @@ use std::ffi::CStr;
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
use std::os::unix::fs::MetadataExt;
|
use std::os::unix::fs::MetadataExt;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use uucore::format_usage;
|
use uucore::{format_usage, help_about, help_usage};
|
||||||
|
|
||||||
mod options {
|
mod options {
|
||||||
pub const ALL: &str = "all";
|
pub const ALL: &str = "all";
|
||||||
|
@ -38,8 +38,8 @@ mod options {
|
||||||
pub const FILE: &str = "FILE"; // if length=1: FILE, if length=2: ARG1 ARG2
|
pub const FILE: &str = "FILE"; // if length=1: FILE, if length=2: ARG1 ARG2
|
||||||
}
|
}
|
||||||
|
|
||||||
static ABOUT: &str = "Print information about users who are currently logged in.";
|
const ABOUT: &str = help_about!("who.md");
|
||||||
const USAGE: &str = "{} [OPTION]... [ FILE | ARG1 ARG2 ]";
|
const USAGE: &str = help_usage!("who.md");
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
static RUNLEVEL_HELP: &str = "print current runlevel";
|
static RUNLEVEL_HELP: &str = "print current runlevel";
|
||||||
|
|
8
src/uu/who/who.md
Normal file
8
src/uu/who/who.md
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
# who
|
||||||
|
|
||||||
|
```
|
||||||
|
who [OPTION]... [ FILE | ARG1 ARG2 ]
|
||||||
|
```
|
||||||
|
|
||||||
|
Print information about users who are currently logged in.
|
||||||
|
|
|
@ -11,10 +11,12 @@ use clap::{crate_version, Command};
|
||||||
|
|
||||||
use uucore::display::println_verbatim;
|
use uucore::display::println_verbatim;
|
||||||
use uucore::error::{FromIo, UResult};
|
use uucore::error::{FromIo, UResult};
|
||||||
|
use uucore::{format_usage, help_about, help_usage};
|
||||||
|
|
||||||
mod platform;
|
mod platform;
|
||||||
|
|
||||||
static ABOUT: &str = "Print the current username.";
|
const ABOUT: &str = help_about!("whoami.md");
|
||||||
|
const USAGE: &str = help_usage!("whoami.md");
|
||||||
|
|
||||||
#[uucore::main]
|
#[uucore::main]
|
||||||
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
|
||||||
|
@ -28,5 +30,6 @@ pub fn uu_app() -> Command {
|
||||||
Command::new(uucore::util_name())
|
Command::new(uucore::util_name())
|
||||||
.version(crate_version!())
|
.version(crate_version!())
|
||||||
.about(ABOUT)
|
.about(ABOUT)
|
||||||
|
.override_usage(format_usage(USAGE))
|
||||||
.infer_long_args(true)
|
.infer_long_args(true)
|
||||||
}
|
}
|
||||||
|
|
7
src/uu/whoami/whoami.md
Normal file
7
src/uu/whoami/whoami.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# whoami
|
||||||
|
|
||||||
|
```
|
||||||
|
whoami
|
||||||
|
```
|
||||||
|
|
||||||
|
Print the current username.
|
|
@ -122,6 +122,15 @@ fn parse_change(mode: &str, fperm: u32, considering_dir: bool) -> (u32, usize) {
|
||||||
'o' => srwx = ((fperm << 6) & 0o700) | ((fperm << 3) & 0o070) | (fperm & 0o007),
|
'o' => srwx = ((fperm << 6) & 0o700) | ((fperm << 3) & 0o070) | (fperm & 0o007),
|
||||||
_ => break,
|
_ => break,
|
||||||
};
|
};
|
||||||
|
if ch == 'u' || ch == 'g' || ch == 'o' {
|
||||||
|
// symbolic modes only allows perms to be a single letter of 'ugo'
|
||||||
|
// therefore this must either be the first char or it is unexpected
|
||||||
|
if pos != 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
pos = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
pos += 1;
|
pos += 1;
|
||||||
}
|
}
|
||||||
if pos == 0 {
|
if pos == 0 {
|
||||||
|
|
|
@ -48,6 +48,9 @@ pub trait ChildExt {
|
||||||
/// send the signal to an unrelated process that recycled the PID.
|
/// send the signal to an unrelated process that recycled the PID.
|
||||||
fn send_signal(&mut self, signal: usize) -> io::Result<()>;
|
fn send_signal(&mut self, signal: usize) -> io::Result<()>;
|
||||||
|
|
||||||
|
/// Send a signal to a process group.
|
||||||
|
fn send_signal_group(&mut self, signal: usize) -> io::Result<()>;
|
||||||
|
|
||||||
/// Wait for a process to finish or return after the specified duration.
|
/// Wait for a process to finish or return after the specified duration.
|
||||||
/// A `timeout` of zero disables the timeout.
|
/// A `timeout` of zero disables the timeout.
|
||||||
fn wait_or_timeout(&mut self, timeout: Duration) -> io::Result<Option<ExitStatus>>;
|
fn wait_or_timeout(&mut self, timeout: Duration) -> io::Result<Option<ExitStatus>>;
|
||||||
|
@ -62,6 +65,18 @@ impl ChildExt for Child {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn send_signal_group(&mut self, signal: usize) -> io::Result<()> {
|
||||||
|
// Ignore the signal, so we don't go into a signal loop.
|
||||||
|
if unsafe { libc::signal(signal as i32, libc::SIG_IGN) } != 0 {
|
||||||
|
return Err(io::Error::last_os_error());
|
||||||
|
}
|
||||||
|
if unsafe { libc::kill(0, signal as i32) } != 0 {
|
||||||
|
Err(io::Error::last_os_error())
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn wait_or_timeout(&mut self, timeout: Duration) -> io::Result<Option<ExitStatus>> {
|
fn wait_or_timeout(&mut self, timeout: Duration) -> io::Result<Option<ExitStatus>> {
|
||||||
if timeout == Duration::from_micros(0) {
|
if timeout == Duration::from_micros(0) {
|
||||||
return self.wait().map(Some);
|
return self.wait().map(Some);
|
||||||
|
|
|
@ -4,9 +4,8 @@ use std::fs::{metadata, set_permissions, OpenOptions, Permissions};
|
||||||
use std::os::unix::fs::{OpenOptionsExt, PermissionsExt};
|
use std::os::unix::fs::{OpenOptionsExt, PermissionsExt};
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
extern crate libc;
|
|
||||||
use uucore::mode::strip_minus_from_mode;
|
|
||||||
extern crate chmod;
|
extern crate chmod;
|
||||||
|
extern crate libc;
|
||||||
use self::libc::umask;
|
use self::libc::umask;
|
||||||
|
|
||||||
static TEST_FILE: &str = "file";
|
static TEST_FILE: &str = "file";
|
||||||
|
@ -503,35 +502,6 @@ fn test_chmod_symlink_non_existing_file_recursive() {
|
||||||
.no_stderr();
|
.no_stderr();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_chmod_strip_minus_from_mode() {
|
|
||||||
let tests = vec![
|
|
||||||
// ( before, after )
|
|
||||||
("chmod -v -xw -R FILE", "chmod -v xw -R FILE"),
|
|
||||||
("chmod g=rwx FILE -c", "chmod g=rwx FILE -c"),
|
|
||||||
(
|
|
||||||
"chmod -c -R -w,o+w FILE --preserve-root",
|
|
||||||
"chmod -c -R w,o+w FILE --preserve-root",
|
|
||||||
),
|
|
||||||
("chmod -c -R +w FILE ", "chmod -c -R +w FILE "),
|
|
||||||
("chmod a=r,=xX FILE", "chmod a=r,=xX FILE"),
|
|
||||||
(
|
|
||||||
"chmod -v --reference REF_FILE -R FILE",
|
|
||||||
"chmod -v --reference REF_FILE -R FILE",
|
|
||||||
),
|
|
||||||
("chmod -Rvc -w-x FILE", "chmod -Rvc w-x FILE"),
|
|
||||||
("chmod 755 -v FILE", "chmod 755 -v FILE"),
|
|
||||||
("chmod -v +0004 FILE -R", "chmod -v +0004 FILE -R"),
|
|
||||||
("chmod -v -0007 FILE -R", "chmod -v 0007 FILE -R"),
|
|
||||||
];
|
|
||||||
|
|
||||||
for test in tests {
|
|
||||||
let mut args: Vec<String> = test.0.split(' ').map(|v| v.to_string()).collect();
|
|
||||||
let _mode_had_minus_prefix = strip_minus_from_mode(&mut args);
|
|
||||||
assert_eq!(test.1, args.join(" "));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_chmod_keep_setgid() {
|
fn test_chmod_keep_setgid() {
|
||||||
for (from, arg, to) in [
|
for (from, arg, to) in [
|
||||||
|
@ -671,3 +641,68 @@ fn test_quiet_n_verbose_used_multiple_times() {
|
||||||
.arg("file")
|
.arg("file")
|
||||||
.succeeds();
|
.succeeds();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_gnu_invalid_mode() {
|
||||||
|
let scene = TestScenario::new(util_name!());
|
||||||
|
let at = &scene.fixtures;
|
||||||
|
at.touch("file");
|
||||||
|
scene.ucmd().arg("u+gr").arg("file").fails();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_gnu_options() {
|
||||||
|
let scene = TestScenario::new(util_name!());
|
||||||
|
let at = &scene.fixtures;
|
||||||
|
at.touch("file");
|
||||||
|
scene.ucmd().arg("-w").arg("file").succeeds();
|
||||||
|
scene.ucmd().arg("file").arg("-w").succeeds();
|
||||||
|
scene.ucmd().arg("-w").arg("--").arg("file").succeeds();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_gnu_repeating_options() {
|
||||||
|
let scene = TestScenario::new(util_name!());
|
||||||
|
let at = &scene.fixtures;
|
||||||
|
at.touch("file");
|
||||||
|
scene.ucmd().arg("-w").arg("-w").arg("file").succeeds();
|
||||||
|
scene
|
||||||
|
.ucmd()
|
||||||
|
.arg("-w")
|
||||||
|
.arg("-w")
|
||||||
|
.arg("-w")
|
||||||
|
.arg("file")
|
||||||
|
.succeeds();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_gnu_special_filenames() {
|
||||||
|
let scene = TestScenario::new(util_name!());
|
||||||
|
let at = &scene.fixtures;
|
||||||
|
let perms_before = Permissions::from_mode(0o100640);
|
||||||
|
let perms_after = Permissions::from_mode(0o100440);
|
||||||
|
|
||||||
|
make_file(&at.plus_as_string("--"), perms_before.mode());
|
||||||
|
scene.ucmd().arg("-w").arg("--").arg("--").succeeds();
|
||||||
|
assert_eq!(at.metadata("--").permissions(), perms_after);
|
||||||
|
set_permissions(at.plus("--"), perms_before.clone()).unwrap();
|
||||||
|
scene.ucmd().arg("--").arg("-w").arg("--").succeeds();
|
||||||
|
assert_eq!(at.metadata("--").permissions(), perms_after);
|
||||||
|
at.remove("--");
|
||||||
|
|
||||||
|
make_file(&at.plus_as_string("-w"), perms_before.mode());
|
||||||
|
scene.ucmd().arg("-w").arg("--").arg("-w").succeeds();
|
||||||
|
assert_eq!(at.metadata("-w").permissions(), perms_after);
|
||||||
|
set_permissions(at.plus("-w"), perms_before).unwrap();
|
||||||
|
scene.ucmd().arg("--").arg("-w").arg("-w").succeeds();
|
||||||
|
assert_eq!(at.metadata("-w").permissions(), perms_after);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_gnu_special_options() {
|
||||||
|
let scene = TestScenario::new(util_name!());
|
||||||
|
let at = &scene.fixtures;
|
||||||
|
at.touch("file");
|
||||||
|
scene.ucmd().arg("--").arg("--").arg("file").succeeds();
|
||||||
|
scene.ucmd().arg("--").arg("--").fails();
|
||||||
|
}
|
||||||
|
|
|
@ -44,16 +44,91 @@ fn test_date_rfc_3339() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_date_rfc_8601() {
|
fn test_date_rfc_3339_invalid_arg() {
|
||||||
|
for param in ["--iso-3339", "--rfc-3"] {
|
||||||
|
new_ucmd!().arg(format!("{param}=foo")).fails();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_date_rfc_8601_default() {
|
||||||
|
let re = Regex::new(r"^\d{4}-\d{2}-\d{2}\n$").unwrap();
|
||||||
for param in ["--iso-8601", "--i"] {
|
for param in ["--iso-8601", "--i"] {
|
||||||
new_ucmd!().arg(format!("{param}=ns")).succeeds();
|
new_ucmd!().arg(param).succeeds().stdout_matches(&re);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_date_rfc_8601() {
|
||||||
|
let re = Regex::new(r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2},\d{9}[+-]\d{2}:\d{2}\n$").unwrap();
|
||||||
|
for param in ["--iso-8601", "--i"] {
|
||||||
|
new_ucmd!()
|
||||||
|
.arg(format!("{param}=ns"))
|
||||||
|
.succeeds()
|
||||||
|
.stdout_matches(&re);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_date_rfc_8601_invalid_arg() {
|
||||||
|
for param in ["--iso-8601", "--i"] {
|
||||||
|
new_ucmd!().arg(format!("{param}=@")).fails();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_date_rfc_8601_second() {
|
fn test_date_rfc_8601_second() {
|
||||||
|
let re = Regex::new(r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[+-]\d{2}:\d{2}\n$").unwrap();
|
||||||
for param in ["--iso-8601", "--i"] {
|
for param in ["--iso-8601", "--i"] {
|
||||||
new_ucmd!().arg(format!("{param}=second")).succeeds();
|
new_ucmd!()
|
||||||
|
.arg(format!("{param}=second"))
|
||||||
|
.succeeds()
|
||||||
|
.stdout_matches(&re);
|
||||||
|
new_ucmd!()
|
||||||
|
.arg(format!("{param}=seconds"))
|
||||||
|
.succeeds()
|
||||||
|
.stdout_matches(&re);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_date_rfc_8601_minute() {
|
||||||
|
let re = Regex::new(r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}[+-]\d{2}:\d{2}\n$").unwrap();
|
||||||
|
for param in ["--iso-8601", "--i"] {
|
||||||
|
new_ucmd!()
|
||||||
|
.arg(format!("{param}=minute"))
|
||||||
|
.succeeds()
|
||||||
|
.stdout_matches(&re);
|
||||||
|
new_ucmd!()
|
||||||
|
.arg(format!("{param}=minutes"))
|
||||||
|
.succeeds()
|
||||||
|
.stdout_matches(&re);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_date_rfc_8601_hour() {
|
||||||
|
let re = Regex::new(r"^\d{4}-\d{2}-\d{2}T\d{2}[+-]\d{2}:\d{2}\n$").unwrap();
|
||||||
|
for param in ["--iso-8601", "--i"] {
|
||||||
|
new_ucmd!()
|
||||||
|
.arg(format!("{param}=hour"))
|
||||||
|
.succeeds()
|
||||||
|
.stdout_matches(&re);
|
||||||
|
new_ucmd!()
|
||||||
|
.arg(format!("{param}=hours"))
|
||||||
|
.succeeds()
|
||||||
|
.stdout_matches(&re);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_date_rfc_8601_date() {
|
||||||
|
let re = Regex::new(r"^\d{4}-\d{2}-\d{2}\n$").unwrap();
|
||||||
|
for param in ["--iso-8601", "--i"] {
|
||||||
|
new_ucmd!()
|
||||||
|
.arg(format!("{param}=date"))
|
||||||
|
.succeeds()
|
||||||
|
.stdout_matches(&re);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,6 +273,24 @@ fn test_date_set_valid_2() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_date_for_invalid_file() {
|
||||||
|
let result = new_ucmd!().arg("--file").arg("invalid_file").fails();
|
||||||
|
result.no_stdout();
|
||||||
|
assert_eq!(
|
||||||
|
result.stderr_str().trim(),
|
||||||
|
"date: invalid_file: No such file or directory",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_date_for_file() {
|
||||||
|
let (at, mut ucmd) = at_and_ucmd!();
|
||||||
|
let file = "test_date_for_file";
|
||||||
|
at.touch(file);
|
||||||
|
ucmd.arg("--file").arg(file).succeeds();
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(all(unix, not(target_os = "macos")))]
|
#[cfg(all(unix, not(target_os = "macos")))]
|
||||||
/// TODO: expected to fail currently; change to succeeds() when required.
|
/// TODO: expected to fail currently; change to succeeds() when required.
|
||||||
|
@ -232,3 +325,13 @@ fn test_invalid_format_string() {
|
||||||
result.no_stdout();
|
result.no_stdout();
|
||||||
assert!(result.stderr_str().starts_with("date: invalid format "));
|
assert!(result.stderr_str().starts_with("date: invalid format "));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_invalid_date_string() {
|
||||||
|
new_ucmd!()
|
||||||
|
.arg("-d")
|
||||||
|
.arg("foo")
|
||||||
|
.fails()
|
||||||
|
.no_stdout()
|
||||||
|
.stderr_contains("invalid date");
|
||||||
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ use regex::Regex;
|
||||||
use std::fs::{File, OpenOptions};
|
use std::fs::{File, OpenOptions};
|
||||||
use std::io::{BufReader, Read, Write};
|
use std::io::{BufReader, Read, Write};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
#[cfg(all(not(windows), not(target_os = "macos")))]
|
#[cfg(all(unix, not(target_os = "macos"), not(target_os = "freebsd")))]
|
||||||
use std::process::{Command, Stdio};
|
use std::process::{Command, Stdio};
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
use std::thread::sleep;
|
use std::thread::sleep;
|
||||||
|
@ -1520,3 +1520,17 @@ fn test_skip_input_fifo() {
|
||||||
assert!(output.stdout.is_empty());
|
assert!(output.stdout.is_empty());
|
||||||
assert_eq!(&output.stderr, b"1+0 records in\n1+0 records out\n");
|
assert_eq!(&output.stderr, b"1+0 records in\n1+0 records out\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Test for reading part of stdin from each of two child processes.
|
||||||
|
#[cfg(all(not(windows), feature = "printf"))]
|
||||||
|
#[test]
|
||||||
|
fn test_multiple_processes_reading_stdin() {
|
||||||
|
// TODO Investigate if this is possible on Windows.
|
||||||
|
let printf = format!("{TESTS_BINARY} printf 'abcdef\n'");
|
||||||
|
let dd_skip = format!("{TESTS_BINARY} dd bs=1 skip=3 count=0");
|
||||||
|
let dd = format!("{TESTS_BINARY} dd");
|
||||||
|
UCommand::new()
|
||||||
|
.arg(format!("{printf} | ( {dd_skip} && {dd} ) 2> /dev/null"))
|
||||||
|
.succeeds()
|
||||||
|
.stdout_only("def\n");
|
||||||
|
}
|
||||||
|
|
|
@ -82,10 +82,10 @@ fn _du_basics_subdir(s: &str) {
|
||||||
))]
|
))]
|
||||||
fn _du_basics_subdir(s: &str) {
|
fn _du_basics_subdir(s: &str) {
|
||||||
// MS-WSL linux has altered expected output
|
// MS-WSL linux has altered expected output
|
||||||
if !uucore::os::is_wsl_1() {
|
if uucore::os::is_wsl_1() {
|
||||||
assert_eq!(s, "8\tsubdir/deeper\n");
|
|
||||||
} else {
|
|
||||||
assert_eq!(s, "0\tsubdir/deeper\n");
|
assert_eq!(s, "0\tsubdir/deeper\n");
|
||||||
|
} else {
|
||||||
|
assert_eq!(s, "8\tsubdir/deeper\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,10 +164,10 @@ fn _du_soft_link(s: &str) {
|
||||||
))]
|
))]
|
||||||
fn _du_soft_link(s: &str) {
|
fn _du_soft_link(s: &str) {
|
||||||
// MS-WSL linux has altered expected output
|
// MS-WSL linux has altered expected output
|
||||||
if !uucore::os::is_wsl_1() {
|
if uucore::os::is_wsl_1() {
|
||||||
assert_eq!(s, "16\tsubdir/links\n");
|
|
||||||
} else {
|
|
||||||
assert_eq!(s, "8\tsubdir/links\n");
|
assert_eq!(s, "8\tsubdir/links\n");
|
||||||
|
} else {
|
||||||
|
assert_eq!(s, "16\tsubdir/links\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,10 +212,10 @@ fn _du_hard_link(s: &str) {
|
||||||
))]
|
))]
|
||||||
fn _du_hard_link(s: &str) {
|
fn _du_hard_link(s: &str) {
|
||||||
// MS-WSL linux has altered expected output
|
// MS-WSL linux has altered expected output
|
||||||
if !uucore::os::is_wsl_1() {
|
if uucore::os::is_wsl_1() {
|
||||||
assert_eq!(s, "16\tsubdir/links\n");
|
|
||||||
} else {
|
|
||||||
assert_eq!(s, "8\tsubdir/links\n");
|
assert_eq!(s, "8\tsubdir/links\n");
|
||||||
|
} else {
|
||||||
|
assert_eq!(s, "16\tsubdir/links\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -255,10 +255,10 @@ fn _du_d_flag(s: &str) {
|
||||||
))]
|
))]
|
||||||
fn _du_d_flag(s: &str) {
|
fn _du_d_flag(s: &str) {
|
||||||
// MS-WSL linux has altered expected output
|
// MS-WSL linux has altered expected output
|
||||||
if !uucore::os::is_wsl_1() {
|
if uucore::os::is_wsl_1() {
|
||||||
assert_eq!(s, "28\t./subdir\n36\t.\n");
|
|
||||||
} else {
|
|
||||||
assert_eq!(s, "8\t./subdir\n8\t.\n");
|
assert_eq!(s, "8\t./subdir\n8\t.\n");
|
||||||
|
} else {
|
||||||
|
assert_eq!(s, "28\t./subdir\n36\t.\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -303,10 +303,10 @@ fn _du_dereference(s: &str) {
|
||||||
))]
|
))]
|
||||||
fn _du_dereference(s: &str) {
|
fn _du_dereference(s: &str) {
|
||||||
// MS-WSL linux has altered expected output
|
// MS-WSL linux has altered expected output
|
||||||
if !uucore::os::is_wsl_1() {
|
if uucore::os::is_wsl_1() {
|
||||||
assert_eq!(s, "8\tsubdir/links/deeper_dir\n24\tsubdir/links\n");
|
|
||||||
} else {
|
|
||||||
assert_eq!(s, "0\tsubdir/links/deeper_dir\n8\tsubdir/links\n");
|
assert_eq!(s, "0\tsubdir/links/deeper_dir\n8\tsubdir/links\n");
|
||||||
|
} else {
|
||||||
|
assert_eq!(s, "8\tsubdir/links/deeper_dir\n24\tsubdir/links\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1280,7 +1280,7 @@ fn test_ls_long_formats() {
|
||||||
// Zero or one "." for indicating a file with security context
|
// Zero or one "." for indicating a file with security context
|
||||||
|
|
||||||
// Regex for three names, so all of author, group and owner
|
// Regex for three names, so all of author, group and owner
|
||||||
let re_three = Regex::new(r"[xrw-]{9}\.? \d ([-0-9_a-z]+ ){3}0").unwrap();
|
let re_three = Regex::new(r"[xrw-]{9}\.? \d ([-0-9_a-z_A-Z]+ ){3}0").unwrap();
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
let re_three_num = Regex::new(r"[xrw-]{9}\.? \d (\d+ ){3}0").unwrap();
|
let re_three_num = Regex::new(r"[xrw-]{9}\.? \d (\d+ ){3}0").unwrap();
|
||||||
|
@ -1289,13 +1289,13 @@ fn test_ls_long_formats() {
|
||||||
// - group and owner
|
// - group and owner
|
||||||
// - author and owner
|
// - author and owner
|
||||||
// - author and group
|
// - author and group
|
||||||
let re_two = Regex::new(r"[xrw-]{9}\.? \d ([-0-9_a-z]+ ){2}0").unwrap();
|
let re_two = Regex::new(r"[xrw-]{9}\.? \d ([-0-9_a-z_A-Z]+ ){2}0").unwrap();
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
let re_two_num = Regex::new(r"[xrw-]{9}\.? \d (\d+ ){2}0").unwrap();
|
let re_two_num = Regex::new(r"[xrw-]{9}\.? \d (\d+ ){2}0").unwrap();
|
||||||
|
|
||||||
// Regex for one name: author, group or owner
|
// Regex for one name: author, group or owner
|
||||||
let re_one = Regex::new(r"[xrw-]{9}\.? \d [-0-9_a-z]+ 0").unwrap();
|
let re_one = Regex::new(r"[xrw-]{9}\.? \d [-0-9_a-z_A-Z]+ 0").unwrap();
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
let re_one_num = Regex::new(r"[xrw-]{9}\.? \d \d+ 0").unwrap();
|
let re_one_num = Regex::new(r"[xrw-]{9}\.? \d \d+ 0").unwrap();
|
||||||
|
|
|
@ -25,7 +25,6 @@ fn test_shred_remove() {
|
||||||
assert!(at.file_exists(file_b));
|
assert!(at.file_exists(file_b));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_os = "freebsd"))]
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_shred_force() {
|
fn test_shred_force() {
|
||||||
let scene = TestScenario::new(util_name!());
|
let scene = TestScenario::new(util_name!());
|
||||||
|
|
|
@ -10,7 +10,7 @@ fn test_output_is_random_permutation() {
|
||||||
let input_seq = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
let input_seq = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
||||||
let input = input_seq
|
let input = input_seq
|
||||||
.iter()
|
.iter()
|
||||||
.map(|x| x.to_string())
|
.map(ToString::to_string)
|
||||||
.collect::<Vec<String>>()
|
.collect::<Vec<String>>()
|
||||||
.join("\n");
|
.join("\n");
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ fn test_echo() {
|
||||||
.args(
|
.args(
|
||||||
&input_seq
|
&input_seq
|
||||||
.iter()
|
.iter()
|
||||||
.map(|x| x.to_string())
|
.map(ToString::to_string)
|
||||||
.collect::<Vec<String>>(),
|
.collect::<Vec<String>>(),
|
||||||
)
|
)
|
||||||
.succeeds();
|
.succeeds();
|
||||||
|
@ -74,7 +74,7 @@ fn test_head_count() {
|
||||||
let input_seq = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
let input_seq = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
||||||
let input = input_seq
|
let input = input_seq
|
||||||
.iter()
|
.iter()
|
||||||
.map(|x| x.to_string())
|
.map(ToString::to_string)
|
||||||
.collect::<Vec<String>>()
|
.collect::<Vec<String>>()
|
||||||
.join("\n");
|
.join("\n");
|
||||||
|
|
||||||
|
@ -105,7 +105,7 @@ fn test_repeat() {
|
||||||
let input_seq = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
let input_seq = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
||||||
let input = input_seq
|
let input = input_seq
|
||||||
.iter()
|
.iter()
|
||||||
.map(|x| x.to_string())
|
.map(ToString::to_string)
|
||||||
.collect::<Vec<String>>()
|
.collect::<Vec<String>>()
|
||||||
.join("\n");
|
.join("\n");
|
||||||
|
|
||||||
|
|
|
@ -10,11 +10,11 @@ fn test_invalid_time_interval() {
|
||||||
new_ucmd!()
|
new_ucmd!()
|
||||||
.arg("xyz")
|
.arg("xyz")
|
||||||
.fails()
|
.fails()
|
||||||
.usage_error("invalid time interval 'xyz'");
|
.usage_error("invalid time interval 'xyz': Invalid character: 'x' at position 1");
|
||||||
new_ucmd!()
|
new_ucmd!()
|
||||||
.args(&["--", "-1"])
|
.args(&["--", "-1"])
|
||||||
.fails()
|
.fails()
|
||||||
.usage_error("invalid time interval '-1'");
|
.usage_error("invalid time interval '-1': Number was negative");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -204,14 +204,16 @@ fn test_sleep_when_input_has_only_whitespace_then_error(#[case] input: &str) {
|
||||||
.arg(input)
|
.arg(input)
|
||||||
.timeout(Duration::from_secs(10))
|
.timeout(Duration::from_secs(10))
|
||||||
.fails()
|
.fails()
|
||||||
.usage_error(format!("invalid time interval '{input}'"));
|
.usage_error(format!(
|
||||||
|
"invalid time interval '{input}': Found only whitespace in input"
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_sleep_when_multiple_input_some_with_error_then_shows_all_errors() {
|
fn test_sleep_when_multiple_input_some_with_error_then_shows_all_errors() {
|
||||||
let expected = "invalid time interval 'abc'\n\
|
let expected = "invalid time interval 'abc': Invalid character: 'a' at position 1\n\
|
||||||
sleep: invalid time interval '1years'\n\
|
sleep: invalid time interval '1years': Invalid time unit: 'years' at position 2\n\
|
||||||
sleep: invalid time interval ' '";
|
sleep: invalid time interval ' ': Found only whitespace in input";
|
||||||
|
|
||||||
// Even if one of the arguments is valid, but the rest isn't, we should still fail and exit early.
|
// Even if one of the arguments is valid, but the rest isn't, we should still fail and exit early.
|
||||||
// So, the timeout of 10 seconds ensures we haven't executed `thread::sleep` with the only valid
|
// So, the timeout of 10 seconds ensures we haven't executed `thread::sleep` with the only valid
|
||||||
|
@ -228,5 +230,5 @@ fn test_negative_interval() {
|
||||||
new_ucmd!()
|
new_ucmd!()
|
||||||
.args(&["--", "-1"])
|
.args(&["--", "-1"])
|
||||||
.fails()
|
.fails()
|
||||||
.usage_error("invalid time interval '-1'");
|
.usage_error("invalid time interval '-1': Number was negative");
|
||||||
}
|
}
|
||||||
|
|
|
@ -4459,7 +4459,7 @@ fn test_follow_when_files_are_pointing_to_same_relative_file_and_file_stays_same
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
#[case::exponent_exceed_float_max("1.0e2048")]
|
#[case::exponent_exceed_float_max("1.0e100000")]
|
||||||
#[case::underscore_delimiter("1_000")]
|
#[case::underscore_delimiter("1_000")]
|
||||||
#[case::only_point(".")]
|
#[case::only_point(".")]
|
||||||
#[case::space_in_primes("' '")]
|
#[case::space_in_primes("' '")]
|
||||||
|
|
|
@ -138,3 +138,19 @@ fn test_kill_after_long() {
|
||||||
.no_stdout()
|
.no_stdout()
|
||||||
.no_stderr();
|
.no_stderr();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_kill_subprocess() {
|
||||||
|
new_ucmd!()
|
||||||
|
.args(&[
|
||||||
|
// Make sure the CI can spawn the subprocess.
|
||||||
|
"10",
|
||||||
|
"sh",
|
||||||
|
"-c",
|
||||||
|
"sh -c \"trap 'echo xyz' TERM; sleep 30\"",
|
||||||
|
])
|
||||||
|
.fails()
|
||||||
|
.code_is(124)
|
||||||
|
.stdout_contains("xyz")
|
||||||
|
.stderr_contains("Terminated");
|
||||||
|
}
|
||||||
|
|
|
@ -2822,7 +2822,7 @@ mod tests {
|
||||||
#[should_panic]
|
#[should_panic]
|
||||||
fn test_cmd_result_stdout_check_when_false_then_panics() {
|
fn test_cmd_result_stdout_check_when_false_then_panics() {
|
||||||
let result = TestScenario::new("echo").ucmd().arg("Hello world").run();
|
let result = TestScenario::new("echo").ucmd().arg("Hello world").run();
|
||||||
result.stdout_check(|s| s.is_empty());
|
result.stdout_check(<[u8]>::is_empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "echo")]
|
#[cfg(feature = "echo")]
|
||||||
|
@ -3065,9 +3065,10 @@ mod tests {
|
||||||
// check `child.is_alive()` and `child.delay()` is working
|
// check `child.is_alive()` and `child.delay()` is working
|
||||||
let mut trials = 10;
|
let mut trials = 10;
|
||||||
while child.is_alive() {
|
while child.is_alive() {
|
||||||
if trials <= 0 {
|
assert!(
|
||||||
panic!("Assertion failed: child process is still alive.")
|
trials > 0,
|
||||||
}
|
"Assertion failed: child process is still alive."
|
||||||
|
);
|
||||||
|
|
||||||
child.delay(500);
|
child.delay(500);
|
||||||
trials -= 1;
|
trials -= 1;
|
||||||
|
|
|
@ -18,7 +18,7 @@ for filepath in test_dir.glob("**/*.log"):
|
||||||
current[key] = {}
|
current[key] = {}
|
||||||
current = current[key]
|
current = current[key]
|
||||||
try:
|
try:
|
||||||
with open(path) as f:
|
with open(path, errors="ignore") as f:
|
||||||
content = f.read()
|
content = f.read()
|
||||||
result = re.search(
|
result = re.search(
|
||||||
r"(PASS|FAIL|SKIP|ERROR) [^ ]+ \(exit status: \d+\)$", content
|
r"(PASS|FAIL|SKIP|ERROR) [^ ]+ \(exit status: \d+\)$", content
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue