diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml index 941bb49fad3..a65062b22de 100644 --- a/.github/workflows/CICD.yml +++ b/.github/workflows/CICD.yml @@ -945,7 +945,7 @@ jobs: - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.job.toolchain }} - components: rustfmt + components: llvm-tools-preview - uses: taiki-e/install-action@nextest - uses: taiki-e/install-action@grcov - uses: Swatinem/rust-cache@v2 @@ -1006,24 +1006,32 @@ jobs: run: | ## Dependent VARs setup outputs() { step_id="${{ github.action }}"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo "${var}=${!var}" >> $GITHUB_OUTPUT; done; } + df -h . + df -h ${{ github.workspace }} # * determine sub-crate utility list UTILITY_LIST="$(./util/show-utils.sh ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }})" CARGO_UTILITY_LIST_OPTIONS="$(for u in ${UTILITY_LIST}; do echo -n "-puu_${u} "; done;)" + df -h outputs CARGO_UTILITY_LIST_OPTIONS - name: Test - run: cargo nextest run --profile ci --hide-progress-bar ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} -p uucore -p coreutils + run: | + df -h + cargo nextest run --profile ci --hide-progress-bar ${{ steps.vars.outputs.CARGO_FEATURES_OPTION }} -p uucore -p coreutils + df -h env: RUSTC_WRAPPER: "" - RUSTFLAGS: "-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort" + RUSTFLAGS: "-Cinstrument-coverage -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort" RUSTDOCFLAGS: "-Cpanic=abort" RUST_BACKTRACE: "1" + LLVM_PROFILE_FILE: "${{ github.workspace }}/build/coverage-%p-%m.profraw" # RUSTUP_TOOLCHAIN: ${{ steps.vars.outputs.TOOLCHAIN }} - name: Test individual utilities run: cargo nextest run --profile ci --hide-progress-bar ${{ steps.dep_vars.outputs.CARGO_UTILITY_LIST_OPTIONS }} env: RUSTC_WRAPPER: "" - RUSTFLAGS: "-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort" + RUSTFLAGS: "-Cinstrument-coverage -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort" RUSTDOCFLAGS: "-Cpanic=abort" + LLVM_PROFILE_FILE: "${{ github.workspace }}/build/coverage-%p-%m.profraw" RUST_BACKTRACE: "1" # RUSTUP_TOOLCHAIN: ${{ steps.vars.outputs.TOOLCHAIN }} - name: Generate coverage data (via `grcov`) @@ -1033,13 +1041,14 @@ jobs: ## Generate coverage data COVERAGE_REPORT_DIR="target/debug" COVERAGE_REPORT_FILE="${COVERAGE_REPORT_DIR}/lcov.info" - # GRCOV_IGNORE_OPTION='--ignore build.rs --ignore "vendor/*" --ignore "/*" --ignore "[a-zA-Z]:/*"' ## `grcov` ignores these params when passed as an environment variable (why?) - # GRCOV_EXCLUDE_OPTION='--excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()"' ## `grcov` ignores these params when passed as an environment variable (why?) mkdir -p "${COVERAGE_REPORT_DIR}" - # display coverage files - grcov . --output-type files --ignore build.rs --ignore "vendor/*" --ignore "/*" --ignore "[a-zA-Z]:/*" --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()" | sort --unique + # display coverage files (for debug) + du -h ${{ github.workspace }}/build/ + grcov --version + ls -al target/ + ls -al target/debug # generate coverage report - grcov . --output-type lcov --output-path "${COVERAGE_REPORT_FILE}" --branch --ignore build.rs --ignore "vendor/*" --ignore "/*" --ignore "[a-zA-Z]:/*" --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()" + grcov ${{ github.workspace }}/build/ --output-type lcov -b target/debug/ --output-path "${COVERAGE_REPORT_FILE}" --branch --ignore build.rs --ignore "vendor/*" --ignore "/*" --ignore "[a-zA-Z]:/*" --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()" echo "report=${COVERAGE_REPORT_FILE}" >> $GITHUB_OUTPUT - name: Upload coverage results (to Codecov.io) uses: codecov/codecov-action@v4 diff --git a/.github/workflows/GnuTests.yml b/.github/workflows/GnuTests.yml index fc165e0b3e5..41091cc1696 100644 --- a/.github/workflows/GnuTests.yml +++ b/.github/workflows/GnuTests.yml @@ -383,8 +383,9 @@ jobs: locale -a - name: Build binaries env: - RUSTFLAGS: "-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort" + RUSTFLAGS: "-Cinstrument-coverage -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort" RUSTDOCFLAGS: "-Cpanic=abort" + LLVM_PROFILE_FILE: ${{ github.workspace }}/build/profile-%p-%m.profraw run: | ## Build binaries cd uutils @@ -401,9 +402,9 @@ jobs: mkdir -p "${COVERAGE_REPORT_DIR}" sudo chown -R "$(whoami)" "${COVERAGE_REPORT_DIR}" # display coverage files - grcov . --output-type files --ignore build.rs --ignore "vendor/*" --ignore "/*" --ignore "[a-zA-Z]:/*" --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()" | sort --unique + grcov ${{ github.workspace }}/build/profile-%p-%m.profraw --output-type files --ignore build.rs --ignore "vendor/*" --ignore "/*" --ignore "[a-zA-Z]:/*" --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()" | sort --unique # generate coverage report - grcov . --output-type lcov --output-path "${COVERAGE_REPORT_FILE}" --branch --ignore build.rs --ignore "vendor/*" --ignore "/*" --ignore "[a-zA-Z]:/*" --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()" + grcov ${{ github.workspace }}/build/profile-%p-%m.profraw --output-type lcov --output-path "${COVERAGE_REPORT_FILE}" --branch --ignore build.rs --ignore "vendor/*" --ignore "/*" --ignore "[a-zA-Z]:/*" --excl-br-line "^\s*((debug_)?assert(_eq|_ne)?!|#\[derive\()" echo "report=${COVERAGE_REPORT_FILE}" >> $GITHUB_OUTPUT - name: Upload coverage results (to Codecov.io) uses: codecov/codecov-action@v4 diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml new file mode 100644 index 00000000000..f221a3b2ca0 --- /dev/null +++ b/.github/workflows/coverage.yml @@ -0,0 +1,56 @@ +name: Coverage + +on: + workflow_dispatch: + push: + branches: + - main + pull_request: + +jobs: + codecov: + name: Code coverage (new) + runs-on: ${{ matrix.job.os }} + timeout-minutes: 90 + + strategy: + fail-fast: false + matrix: + job: + - { os: ubuntu-latest , features: unix, toolchain: nightly } + - { os: macos-latest , features: macos, toolchain: nightly } + # FIXME: Re-enable Code Coverage on windows, which currently fails due to "profiler_builtins". See #6686. + # - { os: windows-latest , features: windows, toolchain: nightly-x86_64-pc-windows-gnu } + env: + CARGO_INCREMENTAL: "0" + + steps: + - uses: actions/checkout@v4 + + - name: Install Rust + run: rustup update stable + + - name: Install cargo-llvm-cov + uses: taiki-e/install-action@cargo-llvm-cov + + - name: Install Rust + run: rustup update stable + + - name: llvm-cov show-env + run: cargo llvm-cov show-env --export-prefix + + - name: Build + run: source <(cargo llvm-cov show-env --export-prefix) && cargo build --features=${{ matrix.job.features }} + + - name: Test main crate + run: source <(cargo llvm-cov show-env --export-prefix) && cargo test --features=${{ matrix.job.features }} -p uucore -p coreutils + + - name: Generate code coverage + run: source <(cargo llvm-cov show-env --export-prefix) && cargo llvm-cov report --lcov --output-path lcov.info + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: lcov.info + fail_ci_if_error: true diff --git a/src/uu/tr/src/operation.rs b/src/uu/tr/src/operation.rs index fc01a83608d..475a2b47903 100644 --- a/src/uu/tr/src/operation.rs +++ b/src/uu/tr/src/operation.rs @@ -22,7 +22,7 @@ use std::{ io::{BufRead, Write}, ops::Not, }; -use uucore::error::UError; +use uucore::error::{UError, UResult, USimpleError}; #[derive(Debug, Clone)] pub enum BadSequence { @@ -577,7 +577,7 @@ impl SymbolTranslator for SqueezeOperation { } } -pub fn translate_input(input: &mut R, output: &mut W, mut translator: T) +pub fn translate_input(input: &mut R, output: &mut W, mut translator: T) -> UResult<()> where T: SymbolTranslator, R: BufRead, @@ -585,7 +585,17 @@ where { let mut buf = Vec::new(); let mut output_buf = Vec::new(); - while let Ok(length) = input.read_until(b'\n', &mut buf) { + loop { + let length = match input.read_until(b'\n', &mut buf) { + Ok(0) => break, // EOF reached + Ok(n) => n, + Err(e) => { + return Err(USimpleError::new( + 1, + format!("{}: read error: {}", uucore::util_name(), e), + )); + } + }; if length == 0 { break; } else { @@ -596,4 +606,5 @@ where buf.clear(); output_buf.clear(); } + Ok(()) } diff --git a/src/uu/tr/src/tr.rs b/src/uu/tr/src/tr.rs index 67998d26d4b..ff85002e71d 100644 --- a/src/uu/tr/src/tr.rs +++ b/src/uu/tr/src/tr.rs @@ -132,24 +132,24 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let delete_op = DeleteOperation::new(set1); let squeeze_op = SqueezeOperation::new(set2); let op = delete_op.chain(squeeze_op); - translate_input(&mut locked_stdin, &mut buffered_stdout, op); + translate_input(&mut locked_stdin, &mut buffered_stdout, op)?; } else { let op = DeleteOperation::new(set1); - translate_input(&mut locked_stdin, &mut buffered_stdout, op); + translate_input(&mut locked_stdin, &mut buffered_stdout, op)?; } } else if squeeze_flag { if sets_len < 2 { let op = SqueezeOperation::new(set1); - translate_input(&mut locked_stdin, &mut buffered_stdout, op); + translate_input(&mut locked_stdin, &mut buffered_stdout, op)?; } else { let translate_op = TranslateOperation::new(set1, set2.clone())?; let squeeze_op = SqueezeOperation::new(set2); let op = translate_op.chain(squeeze_op); - translate_input(&mut locked_stdin, &mut buffered_stdout, op); + translate_input(&mut locked_stdin, &mut buffered_stdout, op)?; } } else { let op = TranslateOperation::new(set1, set2)?; - translate_input(&mut locked_stdin, &mut buffered_stdout, op); + translate_input(&mut locked_stdin, &mut buffered_stdout, op)?; } Ok(()) } diff --git a/tests/by-util/test_env.rs b/tests/by-util/test_env.rs index 208feab6db2..f9d81dbd97f 100644 --- a/tests/by-util/test_env.rs +++ b/tests/by-util/test_env.rs @@ -512,7 +512,14 @@ fn test_gnu_e20() { ); let out = scene.ucmd().args(&input).succeeds(); - assert_eq!(out.stdout_str(), output); + + // Remove the unwanted substring from stdout + let filtered_stdout = out.stdout_str().replace( + "__LLVM_PROFILE_RT_INIT_ONCE=__LLVM_PROFILE_RT_INIT_ONCE\n", + "", + ); + + assert_eq!(filtered_stdout, output); } #[test] diff --git a/tests/by-util/test_tr.rs b/tests/by-util/test_tr.rs index ebd7635e432..1c248b1065a 100644 --- a/tests/by-util/test_tr.rs +++ b/tests/by-util/test_tr.rs @@ -13,6 +13,11 @@ fn test_invalid_arg() { new_ucmd!().arg("--definitely-invalid").fails().code_is(1); } +#[test] +fn test_invalid_input() { + new_ucmd!().args(&["1", "1", "<", "."]).fails().code_is(1); +} + #[test] fn test_to_upper() { new_ucmd!() diff --git a/tests/common/util.rs b/tests/common/util.rs index 87c937492f3..ebe13a7749f 100644 --- a/tests/common/util.rs +++ b/tests/common/util.rs @@ -1614,6 +1614,11 @@ impl UCommand { } } + // Pass LLVM_PROFILE_FILE if it is set in the environment + if let Some(llvm_profile) = env::var_os("LLVM_PROFILE_FILE") { + command.env("LLVM_PROFILE_FILE", llvm_profile); + } + command .envs(DEFAULT_ENV) .envs(self.env_vars.iter().cloned());