diff --git a/.github/workflows/GnuComment.yml b/.github/workflows/GnuComment.yml deleted file mode 100644 index 7fc8880..0000000 --- a/.github/workflows/GnuComment.yml +++ /dev/null @@ -1,80 +0,0 @@ -name: GnuComment - -on: - workflow_run: - workflows: ["GnuTests"] - types: - - completed - -permissions: {} -jobs: - post-comment: - permissions: - actions: read # to list workflow runs artifacts - pull-requests: write # to comment on pr - - runs-on: ubuntu-latest - if: > - github.event.workflow_run.event == 'pull_request' - steps: - - name: 'Download artifact' - uses: actions/github-script@v8 - with: - script: | - // List all artifacts from GnuTests - var artifacts = await github.rest.actions.listWorkflowRunArtifacts({ - owner: context.repo.owner, - repo: context.repo.repo, - run_id: ${{ github.event.workflow_run.id }}, - }); - - // Download the "comment" artifact, which contains a PR number (NR) and result.txt - var matchArtifact = artifacts.data.artifacts.filter((artifact) => { - return artifact.name == "comment" - })[0]; - - if (!matchArtifact) { - console.log('No comment artifact found'); - return; - } - - var download = await github.rest.actions.downloadArtifact({ - owner: context.repo.owner, - repo: context.repo.repo, - artifact_id: matchArtifact.id, - archive_format: 'zip', - }); - var fs = require('fs'); - fs.writeFileSync('${{ github.workspace }}/comment.zip', Buffer.from(download.data)); - - run: unzip comment.zip || echo "Failed to unzip comment artifact" - - - name: 'Comment on PR' - uses: actions/github-script@v8 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - var fs = require('fs'); - - // Check if files exist - if (!fs.existsSync('./NR')) { - console.log('No NR file found, skipping comment'); - return; - } - if (!fs.existsSync('./result.txt')) { - console.log('No result.txt file found, skipping comment'); - return; - } - - var issue_number = Number(fs.readFileSync('./NR')); - var content = fs.readFileSync('./result.txt'); - - if (content.toString().trim().length > 7) { // 7 because we have backquote + \n - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issue_number, - body: 'GNU sed testsuite comparison:\n```\n' + content + '```' - }); - } else { - console.log('Comment content too short, skipping'); - } \ No newline at end of file diff --git a/.github/workflows/GnuTests.yml b/.github/workflows/GnuTests.yml deleted file mode 100644 index 61c06de..0000000 --- a/.github/workflows/GnuTests.yml +++ /dev/null @@ -1,284 +0,0 @@ -name: GnuTests - -# Run GNU sed testsuite against the Rust sed implementation -# This workflow extracts and runs tests from the GNU sed testsuite to ensure compatibility - -on: - pull_request: - push: - branches: - - '*' - -permissions: - contents: write # Publish sed instead of discarding - -# End the current execution if there is a new changeset in the PR -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} - -env: - DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} - TEST_FULL_SUMMARY_FILE: 'sed-gnu-full-result.json' - -jobs: - native: - name: Run GNU sed testsuite - runs-on: ubuntu-24.04 - steps: - #### Get the code, setup cache - - name: Checkout code (sed) - uses: actions/checkout@v6 - with: - path: 'sed' - persist-credentials: false - - uses: dtolnay/rust-toolchain@master - with: - toolchain: stable - components: rustfmt - - uses: Swatinem/rust-cache@v2 - with: - workspaces: "./sed -> target" - - name: Checkout code (GNU sed testsuite) - uses: actions/checkout@v6 - with: - repository: 'mirror/sed' - path: 'gnu.sed' - ref: 'master' - persist-credentials: false - - # Omit installing part of https://github.com/actions/runner-images/tree/main/images/ubuntu - - ### Build - - name: Build Rust sed binary - shell: bash - run: | - ## Build Rust sed binary - cd 'sed' - cargo build --config=profile.release.strip=true --profile=release #-fast - zstd -19 target/release/sed -o ../sed-x86_64-unknown-linux-gnu.zst - - name: Publish latest commit - uses: softprops/action-gh-release@v2 - if: github.event_name == 'push' && github.ref == 'refs/heads/main' - with: - tag_name: latest-commit - draft: false - prerelease: true - files: | - sed-x86_64-unknown-linux-gnu.zst - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - ### Run tests - - name: Run GNU sed testsuite - shell: bash - run: | - ## Run GNU sed testsuite using our script - cd 'sed' - # Set GNU testsuite directory - export GNU_TESTSUITE_DIR="../gnu.sed/testsuite" - # Run tests with JSON output - ./util/run-gnu-testsuite.sh --json-output "${{ env.TEST_FULL_SUMMARY_FILE }}" || true - - ### Upload artifacts - - name: Check for JSON results file - shell: bash - run: | - echo "Checking for JSON results file..." - ls -la sed/ || true - ls -la sed/${{ env.TEST_FULL_SUMMARY_FILE }} || echo "JSON file not found at sed/${{ env.TEST_FULL_SUMMARY_FILE }}" - ls -la ${{ env.TEST_FULL_SUMMARY_FILE }} || echo "JSON file not found at ${{ env.TEST_FULL_SUMMARY_FILE }}" - find . -name "*json*" -type f || echo "No JSON files found" - - - name: Upload full json results - uses: actions/upload-artifact@v6 - with: - name: sed-gnu-full-result - path: sed/${{ env.TEST_FULL_SUMMARY_FILE }} - if-no-files-found: warn - - - name: Upload test logs - if: always() - uses: actions/upload-artifact@v6 - with: - name: test-logs - path: | - sed/test-logs/*.log - sed/test-results/*.json - - aggregate: - needs: [native] - permissions: - actions: read - contents: read - pull-requests: read - name: Aggregate GNU test results - runs-on: ubuntu-24.04 - steps: - - name: Initialize workflow variables - id: vars - shell: bash - run: | - ## VARs setup - outputs() { step_id="${{ github.action }}"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo "${var}=${!var}" >> $GITHUB_OUTPUT; done; } - - TEST_SUMMARY_FILE='sed-gnu-result.json' - outputs TEST_SUMMARY_FILE - - - name: Checkout code (sed) - uses: actions/checkout@v6 - with: - path: 'sed' - persist-credentials: false - - - name: Retrieve reference artifacts - uses: dawidd6/action-download-artifact@v14 - continue-on-error: true - with: - workflow: GnuTests.yml - branch: "${{ env.DEFAULT_BRANCH }}" - workflow_conclusion: completed - path: "reference" - if_no_artifact_found: warn - - - name: Download full json results - uses: actions/download-artifact@v7 - with: - name: sed-gnu-full-result - path: results - - - name: Extract/summarize testing info - id: summary - shell: bash - run: | - ## Extract/summarize testing info - outputs() { step_id="${{ github.action }}"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo "${var}=${!var}" >> $GITHUB_OUTPUT; done; } - - # Check if results directory exists and has JSON files - json_count=0 - if [[ -d "results" ]]; then - json_count=$(find results -name "*.json" | wc -l) - fi - - if [[ "$json_count" -lt 1 ]]; then - echo "::error ::Failed to download results json files; failing early" - echo "::error ::Contents of results directory:" - ls -lR results || echo "::error ::Results directory does not exist" - exit 1 - fi - - # Extract summary from JSON results - RESULT_FILE="results/${{ env.TEST_FULL_SUMMARY_FILE }}" - if [[ -f "$RESULT_FILE" ]]; then - TOTAL=$(jq -r '.summary.total // 0' "$RESULT_FILE") - PASS=$(jq -r '.summary.passed // 0' "$RESULT_FILE") - FAIL=$(jq -r '.summary.failed // 0' "$RESULT_FILE") - SKIP=$(jq -r '.summary.skipped // 0' "$RESULT_FILE") - ERROR=0 # Our format doesn't distinguish errors from failures - else - echo "::error ::Result file $RESULT_FILE not found" - echo "::error ::Available files in results:" - find results -type f || true - TOTAL=0; PASS=0; FAIL=0; SKIP=0; ERROR=0 - fi - - output="GNU sed tests summary = TOTAL: $TOTAL / PASS: $PASS / FAIL: $FAIL / SKIP: $SKIP" - echo "${output}" - - if [[ "$FAIL" -gt 0 ]]; then - echo "::warning ::${output}" - fi - - jq -n \ - --arg date "$(date --rfc-email)" \ - --arg sha "$GITHUB_SHA" \ - --arg total "$TOTAL" \ - --arg pass "$PASS" \ - --arg skip "$SKIP" \ - --arg fail "$FAIL" \ - --arg error "$ERROR" \ - '{($date): { sha: $sha, total: $total, pass: $pass, skip: $skip, fail: $fail, error: $error }}' > '${{ steps.vars.outputs.TEST_SUMMARY_FILE }}' - - HASH=$(sha1sum '${{ steps.vars.outputs.TEST_SUMMARY_FILE }}' | cut --delim=" " -f 1) - outputs HASH TOTAL PASS FAIL SKIP - - - name: Upload SHA1/ID of 'test-summary' - uses: actions/upload-artifact@v6 - with: - name: "${{ steps.summary.outputs.HASH }}" - path: "${{ steps.vars.outputs.TEST_SUMMARY_FILE }}" - - - name: Upload test results summary - uses: actions/upload-artifact@v6 - with: - name: test-summary - path: "${{ steps.vars.outputs.TEST_SUMMARY_FILE }}" - - - name: Compare test failures VS reference - shell: bash - run: | - ## Compare test failures VS reference using JSON files - REF_SUMMARY_FILE='reference/sed-gnu-full-result/sed-gnu-full-result.json' - CURRENT_SUMMARY_FILE="results/${{ env.TEST_FULL_SUMMARY_FILE }}" - REPO_DEFAULT_BRANCH='${{ env.DEFAULT_BRANCH }}' - - # Path to ignore file for intermittent issues - IGNORE_INTERMITTENT="sed/.github/workflows/ignore-intermittent.txt" - - # Set up comment directory - COMMENT_DIR="reference/comment" - mkdir -p ${COMMENT_DIR} - echo ${{ github.event.number }} > ${COMMENT_DIR}/NR - COMMENT_LOG="${COMMENT_DIR}/result.txt" - - COMPARISON_RESULT=0 - if test -f "${CURRENT_SUMMARY_FILE}"; then - if test -f "${REF_SUMMARY_FILE}"; then - echo "Reference summary SHA1/ID: $(sha1sum -- "${REF_SUMMARY_FILE}")" - echo "Current summary SHA1/ID: $(sha1sum -- "${CURRENT_SUMMARY_FILE}")" - - python3 sed/util/compare_test_results.py \ - --ignore-file "${IGNORE_INTERMITTENT}" \ - --output "${COMMENT_LOG}" \ - "${CURRENT_SUMMARY_FILE}" "${REF_SUMMARY_FILE}" - - COMPARISON_RESULT=$? - else - echo "::warning ::Skipping test comparison; no prior reference summary is available at '${REF_SUMMARY_FILE}'." - fi - else - echo "::error ::Failed to find summary of test results (missing '${CURRENT_SUMMARY_FILE}'); failing early" - exit 1 - fi - - if [ ${COMPARISON_RESULT} -eq 1 ]; then - echo "ONLY_INTERMITTENT=false" >> $GITHUB_ENV - echo "::error ::Found new non-intermittent test failures" - exit 1 - else - echo "ONLY_INTERMITTENT=true" >> $GITHUB_ENV - echo "::notice ::No new test failures detected" - fi - - - name: Upload comparison log (for GnuComment workflow) - if: success() || failure() - uses: actions/upload-artifact@v6 - with: - name: comment - path: reference/comment/ - - - name: Report test results - if: success() || failure() - shell: bash - run: | - ## Report final results - echo "::notice ::GNU sed testsuite results:" - echo "::notice :: Total tests: ${{ steps.summary.outputs.TOTAL }}" - echo "::notice :: Passed: ${{ steps.summary.outputs.PASS }}" - echo "::notice :: Failed: ${{ steps.summary.outputs.FAIL }}" - echo "::notice :: Skipped: ${{ steps.summary.outputs.SKIP }}" - - if [[ "${{ steps.summary.outputs.FAIL }}" -gt 0 ]]; then - PASS_RATE=$(( ${{ steps.summary.outputs.PASS }} * 100 / (${{ steps.summary.outputs.PASS }} + ${{ steps.summary.outputs.FAIL }}) )) - echo "::notice :: Pass rate: ${PASS_RATE}%" - fi diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 2e2627d..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,119 +0,0 @@ -on: [push, pull_request] - -name: Basic CI - -env: - CARGO_TERM_COLOR: always - -jobs: - check: - name: cargo check - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, macOS-latest, windows-latest] - steps: - - uses: actions/checkout@v6 - - uses: dtolnay/rust-toolchain@stable - - run: cargo check - - test: - name: cargo test - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, macOS-latest, windows-latest] - steps: - - name: Set Git to use LF, even on Windows - run: | - git config --global core.autocrlf false - git config --global core.eol lf - - uses: actions/checkout@v6 - - uses: dtolnay/rust-toolchain@stable - - run: cargo test --all - - coverage: - name: Code Coverage - runs-on: ${{ matrix.job.os }} - strategy: - fail-fast: true - matrix: - job: - - { os: ubuntu-latest , features: unix } - - { os: macos-latest , features: macos } - - { os: windows-latest , features: windows } - steps: - - name: Set Git to use LF, even on Windows - run: | - git config --global core.autocrlf false - git config --global core.eol lf - - uses: actions/checkout@v6 - - name: Initialize workflow variables - id: vars - shell: bash - run: | - ## VARs setup - outputs() { step_id="vars"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo "${var}=${!var}" >> $GITHUB_OUTPUT; done; } - # toolchain - TOOLCHAIN="nightly" ## default to "nightly" toolchain (required for certain required unstable compiler flags) ## !maint: refactor when stable channel has needed support - # * specify gnu-type TOOLCHAIN for windows; `grcov` requires gnu-style code coverage data files - case ${{ matrix.job.os }} in windows-*) TOOLCHAIN="$TOOLCHAIN-x86_64-pc-windows-gnu" ;; esac; - # * use requested TOOLCHAIN if specified - if [ -n "${{ matrix.job.toolchain }}" ]; then TOOLCHAIN="${{ matrix.job.toolchain }}" ; fi - outputs TOOLCHAIN - # target-specific options - # * CODECOV_FLAGS - CODECOV_FLAGS=$( echo "${{ matrix.job.os }}" | sed 's/[^[:alnum:]]/_/g' ) - outputs CODECOV_FLAGS - - - name: rust toolchain ~ install - uses: dtolnay/rust-toolchain@nightly - - run: rustup component add llvm-tools-preview - - name: Test - run: cargo test --no-fail-fast - env: - CARGO_INCREMENTAL: "0" - RUSTC_WRAPPER: "" - RUSTFLAGS: "-Cinstrument-coverage -Zcoverage-options=branch -Ccodegen-units=1 -Copt-level=0 -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort" - RUSTDOCFLAGS: "-Cpanic=abort" - LLVM_PROFILE_FILE: "sed-%p-%m.profraw" - - name: "`grcov` ~ install" - id: build_grcov - shell: bash - run: | - git clone https://github.com/mozilla/grcov.git ~/grcov/ - cd ~/grcov - cargo install --path . - cd - -# Uncomment when the upstream issue -# https://github.com/mozilla/grcov/issues/849 is fixed -# uses: actions-rs/install@v0.1 -# with: -# crate: grcov -# version: latest -# use-tool-cache: false - - name: Generate coverage data (via `grcov`) - id: coverage - shell: bash - run: | - set -v - ## Generate coverage data - COVERAGE_REPORT_DIR="target/debug" - COVERAGE_REPORT_FILE="${COVERAGE_REPORT_DIR}/lcov.info" - mkdir -p "${COVERAGE_REPORT_DIR}" - # display coverage files - grcov . --output-type files --binary-path "${COVERAGE_REPORT_DIR}" --source-dir . --keep-only "src/*" | sort --unique - # generate coverage report - grcov . --output-type lcov --output-path "${COVERAGE_REPORT_FILE}" --binary-path "${COVERAGE_REPORT_DIR}" --source-dir . --keep-only "src/*" --branch - ls -al ${COVERAGE_REPORT_FILE} - ls -al - echo "report=${COVERAGE_REPORT_FILE}" >> $GITHUB_OUTPUT - - name: Upload coverage results (to Codecov.io) - uses: codecov/codecov-action@v5 - with: - token: ${{ secrets.CODECOV_TOKEN }} - files: ${{ steps.coverage.outputs.report }} - ## flags: IntegrationTests, UnitTests, ${{ steps.vars.outputs.CODECOV_FLAGS }} - flags: ${{ steps.vars.outputs.CODECOV_FLAGS }} - name: codecov-umbrella - fail_ci_if_error: false diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml deleted file mode 100644 index cacbb27..0000000 --- a/.github/workflows/code-quality.yml +++ /dev/null @@ -1,76 +0,0 @@ -name: Code Quality - -# spell-checker:ignore TERMUX reactivecircus Swatinem noaudio pkill swiftshader dtolnay juliangruber - -on: - pull_request: - push: - branches: - - main - -env: - # * style job configuration - STYLE_FAIL_ON_FAULT: true ## (bool) fail the build if a style job contains a fault (error or warning); may be overridden on a per-job basis - -permissions: - contents: read # to fetch code (actions/checkout) - -# End the current execution if there is a new changeset in the PR. -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} - -jobs: - fmt: - name: cargo fmt --all -- --check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - uses: dtolnay/rust-toolchain@stable - - run: rustup component add rustfmt - - run: cargo fmt --all -- --check - - style_lint: - name: Style/lint - runs-on: ${{ matrix.job.os }} - env: - SCCACHE_GHA_ENABLED: "true" - RUSTC_WRAPPER: "sccache" - strategy: - fail-fast: false - matrix: - job: - - { os: ubuntu-latest } - - { os: macos-latest } - - { os: windows-latest } - steps: - - uses: actions/checkout@v6 - - uses: dtolnay/rust-toolchain@master - with: - toolchain: stable - components: clippy - - uses: Swatinem/rust-cache@v2 - - name: Run sccache-cache - uses: mozilla-actions/sccache-action@v0.0.9 - - name: Initialize workflow variables - id: vars - shell: bash - run: | - ## VARs setup - outputs() { step_id="${{ github.action }}"; for var in "$@" ; do echo steps.${step_id}.outputs.${var}="${!var}"; echo "${var}=${!var}" >> $GITHUB_OUTPUT; done; } - # failure mode - unset FAIL_ON_FAULT ; case '${{ env.STYLE_FAIL_ON_FAULT }}' in - ''|0|f|false|n|no|off) FAULT_TYPE=warning ;; - *) FAIL_ON_FAULT=true ; FAULT_TYPE=error ;; - esac; - outputs FAIL_ON_FAULT FAULT_TYPE - - name: "`cargo clippy` lint testing" - shell: bash - run: | - ## `cargo clippy` lint testing - unset fault - fault_type="${{ steps.vars.outputs.FAULT_TYPE }}" - fault_prefix=$(echo "$fault_type" | tr '[:lower:]' '[:upper:]') - # * convert any warnings to GHA UI annotations; ref: - S=$(cargo clippy --all-targets --workspace -psed -- -D warnings 2>&1) && printf "%s\n" "$S" || { printf "%s\n" "$S" ; printf "%s" "$S" | sed -E -n -e '/^error:/{' -e "N; s/^error:[[:space:]]+(.*)\\n[[:space:]]+-->[[:space:]]+(.*):([0-9]+):([0-9]+).*$/::${fault_type} file=\2,line=\3,col=\4::${fault_prefix}: \`cargo clippy\`: \1 (file:'\2', line:\3)/p;" -e '}' ; fault=true ; } - if [ -n "${{ steps.vars.outputs.FAIL_ON_FAULT }}" ] && [ -n "$fault" ]; then exit 1 ; fi diff --git a/.github/workflows/fuzzing.yml b/.github/workflows/fuzzing.yml deleted file mode 100644 index 1375d7f..0000000 --- a/.github/workflows/fuzzing.yml +++ /dev/null @@ -1,264 +0,0 @@ -name: Fuzzing - -# spell-checker:ignore fuzzer dtolnay Swatinem - -on: - pull_request: - push: - branches: - - '*' - -permissions: - contents: read # to fetch code (actions/checkout) - -# End the current execution if there is a new changeset in the PR. -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} - -jobs: - fuzz-build: - name: Build the fuzzers - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - with: - persist-credentials: false - - uses: dtolnay/rust-toolchain@nightly - - name: Install `cargo-fuzz` - run: cargo install cargo-fuzz - - uses: Swatinem/rust-cache@v2 - with: - shared-key: "cargo-fuzz-cache-key" - cache-directories: "fuzz/target" - - name: Run `cargo-fuzz build` - run: cargo +nightly fuzz build - - fuzz-run: - needs: fuzz-build - name: Fuzz - runs-on: ubuntu-latest - timeout-minutes: 5 - env: - RUN_FOR: 60 - strategy: - matrix: - test-target: - - { name: fuzz_sed, should_pass: false } - - steps: - - uses: actions/checkout@v6 - with: - persist-credentials: false - - uses: dtolnay/rust-toolchain@nightly - - name: Install `cargo-fuzz` - run: cargo install cargo-fuzz - - uses: Swatinem/rust-cache@v2 - with: - shared-key: "cargo-fuzz-cache-key" - cache-directories: "fuzz/target" - - name: Restore Cached Corpus - uses: actions/cache/restore@v5 - with: - key: corpus-cache-${{ matrix.test-target.name }} - path: | - fuzz/corpus/${{ matrix.test-target.name }} - - name: Run ${{ matrix.test-target.name }} for XX seconds - id: run_fuzzer - shell: bash - continue-on-error: ${{ !matrix.test-target.should_pass }} - run: | - mkdir -p fuzz/stats - STATS_FILE="fuzz/stats/${{ matrix.test-target.name }}.txt" - cargo +nightly fuzz run ${{ matrix.test-target.name }} -- -max_total_time=${{ env.RUN_FOR }} -timeout=${{ env.RUN_FOR }} -detect_leaks=0 -print_final_stats=1 2>&1 | tee "$STATS_FILE" - - # Extract key stats from the output - if grep -q "stat::number_of_executed_units" "$STATS_FILE"; then - RUNS=$(grep "stat::number_of_executed_units" "$STATS_FILE" | awk '{print $2}') - echo "runs=$RUNS" >> "$GITHUB_OUTPUT" - else - echo "runs=unknown" >> "$GITHUB_OUTPUT" - fi - - if grep -q "stat::average_exec_per_sec" "$STATS_FILE"; then - EXEC_RATE=$(grep "stat::average_exec_per_sec" "$STATS_FILE" | awk '{print $2}') - echo "exec_rate=$EXEC_RATE" >> "$GITHUB_OUTPUT" - else - echo "exec_rate=unknown" >> "$GITHUB_OUTPUT" - fi - - if grep -q "stat::new_units_added" "$STATS_FILE"; then - NEW_UNITS=$(grep "stat::new_units_added" "$STATS_FILE" | awk '{print $2}') - echo "new_units=$NEW_UNITS" >> "$GITHUB_OUTPUT" - else - echo "new_units=unknown" >> "$GITHUB_OUTPUT" - fi - - # Save should_pass value to file for summary job to use - echo "${{ matrix.test-target.should_pass }}" > "fuzz/stats/${{ matrix.test-target.name }}.should_pass" - - # Print stats to job output for immediate visibility - echo "----------------------------------------" - echo "FUZZING STATISTICS FOR ${{ matrix.test-target.name }}" - echo "----------------------------------------" - echo "Runs: $(grep -q "stat::number_of_executed_units" "$STATS_FILE" && grep "stat::number_of_executed_units" "$STATS_FILE" | awk '{print $2}' || echo "unknown")" - echo "Execution Rate: $(grep -q "stat::average_exec_per_sec" "$STATS_FILE" && grep "stat::average_exec_per_sec" "$STATS_FILE" | awk '{print $2}' || echo "unknown") execs/sec" - echo "New Units: $(grep -q "stat::new_units_added" "$STATS_FILE" && grep "stat::new_units_added" "$STATS_FILE" | awk '{print $2}' || echo "unknown")" - echo "Expected: ${{ matrix.test-target.should_pass }}" - if grep -q "SUMMARY: " "$STATS_FILE"; then - echo "Status: $(grep "SUMMARY: " "$STATS_FILE" | head -1)" - else - echo "Status: Completed" - fi - echo "----------------------------------------" - - # Add summary to GitHub step summary - echo "### Fuzzing Results for ${{ matrix.test-target.name }}" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "| Metric | Value |" >> $GITHUB_STEP_SUMMARY - echo "|--------|-------|" >> $GITHUB_STEP_SUMMARY - - if grep -q "stat::number_of_executed_units" "$STATS_FILE"; then - echo "| Runs | $(grep "stat::number_of_executed_units" "$STATS_FILE" | awk '{print $2}') |" >> $GITHUB_STEP_SUMMARY - fi - - if grep -q "stat::average_exec_per_sec" "$STATS_FILE"; then - echo "| Execution Rate | $(grep "stat::average_exec_per_sec" "$STATS_FILE" | awk '{print $2}') execs/sec |" >> $GITHUB_STEP_SUMMARY - fi - - if grep -q "stat::new_units_added" "$STATS_FILE"; then - echo "| New Units | $(grep "stat::new_units_added" "$STATS_FILE" | awk '{print $2}') |" >> $GITHUB_STEP_SUMMARY - fi - - echo "| Should pass | ${{ matrix.test-target.should_pass }} |" >> $GITHUB_STEP_SUMMARY - - if grep -q "SUMMARY: " "$STATS_FILE"; then - echo "| Status | $(grep "SUMMARY: " "$STATS_FILE" | head -1) |" >> $GITHUB_STEP_SUMMARY - else - echo "| Status | Completed |" >> $GITHUB_STEP_SUMMARY - fi - - echo "" >> $GITHUB_STEP_SUMMARY - - name: Save Corpus Cache - uses: actions/cache/save@v5 - with: - key: corpus-cache-${{ matrix.test-target.name }} - path: | - fuzz/corpus/${{ matrix.test-target.name }} - - name: Upload Stats - uses: actions/upload-artifact@v6 - with: - name: fuzz-stats-${{ matrix.test-target.name }} - path: | - fuzz/stats/${{ matrix.test-target.name }}.txt - fuzz/stats/${{ matrix.test-target.name }}.should_pass - retention-days: 5 - fuzz-summary: - needs: fuzz-run - name: Fuzzing Summary - runs-on: ubuntu-latest - if: always() - steps: - - uses: actions/checkout@v6 - with: - persist-credentials: false - - name: Download all stats - uses: actions/download-artifact@v7 - with: - path: fuzz/stats-artifacts - pattern: fuzz-stats-* - merge-multiple: true - - name: Prepare stats directory - run: | - mkdir -p fuzz/stats - # Debug: List content of stats-artifacts directory - echo "Contents of stats-artifacts directory:" - find fuzz/stats-artifacts -type f | sort - - # Extract files from the artifact directories - handle nested directories - find fuzz/stats-artifacts -type f -name "*.txt" -exec cp {} fuzz/stats/ \; - find fuzz/stats-artifacts -type f -name "*.should_pass" -exec cp {} fuzz/stats/ \; - - # Debug information - echo "Contents of stats directory after extraction:" - ls -la fuzz/stats/ - echo "Contents of should_pass files (if any):" - cat fuzz/stats/*.should_pass 2>/dev/null || echo "No should_pass files found" - - name: Generate Summary - run: | - echo "# Fuzzing Summary" > fuzzing_summary.md - echo "" >> fuzzing_summary.md - echo "| Target | Runs | Exec/sec | New Units | Should pass | Status |" >> fuzzing_summary.md - echo "|--------|------|----------|-----------|-------------|--------|" >> fuzzing_summary.md - - TOTAL_RUNS=0 - TOTAL_NEW_UNITS=0 - - for stat_file in fuzz/stats/*.txt; do - TARGET=$(basename "$stat_file" .txt) - SHOULD_PASS_FILE="${stat_file%.*}.should_pass" - - # Get expected status - if [ -f "$SHOULD_PASS_FILE" ]; then - EXPECTED=$(cat "$SHOULD_PASS_FILE") - else - EXPECTED="unknown" - fi - - # Extract runs - if grep -q "stat::number_of_executed_units" "$stat_file"; then - RUNS=$(grep "stat::number_of_executed_units" "$stat_file" | awk '{print $2}') - TOTAL_RUNS=$((TOTAL_RUNS + RUNS)) - else - RUNS="unknown" - fi - - # Extract execution rate - if grep -q "stat::average_exec_per_sec" "$stat_file"; then - EXEC_RATE=$(grep "stat::average_exec_per_sec" "$stat_file" | awk '{print $2}') - else - EXEC_RATE="unknown" - fi - - # Extract new units added - if grep -q "stat::new_units_added" "$stat_file"; then - NEW_UNITS=$(grep "stat::new_units_added" "$stat_file" | awk '{print $2}') - if [[ "$NEW_UNITS" =~ ^[0-9]+$ ]]; then - TOTAL_NEW_UNITS=$((TOTAL_NEW_UNITS + NEW_UNITS)) - fi - else - NEW_UNITS="unknown" - fi - - # Extract status - if grep -q "SUMMARY: " "$stat_file"; then - STATUS=$(grep "SUMMARY: " "$stat_file" | head -1) - else - STATUS="Completed" - fi - - echo "| $TARGET | $RUNS | $EXEC_RATE | $NEW_UNITS | $EXPECTED | $STATUS |" >> fuzzing_summary.md - done - - echo "" >> fuzzing_summary.md - echo "## Overall Statistics" >> fuzzing_summary.md - echo "" >> fuzzing_summary.md - echo "- **Total runs:** $TOTAL_RUNS" >> fuzzing_summary.md - echo "- **Total new units discovered:** $TOTAL_NEW_UNITS" >> fuzzing_summary.md - echo "- **Average execution rate:** $(grep -h "stat::average_exec_per_sec" fuzz/stats/*.txt | awk '{sum += $2; count++} END {if (count > 0) print sum/count " execs/sec"; else print "unknown"}')" >> fuzzing_summary.md - - # Add count by expected status - echo "- **Tests expected to pass:** $(find fuzz/stats -name "*.should_pass" -exec cat {} \; | grep -c "true")" >> fuzzing_summary.md - echo "- **Tests expected to fail:** $(find fuzz/stats -name "*.should_pass" -exec cat {} \; | grep -c "false")" >> fuzzing_summary.md - - # Write to GitHub step summary - cat fuzzing_summary.md >> $GITHUB_STEP_SUMMARY - - name: Show Summary - run: | - cat fuzzing_summary.md - - name: Upload Summary - uses: actions/upload-artifact@v6 - with: - name: fuzzing-summary - path: fuzzing_summary.md - retention-days: 5 diff --git a/.github/workflows/ignore-intermittent.txt b/.github/workflows/ignore-intermittent.txt deleted file mode 100644 index 9e812e9..0000000 --- a/.github/workflows/ignore-intermittent.txt +++ /dev/null @@ -1,7 +0,0 @@ -# List of intermittent test names to ignore in result comparisons -# Format: one test name per line, lines starting with # are comments -# -# Add test names that are known to be flaky or environment-dependent -# Example: -# basic_substitution -# line_address_test \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index dda512c..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,296 +0,0 @@ -# This file was autogenerated by dist: https://axodotdev.github.io/cargo-dist -# -# Copyright 2022-2024, axodotdev -# SPDX-License-Identifier: MIT or Apache-2.0 -# -# CI that: -# -# * checks for a Git Tag that looks like a release -# * builds artifacts with dist (archives, installers, hashes) -# * uploads those artifacts to temporary workflow zip -# * on success, uploads the artifacts to a GitHub Release -# -# Note that the GitHub Release will be created with a generated -# title/body based on your changelogs. - -name: Release -permissions: - "contents": "write" - -# This task will run whenever you push a git tag that looks like a version -# like "1.0.0", "v0.1.0-prerelease.1", "my-app/0.1.0", "releases/v1.0.0", etc. -# Various formats will be parsed into a VERSION and an optional PACKAGE_NAME, where -# PACKAGE_NAME must be the name of a Cargo package in your workspace, and VERSION -# must be a Cargo-style SemVer Version (must have at least major.minor.patch). -# -# If PACKAGE_NAME is specified, then the announcement will be for that -# package (erroring out if it doesn't have the given version or isn't dist-able). -# -# If PACKAGE_NAME isn't specified, then the announcement will be for all -# (dist-able) packages in the workspace with that version (this mode is -# intended for workspaces with only one dist-able package, or with all dist-able -# packages versioned/released in lockstep). -# -# If you push multiple tags at once, separate instances of this workflow will -# spin up, creating an independent announcement for each one. However, GitHub -# will hard limit this to 3 tags per commit, as it will assume more tags is a -# mistake. -# -# If there's a prerelease-style suffix to the version, then the release(s) -# will be marked as a prerelease. -on: - pull_request: - push: - tags: - - '**[0-9]+.[0-9]+.[0-9]+*' - -jobs: - # Run 'dist plan' (or host) to determine what tasks we need to do - plan: - runs-on: "ubuntu-24.04" - outputs: - val: ${{ steps.plan.outputs.manifest }} - tag: ${{ !github.event.pull_request && github.ref_name || '' }} - tag-flag: ${{ !github.event.pull_request && format('--tag={0}', github.ref_name) || '' }} - publishing: ${{ !github.event.pull_request }} - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - steps: - - uses: actions/checkout@v6 - with: - persist-credentials: false - submodules: recursive - - name: Install dist - # we specify bash to get pipefail; it guards against the `curl` command - # failing. otherwise `sh` won't catch that `curl` returned non-0 - shell: bash - run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.30.3/cargo-dist-installer.sh | sh" - - name: Cache dist - uses: actions/upload-artifact@v6 - with: - name: cargo-dist-cache - path: ~/.cargo/bin/dist - # sure would be cool if github gave us proper conditionals... - # so here's a doubly-nested ternary-via-truthiness to try to provide the best possible - # functionality based on whether this is a pull_request, and whether it's from a fork. - # (PRs run on the *source* but secrets are usually on the *target* -- that's *good* - # but also really annoying to build CI around when it needs secrets to work right.) - - id: plan - run: | - dist ${{ (!github.event.pull_request && format('host --steps=create --tag={0}', github.ref_name)) || 'plan' }} --output-format=json > plan-dist-manifest.json - echo "dist ran successfully" - cat plan-dist-manifest.json - echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT" - - name: "Upload dist-manifest.json" - uses: actions/upload-artifact@v6 - with: - name: artifacts-plan-dist-manifest - path: plan-dist-manifest.json - - # Build and packages all the platform-specific things - build-local-artifacts: - name: build-local-artifacts (${{ join(matrix.targets, ', ') }}) - # Let the initial task tell us to not run (currently very blunt) - needs: - - plan - if: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix.include != null && (needs.plan.outputs.publishing == 'true' || fromJson(needs.plan.outputs.val).ci.github.pr_run_mode == 'upload') }} - strategy: - fail-fast: false - # Target platforms/runners are computed by dist in create-release. - # Each member of the matrix has the following arguments: - # - # - runner: the github runner - # - dist-args: cli flags to pass to dist - # - install-dist: expression to run to install dist on the runner - # - # Typically there will be: - # - 1 "global" task that builds universal installers - # - N "local" tasks that build each platform's binaries and platform-specific installers - matrix: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix }} - runs-on: ${{ matrix.runner }} - container: ${{ matrix.container && matrix.container.image || null }} - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - BUILD_MANIFEST_NAME: target/distrib/${{ join(matrix.targets, '-') }}-dist-manifest.json - steps: - - name: enable windows longpaths - run: | - git config --global core.longpaths true - - uses: actions/checkout@v6 - with: - persist-credentials: false - submodules: recursive - - name: Install Rust non-interactively if not already installed - if: ${{ matrix.container }} - run: | - if ! command -v cargo > /dev/null 2>&1; then - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y - echo "$HOME/.cargo/bin" >> $GITHUB_PATH - fi - - name: Install dist - run: ${{ matrix.install_dist.run }} - # Get the dist-manifest - - name: Fetch local artifacts - uses: actions/download-artifact@v7 - with: - pattern: artifacts-* - path: target/distrib/ - merge-multiple: true - - name: Install dependencies - run: | - ${{ matrix.packages_install }} - - name: Build artifacts - run: | - # Actually do builds and make zips and whatnot - dist build ${{ needs.plan.outputs.tag-flag }} --print=linkage --output-format=json ${{ matrix.dist_args }} > dist-manifest.json - echo "dist ran successfully" - - id: cargo-dist - name: Post-build - # We force bash here just because github makes it really hard to get values up - # to "real" actions without writing to env-vars, and writing to env-vars has - # inconsistent syntax between shell and powershell. - shell: bash - run: | - # Parse out what we just built and upload it to scratch storage - echo "paths<> "$GITHUB_OUTPUT" - dist print-upload-files-from-manifest --manifest dist-manifest.json >> "$GITHUB_OUTPUT" - echo "EOF" >> "$GITHUB_OUTPUT" - - cp dist-manifest.json "$BUILD_MANIFEST_NAME" - - name: "Upload artifacts" - uses: actions/upload-artifact@v6 - with: - name: artifacts-build-local-${{ join(matrix.targets, '_') }} - path: | - ${{ steps.cargo-dist.outputs.paths }} - ${{ env.BUILD_MANIFEST_NAME }} - - # Build and package all the platform-agnostic(ish) things - build-global-artifacts: - needs: - - plan - - build-local-artifacts - runs-on: "ubuntu-24.04" - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json - steps: - - uses: actions/checkout@v6 - with: - persist-credentials: false - submodules: recursive - - name: Install cached dist - uses: actions/download-artifact@v7 - with: - name: cargo-dist-cache - path: ~/.cargo/bin/ - - run: chmod +x ~/.cargo/bin/dist - # Get all the local artifacts for the global tasks to use (for e.g. checksums) - - name: Fetch local artifacts - uses: actions/download-artifact@v7 - with: - pattern: artifacts-* - path: target/distrib/ - merge-multiple: true - - id: cargo-dist - shell: bash - run: | - dist build ${{ needs.plan.outputs.tag-flag }} --output-format=json "--artifacts=global" > dist-manifest.json - echo "dist ran successfully" - - # Parse out what we just built and upload it to scratch storage - echo "paths<> "$GITHUB_OUTPUT" - jq --raw-output ".upload_files[]" dist-manifest.json >> "$GITHUB_OUTPUT" - echo "EOF" >> "$GITHUB_OUTPUT" - - cp dist-manifest.json "$BUILD_MANIFEST_NAME" - - name: "Upload artifacts" - uses: actions/upload-artifact@v6 - with: - name: artifacts-build-global - path: | - ${{ steps.cargo-dist.outputs.paths }} - ${{ env.BUILD_MANIFEST_NAME }} - # Determines if we should publish/announce - host: - needs: - - plan - - build-local-artifacts - - build-global-artifacts - # Only run if we're "publishing", and only if plan, local and global didn't fail (skipped is fine) - if: ${{ always() && needs.plan.result == 'success' && needs.plan.outputs.publishing == 'true' && (needs.build-global-artifacts.result == 'skipped' || needs.build-global-artifacts.result == 'success') && (needs.build-local-artifacts.result == 'skipped' || needs.build-local-artifacts.result == 'success') }} - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - runs-on: "ubuntu-24.04" - outputs: - val: ${{ steps.host.outputs.manifest }} - steps: - - uses: actions/checkout@v6 - with: - persist-credentials: false - submodules: recursive - - name: Install cached dist - uses: actions/download-artifact@v7 - with: - name: cargo-dist-cache - path: ~/.cargo/bin/ - - run: chmod +x ~/.cargo/bin/dist - # Fetch artifacts from scratch-storage - - name: Fetch artifacts - uses: actions/download-artifact@v7 - with: - pattern: artifacts-* - path: target/distrib/ - merge-multiple: true - - id: host - shell: bash - run: | - dist host ${{ needs.plan.outputs.tag-flag }} --steps=upload --steps=release --output-format=json > dist-manifest.json - echo "artifacts uploaded and released successfully" - cat dist-manifest.json - echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT" - - name: "Upload dist-manifest.json" - uses: actions/upload-artifact@v6 - with: - # Overwrite the previous copy - name: artifacts-dist-manifest - path: dist-manifest.json - # Create a GitHub Release while uploading all files to it - - name: "Download GitHub Artifacts" - uses: actions/download-artifact@v7 - with: - pattern: artifacts-* - path: artifacts - merge-multiple: true - - name: Cleanup - run: | - # Remove the granular manifests - rm -f artifacts/*-dist-manifest.json - - name: Create GitHub Release - env: - PRERELEASE_FLAG: "${{ fromJson(steps.host.outputs.manifest).announcement_is_prerelease && '--prerelease' || '' }}" - ANNOUNCEMENT_TITLE: "${{ fromJson(steps.host.outputs.manifest).announcement_title }}" - ANNOUNCEMENT_BODY: "${{ fromJson(steps.host.outputs.manifest).announcement_github_body }}" - RELEASE_COMMIT: "${{ github.sha }}" - run: | - # Write and read notes from a file to avoid quoting breaking things - echo "$ANNOUNCEMENT_BODY" > $RUNNER_TEMP/notes.txt - - gh release create "${{ needs.plan.outputs.tag }}" --target "$RELEASE_COMMIT" $PRERELEASE_FLAG --title "$ANNOUNCEMENT_TITLE" --notes-file "$RUNNER_TEMP/notes.txt" artifacts/* - - announce: - needs: - - plan - - host - # use "always() && ..." to allow us to wait for all publish jobs while - # still allowing individual publish jobs to skip themselves (for prereleases). - # "host" however must run to completion, no skipping allowed! - if: ${{ always() && needs.host.result == 'success' }} - runs-on: "ubuntu-24.04" - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - steps: - - uses: actions/checkout@v6 - with: - persist-credentials: false - submodules: recursive diff --git a/Cargo.lock b/Cargo.lock index 07b5481..8cc0d7d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1291,6 +1291,7 @@ dependencies = [ "rand 0.10.0", "regex", "rlimit", + "rustc-hash", "sha2", "sysinfo", "tempfile", diff --git a/Cargo.toml b/Cargo.toml index b01102a..40a1d6e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -68,6 +68,7 @@ memmap2.workspace = true phf = { workspace = true } predicates = { workspace = true } regex = { workspace = true } +rustc-hash = "2.1.1" sysinfo = { workspace = true } tempfile = { workspace = true } terminal_size = { workspace = true } @@ -112,17 +113,12 @@ harness = false # release profile), the debug info and the stack traces will still be available. [profile.release] lto = true - -# A release-like profile that is tuned to be fast, even when being fast -# compromises on binary size. This includes aborting on panic. -[profile.release-fast] -inherits = "release" panic = "abort" -codegen-units = 1 # should be moved to release without regression +codegen-units = 7 # A release-like profile that is as small as possible. [profile.release-small] -inherits = "release-fast" +inherits = "release" opt-level = "z" strip = true diff --git a/src/sed/command.rs b/src/sed/command.rs index 93168af..b329b88 100644 --- a/src/sed/command.rs +++ b/src/sed/command.rs @@ -15,7 +15,7 @@ use crate::sed::script_char_provider::ScriptCharProvider; use crate::sed::script_line_provider::ScriptLineProvider; use std::cell::RefCell; -use std::collections::HashMap; +use rustc_hash::FxHashMap; use std::path::PathBuf; // For file descriptors and equivalent use std::rc::Rc; use uucore::error::UResult; @@ -63,7 +63,7 @@ pub struct ProcessingContext { /// Nesting of { } at compile time pub parsed_block_nesting: usize, /// Command associated with each label - pub label_to_command_map: HashMap>>, + pub label_to_command_map: FxHashMap>>, /// Commands with a (latchable and resetable) address range pub range_commands: Vec>>, /// True if a substitution was made as specified in the t command @@ -214,7 +214,7 @@ const COMMON_UNICODE: usize = 2048; /// Transliteration command (y) pub struct Transliteration { fast: [char; COMMON_UNICODE], - slow: HashMap, + slow: FxHashMap, } impl Default for Transliteration { @@ -226,7 +226,7 @@ impl Default for Transliteration { } Self { fast, - slow: HashMap::new(), + slow: FxHashMap::default(), } } } diff --git a/src/sed/mod.rs b/src/sed/mod.rs index 0bcba9c..9465c0c 100644 --- a/src/sed/mod.rs +++ b/src/sed/mod.rs @@ -25,7 +25,7 @@ use crate::sed::compiler::compile; use crate::sed::processor::process_all_files; use crate::sed::script_line_provider::ScriptValue; use clap::{Arg, ArgMatches, Command, arg, crate_version}; -use std::collections::HashMap; +use rustc_hash::FxHashMap; use std::path::PathBuf; use uucore::error::{UResult, UUsageError}; use uucore::format_usage; @@ -216,7 +216,7 @@ fn build_context(matches: &ArgMatches) -> ProcessingContext { input_action: None, hold: StringSpace::default(), parsed_block_nesting: 0, - label_to_command_map: HashMap::new(), + label_to_command_map: FxHashMap::default(), range_commands: Vec::new(), substitution_made: false, append_elements: Vec::new(),