mirror of
https://github.com/uutils/sed.git
synced 2026-06-10 16:14:15 -07:00
Compare commits
@@ -0,0 +1,80 @@
|
|||||||
|
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@v9
|
||||||
|
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@v9
|
||||||
|
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');
|
||||||
|
}
|
||||||
@@ -0,0 +1,286 @@
|
|||||||
|
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
|
||||||
|
tar -C target/release -cf - sed | zstd -19 -o ../sed-x86_64-unknown-linux-gnu.tar.zst
|
||||||
|
- name: Publish latest commit
|
||||||
|
uses: softprops/action-gh-release@v3
|
||||||
|
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||||
|
with:
|
||||||
|
tag_name: latest-commit
|
||||||
|
body: |
|
||||||
|
commit: ${{ github.sha }}
|
||||||
|
draft: false
|
||||||
|
prerelease: true
|
||||||
|
files: |
|
||||||
|
sed-x86_64-unknown-linux-gnu.tar.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 sed source directory (testsuite is at gnu.sed/testsuite/)
|
||||||
|
export GNU_SED_DIR="../gnu.sed"
|
||||||
|
# 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@v7
|
||||||
|
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@v7
|
||||||
|
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@v21
|
||||||
|
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@v8
|
||||||
|
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@v7
|
||||||
|
with:
|
||||||
|
name: "${{ steps.summary.outputs.HASH }}"
|
||||||
|
path: "${{ steps.vars.outputs.TEST_SUMMARY_FILE }}"
|
||||||
|
|
||||||
|
- name: Upload test results summary
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
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@v7
|
||||||
|
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
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
name: Security audit
|
||||||
|
|
||||||
|
# spell-checker:ignore (misc) rustsec
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: "0 0 * * *"
|
||||||
|
jobs:
|
||||||
|
audit:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
- uses: rustsec/audit-check@v2
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@@ -29,21 +29,16 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Install system dependencies
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
sudo apt-get -y update
|
|
||||||
|
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
|
||||||
- name: Run sccache-cache
|
- name: Run sccache-cache
|
||||||
uses: mozilla-actions/sccache-action@v0.0.9
|
uses: mozilla-actions/sccache-action@v0.0.10
|
||||||
|
|
||||||
- name: Install cargo-codspeed
|
- name: Install tools
|
||||||
shell: bash
|
uses: taiki-e/install-action@v2
|
||||||
run: cargo install cargo-codspeed --locked
|
with:
|
||||||
|
tool: cargo-codspeed
|
||||||
|
|
||||||
- name: Build benchmarks for ${{ matrix.benchmark-target.package }}
|
- name: Build benchmarks for ${{ matrix.benchmark-target.package }}
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|||||||
@@ -17,6 +17,27 @@ jobs:
|
|||||||
- uses: dtolnay/rust-toolchain@stable
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
- run: cargo check
|
- run: cargo check
|
||||||
|
|
||||||
|
check_android:
|
||||||
|
name: cargo check (Android ${{ matrix.target }})
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
target: [aarch64-linux-android, armv7-linux-androideabi]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
|
with:
|
||||||
|
targets: ${{ matrix.target }}
|
||||||
|
- uses: android-actions/setup-android@v4
|
||||||
|
- name: Install Android NDK
|
||||||
|
run: sdkmanager "ndk;29.0.14206865"
|
||||||
|
- name: Install cargo-ndk
|
||||||
|
run: cargo install cargo-ndk
|
||||||
|
- name: Check Android target
|
||||||
|
run: cargo ndk --platform 21 --target ${{ matrix.target }} check
|
||||||
|
env:
|
||||||
|
ANDROID_NDK_HOME: ${{ env.ANDROID_SDK_ROOT }}/ndk/29.0.14206865
|
||||||
|
|
||||||
test:
|
test:
|
||||||
name: cargo test
|
name: cargo test
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
@@ -109,7 +130,7 @@ jobs:
|
|||||||
ls -al
|
ls -al
|
||||||
echo "report=${COVERAGE_REPORT_FILE}" >> $GITHUB_OUTPUT
|
echo "report=${COVERAGE_REPORT_FILE}" >> $GITHUB_OUTPUT
|
||||||
- name: Upload coverage results (to Codecov.io)
|
- name: Upload coverage results (to Codecov.io)
|
||||||
uses: codecov/codecov-action@v5
|
uses: codecov/codecov-action@v7
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.CODECOV_TOKEN }}
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
files: ${{ steps.coverage.outputs.report }}
|
files: ${{ steps.coverage.outputs.report }}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ jobs:
|
|||||||
components: clippy
|
components: clippy
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
- name: Run sccache-cache
|
- name: Run sccache-cache
|
||||||
uses: mozilla-actions/sccache-action@v0.0.9
|
uses: mozilla-actions/sccache-action@v0.0.10
|
||||||
- name: Initialize workflow variables
|
- name: Initialize workflow variables
|
||||||
id: vars
|
id: vars
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -69,9 +69,8 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
## `cargo clippy` lint testing
|
## `cargo clippy` lint testing
|
||||||
unset fault
|
unset fault
|
||||||
CLIPPY_FLAGS="-W clippy::default_trait_access -W clippy::manual_string_new -W clippy::cognitive_complexity -W clippy::implicit_clone -W clippy::range-plus-one -W clippy::redundant-clone -W clippy::match_bool -W clippy::semicolon_if_nothing_returned"
|
|
||||||
fault_type="${{ steps.vars.outputs.FAULT_TYPE }}"
|
fault_type="${{ steps.vars.outputs.FAULT_TYPE }}"
|
||||||
fault_prefix=$(echo "$fault_type" | tr '[:lower:]' '[:upper:]')
|
fault_prefix=$(echo "$fault_type" | tr '[:lower:]' '[:upper:]')
|
||||||
# * convert any warnings to GHA UI annotations; ref: <https://help.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-a-warning-message>
|
# * convert any warnings to GHA UI annotations; ref: <https://help.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-a-warning-message>
|
||||||
S=$(cargo clippy --all-targets --workspace -psed -- ${CLIPPY_FLAGS} -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 ; }
|
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
|
if [ -n "${{ steps.vars.outputs.FAIL_ON_FAULT }}" ] && [ -n "$fault" ]; then exit 1 ; fi
|
||||||
|
|||||||
@@ -24,15 +24,17 @@ jobs:
|
|||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: dtolnay/rust-toolchain@nightly
|
|
||||||
- name: Install `cargo-fuzz`
|
- name: Install `cargo-fuzz`
|
||||||
run: cargo install cargo-fuzz
|
uses: taiki-e/install-action@v2
|
||||||
|
with:
|
||||||
|
tool: cargo-fuzz
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
with:
|
with:
|
||||||
shared-key: "cargo-fuzz-cache-key"
|
shared-key: "cargo-fuzz-cache-key"
|
||||||
cache-directories: "fuzz/target"
|
cache-directories: "fuzz/target"
|
||||||
- name: Run `cargo-fuzz build`
|
- name: Run `cargo-fuzz build`
|
||||||
run: cargo +nightly fuzz build
|
# https://github.com/rust-fuzz/cargo-fuzz/issues/398
|
||||||
|
run: env RUSTC_BOOTSTRAP=1 cargo fuzz build --target $(rustc --print host-tuple)
|
||||||
|
|
||||||
fuzz-run:
|
fuzz-run:
|
||||||
needs: fuzz-build
|
needs: fuzz-build
|
||||||
@@ -50,9 +52,10 @@ jobs:
|
|||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- uses: dtolnay/rust-toolchain@nightly
|
|
||||||
- name: Install `cargo-fuzz`
|
- name: Install `cargo-fuzz`
|
||||||
run: cargo install cargo-fuzz
|
uses: taiki-e/install-action@v2
|
||||||
|
with:
|
||||||
|
tool: cargo-fuzz
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
with:
|
with:
|
||||||
shared-key: "cargo-fuzz-cache-key"
|
shared-key: "cargo-fuzz-cache-key"
|
||||||
@@ -70,7 +73,8 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
mkdir -p fuzz/stats
|
mkdir -p fuzz/stats
|
||||||
STATS_FILE="fuzz/stats/${{ matrix.test-target.name }}.txt"
|
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"
|
# https://github.com/rust-fuzz/cargo-fuzz/issues/398
|
||||||
|
env RUSTC_BOOTSTRAP=1 cargo fuzz run --target $(rustc --print host-tuple) ${{ 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
|
# Extract key stats from the output
|
||||||
if grep -q "stat::number_of_executed_units" "$STATS_FILE"; then
|
if grep -q "stat::number_of_executed_units" "$STATS_FILE"; then
|
||||||
@@ -146,7 +150,7 @@ jobs:
|
|||||||
path: |
|
path: |
|
||||||
fuzz/corpus/${{ matrix.test-target.name }}
|
fuzz/corpus/${{ matrix.test-target.name }}
|
||||||
- name: Upload Stats
|
- name: Upload Stats
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: fuzz-stats-${{ matrix.test-target.name }}
|
name: fuzz-stats-${{ matrix.test-target.name }}
|
||||||
path: |
|
path: |
|
||||||
@@ -163,7 +167,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Download all stats
|
- name: Download all stats
|
||||||
uses: actions/download-artifact@v7
|
uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
path: fuzz/stats-artifacts
|
path: fuzz/stats-artifacts
|
||||||
pattern: fuzz-stats-*
|
pattern: fuzz-stats-*
|
||||||
@@ -257,7 +261,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
cat fuzzing_summary.md
|
cat fuzzing_summary.md
|
||||||
- name: Upload Summary
|
- name: Upload Summary
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: fuzzing-summary
|
name: fuzzing-summary
|
||||||
path: fuzzing_summary.md
|
path: fuzzing_summary.md
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# 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
|
||||||
@@ -66,7 +66,7 @@ jobs:
|
|||||||
shell: bash
|
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"
|
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
|
- name: Cache dist
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: cargo-dist-cache
|
name: cargo-dist-cache
|
||||||
path: ~/.cargo/bin/dist
|
path: ~/.cargo/bin/dist
|
||||||
@@ -82,7 +82,7 @@ jobs:
|
|||||||
cat plan-dist-manifest.json
|
cat plan-dist-manifest.json
|
||||||
echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT"
|
echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT"
|
||||||
- name: "Upload dist-manifest.json"
|
- name: "Upload dist-manifest.json"
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: artifacts-plan-dist-manifest
|
name: artifacts-plan-dist-manifest
|
||||||
path: plan-dist-manifest.json
|
path: plan-dist-manifest.json
|
||||||
@@ -131,7 +131,7 @@ jobs:
|
|||||||
run: ${{ matrix.install_dist.run }}
|
run: ${{ matrix.install_dist.run }}
|
||||||
# Get the dist-manifest
|
# Get the dist-manifest
|
||||||
- name: Fetch local artifacts
|
- name: Fetch local artifacts
|
||||||
uses: actions/download-artifact@v7
|
uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
pattern: artifacts-*
|
pattern: artifacts-*
|
||||||
path: target/distrib/
|
path: target/distrib/
|
||||||
@@ -158,7 +158,7 @@ jobs:
|
|||||||
|
|
||||||
cp dist-manifest.json "$BUILD_MANIFEST_NAME"
|
cp dist-manifest.json "$BUILD_MANIFEST_NAME"
|
||||||
- name: "Upload artifacts"
|
- name: "Upload artifacts"
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: artifacts-build-local-${{ join(matrix.targets, '_') }}
|
name: artifacts-build-local-${{ join(matrix.targets, '_') }}
|
||||||
path: |
|
path: |
|
||||||
@@ -180,14 +180,14 @@ jobs:
|
|||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
- name: Install cached dist
|
- name: Install cached dist
|
||||||
uses: actions/download-artifact@v7
|
uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: cargo-dist-cache
|
name: cargo-dist-cache
|
||||||
path: ~/.cargo/bin/
|
path: ~/.cargo/bin/
|
||||||
- run: chmod +x ~/.cargo/bin/dist
|
- run: chmod +x ~/.cargo/bin/dist
|
||||||
# Get all the local artifacts for the global tasks to use (for e.g. checksums)
|
# Get all the local artifacts for the global tasks to use (for e.g. checksums)
|
||||||
- name: Fetch local artifacts
|
- name: Fetch local artifacts
|
||||||
uses: actions/download-artifact@v7
|
uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
pattern: artifacts-*
|
pattern: artifacts-*
|
||||||
path: target/distrib/
|
path: target/distrib/
|
||||||
@@ -205,7 +205,7 @@ jobs:
|
|||||||
|
|
||||||
cp dist-manifest.json "$BUILD_MANIFEST_NAME"
|
cp dist-manifest.json "$BUILD_MANIFEST_NAME"
|
||||||
- name: "Upload artifacts"
|
- name: "Upload artifacts"
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: artifacts-build-global
|
name: artifacts-build-global
|
||||||
path: |
|
path: |
|
||||||
@@ -230,14 +230,14 @@ jobs:
|
|||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
- name: Install cached dist
|
- name: Install cached dist
|
||||||
uses: actions/download-artifact@v7
|
uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
name: cargo-dist-cache
|
name: cargo-dist-cache
|
||||||
path: ~/.cargo/bin/
|
path: ~/.cargo/bin/
|
||||||
- run: chmod +x ~/.cargo/bin/dist
|
- run: chmod +x ~/.cargo/bin/dist
|
||||||
# Fetch artifacts from scratch-storage
|
# Fetch artifacts from scratch-storage
|
||||||
- name: Fetch artifacts
|
- name: Fetch artifacts
|
||||||
uses: actions/download-artifact@v7
|
uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
pattern: artifacts-*
|
pattern: artifacts-*
|
||||||
path: target/distrib/
|
path: target/distrib/
|
||||||
@@ -250,14 +250,14 @@ jobs:
|
|||||||
cat dist-manifest.json
|
cat dist-manifest.json
|
||||||
echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT"
|
echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT"
|
||||||
- name: "Upload dist-manifest.json"
|
- name: "Upload dist-manifest.json"
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
# Overwrite the previous copy
|
# Overwrite the previous copy
|
||||||
name: artifacts-dist-manifest
|
name: artifacts-dist-manifest
|
||||||
path: dist-manifest.json
|
path: dist-manifest.json
|
||||||
# Create a GitHub Release while uploading all files to it
|
# Create a GitHub Release while uploading all files to it
|
||||||
- name: "Download GitHub Artifacts"
|
- name: "Download GitHub Artifacts"
|
||||||
uses: actions/download-artifact@v7
|
uses: actions/download-artifact@v8
|
||||||
with:
|
with:
|
||||||
pattern: artifacts-*
|
pattern: artifacts-*
|
||||||
path: artifacts
|
path: artifacts
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# spell-checker:ignore wasip
|
||||||
|
name: WASI
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
# 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:
|
||||||
|
test_wasi:
|
||||||
|
name: Tests
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
|
with:
|
||||||
|
targets: wasm32-wasip1
|
||||||
|
- name: check
|
||||||
|
run: cargo check --target wasm32-wasip1
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
# See https://pre-commit.com for more information
|
||||||
|
# See https://pre-commit.com/hooks.html for more hooks
|
||||||
|
exclude: ^tests/fixtures/
|
||||||
|
repos:
|
||||||
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
|
rev: v6.0.0
|
||||||
|
hooks:
|
||||||
|
- id: check-added-large-files
|
||||||
|
- id: check-executables-have-shebangs
|
||||||
|
- id: check-json
|
||||||
|
exclude: '\.vscode/(cSpell|extensions)\.json' # cSpell.json and extensions.json use comments
|
||||||
|
- id: check-shebang-scripts-are-executable
|
||||||
|
exclude: '.+\.rs' # would be triggered by #![some_attribute]
|
||||||
|
- id: check-symlinks
|
||||||
|
- id: check-toml
|
||||||
|
- id: check-yaml
|
||||||
|
args: [ --allow-multiple-documents ]
|
||||||
|
- id: destroyed-symlinks
|
||||||
|
- id: end-of-file-fixer
|
||||||
|
- id: mixed-line-ending
|
||||||
|
args: [ --fix=lf ]
|
||||||
|
- id: trailing-whitespace
|
||||||
|
|
||||||
|
- repo: local
|
||||||
|
hooks:
|
||||||
|
- id: rust-linting
|
||||||
|
name: Rust linting
|
||||||
|
description: Run cargo fmt on files included in the commit.
|
||||||
|
entry: cargo +stable fmt --
|
||||||
|
pass_filenames: true
|
||||||
|
types: [file, rust]
|
||||||
|
language: system
|
||||||
|
- id: rust-clippy
|
||||||
|
name: Rust clippy
|
||||||
|
description: Run cargo clippy on files included in the commit.
|
||||||
|
entry: cargo +stable clippy --workspace --all-targets --all-features -- -D warnings
|
||||||
|
pass_filenames: false
|
||||||
|
types: [file, rust]
|
||||||
|
language: system
|
||||||
|
- id: cargo-lock-check
|
||||||
|
name: Cargo.lock sync check
|
||||||
|
description: Ensure Cargo.lock and fuzz/Cargo.lock are up-to-date.
|
||||||
|
entry: bash -c 'for dir in . fuzz; do ( cd "$dir" && cargo fetch --quiet ); done'
|
||||||
|
pass_filenames: false
|
||||||
|
files: 'Cargo\.(toml|lock)$'
|
||||||
|
language: system
|
||||||
|
- id: cspell
|
||||||
|
name: Code spell checker (cspell)
|
||||||
|
description: Run cspell to check for spelling errors (if available).
|
||||||
|
entry: bash -c 'if command -v cspell >/dev/null 2>&1; then cspell --no-must-find-files -- "$@"; else echo "cspell not found, skipping spell check"; exit 0; fi' --
|
||||||
|
pass_filenames: true
|
||||||
|
language: system
|
||||||
|
|
||||||
|
ci:
|
||||||
|
skip: [rust-linting, rust-clippy, cargo-lock-check, cspell]
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
# Contributing to sed
|
||||||
|
|
||||||
|
Hi! Welcome to uutils/sed, and thanks for wanting to contribute!
|
||||||
|
|
||||||
|
This project follows the shared conventions of the [uutils](https://github.com/uutils)
|
||||||
|
organization. Before opening a pull request, please read:
|
||||||
|
|
||||||
|
- Our **[Review Guidelines](https://uutils.github.io/reviews/)** — what we expect
|
||||||
|
from a pull request and how reviews are carried out.
|
||||||
|
- Our community's [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md), if present.
|
||||||
|
|
||||||
|
Finally, feel free to join our [Discord](https://discord.gg/wQVJbvJ)!
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> uutils is original code and cannot contain any code from GNU or other
|
||||||
|
> strongly-licensed (GPL/LGPL) implementations. We **cannot** accept changes
|
||||||
|
> based on the GNU source code, and you **must not link** to it either. You may
|
||||||
|
> look at permissively-licensed implementations (MIT/BSD) and read the GNU
|
||||||
|
> *manuals* — never the GNU *source*.
|
||||||
|
|
||||||
|
## In short
|
||||||
|
|
||||||
|
- Discuss non-trivial changes in an issue **before** writing the code.
|
||||||
|
- Keep pull requests **small, self-contained, and descriptively titled**
|
||||||
|
(e.g. `sed: fix ...`).
|
||||||
|
- Make sure CI passes: tests are green, `rustfmt` is satisfied, and there are
|
||||||
|
no `clippy` warnings.
|
||||||
|
- Add tests for new behavior; don't let coverage regress.
|
||||||
|
- Write small, atomic commits annotated with the component you touched.
|
||||||
|
|
||||||
|
See the [Review Guidelines](https://uutils.github.io/reviews/) for the full
|
||||||
|
details.
|
||||||
Generated
+526
-351
File diff suppressed because it is too large
Load Diff
+55
-26
@@ -35,36 +35,34 @@ chrono = { version = "0.4.37", default-features = false, features = [
|
|||||||
] }
|
] }
|
||||||
clap = { version = "4.4", features = ["wrap_help", "cargo"] }
|
clap = { version = "4.4", features = ["wrap_help", "cargo"] }
|
||||||
clap_complete = "4.5"
|
clap_complete = "4.5"
|
||||||
clap_mangen = "0.2"
|
clap_mangen = "0.3"
|
||||||
divan = { package = "codspeed-divan-compat", version = "4.0.5" }
|
divan = { package = "codspeed-divan-compat", version = "4.0.5" }
|
||||||
fancy-regex = "0.17.0"
|
fancy-regex = "0.18.0"
|
||||||
|
hex = "0.4"
|
||||||
libc = "0.2.153"
|
libc = "0.2.153"
|
||||||
memchr = "2.7.4"
|
memchr = "2.7.4"
|
||||||
memmap2 = "0.9"
|
memmap2 = "0.9"
|
||||||
once_cell = "1.21.3"
|
|
||||||
phf = "0.13.0"
|
phf = "0.13.0"
|
||||||
phf_codegen = "0.13.0"
|
phf_codegen = "0.13.0"
|
||||||
predicates = "3.1.3"
|
predicates = "3.1.3"
|
||||||
rand = { version = "0.9", features = ["small_rng"] }
|
rand = { version = "0.10.0" }
|
||||||
regex = "1.10.4"
|
regex = "1.10.4"
|
||||||
sysinfo = "0.37"
|
sha2 = "0.11"
|
||||||
|
sysinfo = "0.38"
|
||||||
tempfile = "3.10.1"
|
tempfile = "3.10.1"
|
||||||
textwrap = { version = "0.16.1", features = ["terminal_size"] }
|
|
||||||
terminal_size = "0.4.2"
|
terminal_size = "0.4.2"
|
||||||
uucore = { version = "0.5.0", features = ["libc"] }
|
textwrap = { version = "0.16.1", features = ["terminal_size"] }
|
||||||
|
uucore = { version = "0.9.0", features = ["libc"] }
|
||||||
xattr = "1.3.1"
|
xattr = "1.3.1"
|
||||||
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
assert_fs = { workspace = true }
|
|
||||||
clap = { workspace = true }
|
clap = { workspace = true }
|
||||||
clap_complete = { workspace = true }
|
clap_complete = { workspace = true }
|
||||||
clap_mangen = { workspace = true }
|
clap_mangen = { workspace = true }
|
||||||
ctor = "0.6.0"
|
|
||||||
fancy-regex = { workspace = true }
|
fancy-regex = { workspace = true }
|
||||||
memchr = { workspace = true }
|
memchr = { workspace = true }
|
||||||
memmap2.workspace = true
|
memmap2.workspace = true
|
||||||
once_cell = { workspace = true }
|
|
||||||
phf = { workspace = true }
|
phf = { workspace = true }
|
||||||
predicates = { workspace = true }
|
predicates = { workspace = true }
|
||||||
regex = { workspace = true }
|
regex = { workspace = true }
|
||||||
@@ -75,21 +73,25 @@ textwrap = { workspace = true }
|
|||||||
uucore = { workspace = true }
|
uucore = { workspace = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
assert_fs = { workspace = true }
|
||||||
chrono = { workspace = true }
|
chrono = { workspace = true }
|
||||||
|
ctor = "1.0.0"
|
||||||
divan = { workspace = true }
|
divan = { workspace = true }
|
||||||
|
hex = { workspace = true }
|
||||||
libc = { workspace = true }
|
libc = { workspace = true }
|
||||||
pretty_assertions = "1"
|
pretty_assertions = "1"
|
||||||
rand = { workspace = true }
|
rand = { workspace = true }
|
||||||
regex = { workspace = true }
|
regex = { workspace = true }
|
||||||
|
sha2 = { workspace = true }
|
||||||
tempfile = { workspace = true }
|
tempfile = { workspace = true }
|
||||||
uucore = { workspace = true, features = ["entries", "process", "signals", "benchmark"] }
|
uucore = { workspace = true, features = ["entries", "process", "signals", "benchmark"] }
|
||||||
uutests = "0.5.0"
|
uutests = "0.9.0"
|
||||||
|
|
||||||
[target.'cfg(unix)'.dev-dependencies]
|
[target.'cfg(unix)'.dev-dependencies]
|
||||||
xattr = { workspace = true }
|
xattr = { workspace = true }
|
||||||
|
|
||||||
[target.'cfg(any(target_os = "linux", target_os = "android"))'.dev-dependencies]
|
[target.'cfg(any(target_os = "linux", target_os = "android"))'.dev-dependencies]
|
||||||
rlimit = "0.10.1"
|
rlimit = "0.11.0"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
phf_codegen = { workspace = true }
|
phf_codegen = { workspace = true }
|
||||||
@@ -105,26 +107,53 @@ path = "src/bin/sed.rs"
|
|||||||
name = "sed_bench"
|
name = "sed_bench"
|
||||||
harness = false
|
harness = false
|
||||||
|
|
||||||
# The default release profile. It contains all optimizations, without
|
|
||||||
# sacrificing debug info. With this profile (like in the standard
|
|
||||||
# release profile), the debug info and the stack traces will still be available.
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
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"
|
panic = "abort"
|
||||||
codegen-units = 1
|
# should be dropped to 1 for binary size without performance drop
|
||||||
|
codegen-units = 7
|
||||||
|
|
||||||
# A release-like profile that is as small as possible.
|
# A release-like profile that is as small as possible.
|
||||||
[profile.release-small]
|
[profile.release-small]
|
||||||
inherits = "release-fast"
|
inherits = "release"
|
||||||
opt-level = "z"
|
opt-level = "z"
|
||||||
strip = true
|
strip = true
|
||||||
|
|
||||||
# The profile that 'cargo dist' will build with
|
[lints.clippy]
|
||||||
[profile.dist]
|
all = { level = "warn", priority = -1 }
|
||||||
inherits = "release"
|
|
||||||
lto = "thin"
|
cargo = { level = "warn", priority = -1 }
|
||||||
|
|
||||||
|
pedantic = { level = "warn", priority = -1 }
|
||||||
|
# The counts were generated with this command:
|
||||||
|
# cargo +nightly clippy --all-targets --workspace --message-format=json --quiet \
|
||||||
|
# | jq -r '.message.code.code | select(. != null and startswith("clippy::"))' \
|
||||||
|
# | sort | uniq -c | sort -h -r
|
||||||
|
missing_errors_doc = "allow" # 69
|
||||||
|
doc_markdown = "allow" # 63
|
||||||
|
must_use_candidate = "allow" # 56
|
||||||
|
needless_raw_string_hashes = "allow" # 20
|
||||||
|
needless_pass_by_value = "allow" # 15
|
||||||
|
missing_panics_doc = "allow" # 12
|
||||||
|
cast_possible_truncation = "allow" # 7
|
||||||
|
unnecessary_wraps = "allow" # 6
|
||||||
|
match_wildcard_for_single_variants = "allow" # 4
|
||||||
|
cast_sign_loss = "allow" # 4
|
||||||
|
cast_possible_wrap = "allow" # 4
|
||||||
|
uninlined_format_args = "allow" # 3
|
||||||
|
similar_names = "allow" # 3
|
||||||
|
used_underscore_binding = "allow" # 2
|
||||||
|
too_many_lines = "allow" # 2
|
||||||
|
struct_excessive_bools = "allow" # 2
|
||||||
|
match_same_arms = "allow" # 2
|
||||||
|
ignore_without_reason = "allow" # 2
|
||||||
|
format_push_string = "allow" # 2
|
||||||
|
should_panic_without_expect = "allow" # 1
|
||||||
|
many_single_char_names = "allow" # 1
|
||||||
|
comparison_chain = "allow"
|
||||||
|
multiple_crate_versions = "allow"
|
||||||
|
unnested_or_patterns = "allow"
|
||||||
|
|
||||||
|
restriction = { level = "allow", priority = -1 }
|
||||||
|
cognitive_complexity = "warn"
|
||||||
|
redundant_clone = "warn"
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
[](https://github.com/uutils/sed/blob/main/LICENSE)
|
[](https://github.com/uutils/sed/blob/main/LICENSE)
|
||||||
[](https://deps.rs/repo/github/uutils/sed)
|
[](https://deps.rs/repo/github/uutils/sed)
|
||||||
|
|
||||||
[](https://codecov.io/gh/uutils/sed)
|
[](https://codecov.io/gh/uutils/sed)
|
||||||
|
|
||||||
# sed
|
# sed
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ and other extensions.
|
|||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
At this state _sed_ implements all POSIX commands
|
At this state _sed_ implements all [POSIX features](https://pubs.opengroup.org/onlinepubs/9799919799/)
|
||||||
and can run correctly the two complex scripts of its integration tests:
|
and can run correctly the two complex scripts of its integration tests:
|
||||||
[hanoi.sed](https://github.com/uutils/sed/blob/main/tests/fixtures/sed/script/hanoi.sed) (solves the Towers of Hanoi puzzle) and
|
[hanoi.sed](https://github.com/uutils/sed/blob/main/tests/fixtures/sed/script/hanoi.sed) (solves the Towers of Hanoi puzzle) and
|
||||||
[math.sed](https://github.com/uutils/sed/blob/main/tests/fixtures/sed/script/math.sed) (implements an arbitrary precision integer math calculator).
|
[math.sed](https://github.com/uutils/sed/blob/main/tests/fixtures/sed/script/math.sed) (implements an arbitrary precision integer math calculator).
|
||||||
@@ -28,7 +28,10 @@ Further work aims to:
|
|||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
Ensure you have Rust installed on your system. You can install Rust through [rustup](https://rustup.rs/).
|
We provide a Linux x86_64 binary archive from the main branch at
|
||||||
|
https://github.com/uutils/sed/releases/tag/latest-commit .
|
||||||
|
|
||||||
|
For other platforms, ensure you have Rust installed on your system. You can install Rust through [rustup](https://rustup.rs/).
|
||||||
|
|
||||||
Clone the repository and build the project using Cargo:
|
Clone the repository and build the project using Cargo:
|
||||||
|
|
||||||
@@ -41,6 +44,37 @@ cargo run --release
|
|||||||
|
|
||||||
The binary is named `sed` in `target/release/sed`.
|
The binary is named `sed` in `target/release/sed`.
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
### GNU sed Compatibility Testing
|
||||||
|
|
||||||
|
Test compatibility against GNU sed by running the upstream testsuite shell scripts
|
||||||
|
with a lightweight gnulib test-framework shim:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Clone GNU sed testsuite (one time setup)
|
||||||
|
git clone https://github.com/mirror/sed.git ../gnu.sed
|
||||||
|
|
||||||
|
# Run compatibility tests
|
||||||
|
./util/run-gnu-testsuite.sh
|
||||||
|
|
||||||
|
# Verbose mode shows failure details
|
||||||
|
./util/run-gnu-testsuite.sh -v
|
||||||
|
|
||||||
|
# Generate JSON results for CI
|
||||||
|
./util/run-gnu-testsuite.sh --json-output results.json
|
||||||
|
```
|
||||||
|
|
||||||
|
The harness executes each `.sh` test from the GNU sed testsuite directly, injecting
|
||||||
|
our Rust sed binary via `PATH` and providing shim implementations of the gnulib test
|
||||||
|
framework functions (`compare_`, `returns_`, `skip_`, etc.).
|
||||||
|
|
||||||
|
### Unit Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo test
|
||||||
|
```
|
||||||
|
|
||||||
## Extensions and incompatibilities
|
## Extensions and incompatibilities
|
||||||
### Supported GNU extensions
|
### Supported GNU extensions
|
||||||
* Command-line arguments can be specified in long (`--`) form.
|
* Command-line arguments can be specified in long (`--`) form.
|
||||||
@@ -53,11 +87,16 @@ The binary is named `sed` in `target/release/sed`.
|
|||||||
* The `a`, `c`, and `i` commands do not require an initial backslash,
|
* The `a`, `c`, and `i` commands do not require an initial backslash,
|
||||||
allow text to appear on the same line, and support escape sequences
|
allow text to appear on the same line, and support escape sequences
|
||||||
in the specified text.
|
in the specified text.
|
||||||
|
* The `a`, `i`, `=`, `l`, `q` and `r` commands support address range as an extension to POSIX.
|
||||||
* The substitution command replacement group `\0` is a synonym for &.
|
* The substitution command replacement group `\0` is a synonym for &.
|
||||||
* A `Q` command (optionally followed by an exit code) quits immediately.
|
* A `Q` command (optionally followed by an exit code) quits immediately.
|
||||||
* The `q` command can be optionally followed by an exit code.
|
* The `q` command can be optionally followed by an exit code.
|
||||||
* The `l` command can be optionally followed by the output width.
|
* The `l` command can be optionally followed by the output width.
|
||||||
* The `--follow-symlinks` flag for in-place editing.
|
* The `--follow-symlinks` flag for in-place editing.
|
||||||
|
* Address 0 can be used to specify an address range that is already
|
||||||
|
active on line 1 and can finish with the specified regular expression.
|
||||||
|
* Address steps can be specified in the form of start~step and start,~step
|
||||||
|
ranges.
|
||||||
|
|
||||||
### Supported BSD and GNU extensions
|
### Supported BSD and GNU extensions
|
||||||
* The second address in a range can be specified as a relative address with +N.
|
* The second address in a range can be specified as a relative address with +N.
|
||||||
@@ -87,6 +126,13 @@ The binary is named `sed` in `target/release/sed`.
|
|||||||
* Labels are parsed for alphanumeric characters. The BSD version parses them
|
* Labels are parsed for alphanumeric characters. The BSD version parses them
|
||||||
until the end of the line, preventing ; to be used as a separator.
|
until the end of the line, preventing ; to be used as a separator.
|
||||||
|
|
||||||
|
## GNU test suite compatibility
|
||||||
|
|
||||||
|
Below is the evolution of how many GNU tests uutils passes.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
sed is licensed under the MIT License - see the `LICENSE` file for details
|
sed is licensed under the MIT License - see the `LICENSE` file for details
|
||||||
|
|||||||
+44
@@ -0,0 +1,44 @@
|
|||||||
|
# Security Policy
|
||||||
|
|
||||||
|
## Supported Versions
|
||||||
|
|
||||||
|
We provide security updates only for the latest released version of `uutils/sed`.
|
||||||
|
Older versions may not receive patches.
|
||||||
|
If you are using a version packaged by your Linux distribution, please check with your distribution maintainers for their update policy.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
|
**Do not open public GitHub issues for security vulnerabilities.**
|
||||||
|
This prevents accidental disclosure before a fix is available.
|
||||||
|
|
||||||
|
Instead, please use the following method:
|
||||||
|
|
||||||
|
- **Email:** [sylvestre@debian.org](mailto:Sylvestre@debian.org)
|
||||||
|
- **Encryption (optional):** You may encrypt your report using our PGP key:
|
||||||
|
Fingerprint: B60D B599 4D39 BEC4 D1A9 5CCF 7E65 28DA 752F 1BE1
|
||||||
|
---
|
||||||
|
|
||||||
|
### What to Include in Your Report
|
||||||
|
|
||||||
|
To help us investigate and resolve the issue quickly, please include as much detail as possible:
|
||||||
|
|
||||||
|
- **Type of issue:** e.g. privilege escalation, information disclosure.
|
||||||
|
- **Location in the source:** file path, commit hash, branch, or tag.
|
||||||
|
- **Steps to reproduce:** exact commands, test cases, or scripts.
|
||||||
|
- **Special configuration:** any flags, environment variables, or system setup required.
|
||||||
|
- **Affected systems:** OS/distribution and version(s) where the issue occurs.
|
||||||
|
- **Impact:** your assessment of the potential severity (DoS, RCE, data leak, etc.).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Disclosure Policy
|
||||||
|
|
||||||
|
We follow a **Coordinated Vulnerability Disclosure (CVD)** process:
|
||||||
|
|
||||||
|
1. We will acknowledge receipt of your report within **10 days**.
|
||||||
|
2. We will investigate, reproduce, and assess the issue.
|
||||||
|
3. We will provide a timeline for developing and releasing a fix.
|
||||||
|
4. Once a fix is available, we will publish a GitHub Security Advisory.
|
||||||
|
5. You will be credited in the advisory unless you request anonymity.
|
||||||
@@ -17,7 +17,7 @@ fn no_op_short(bencher: Bencher) {
|
|||||||
let temp_dir = tempfile::tempdir().unwrap();
|
let temp_dir = tempfile::tempdir().unwrap();
|
||||||
let mut data = Vec::new();
|
let mut data = Vec::new();
|
||||||
for i in 0..10_000_000 {
|
for i in 0..10_000_000 {
|
||||||
data.extend_from_slice(format!("{}\n", i).as_bytes());
|
data.extend_from_slice(format!("{i}\n").as_bytes());
|
||||||
}
|
}
|
||||||
let file_path = create_test_file(&data, temp_dir.path());
|
let file_path = create_test_file(&data, temp_dir.path());
|
||||||
let file_path_str = file_path.to_str().unwrap();
|
let file_path_str = file_path.to_str().unwrap();
|
||||||
@@ -192,7 +192,7 @@ fn remove_cr(bencher: Bencher) {
|
|||||||
let temp_dir = tempfile::tempdir().unwrap();
|
let temp_dir = tempfile::tempdir().unwrap();
|
||||||
let mut data = Vec::new();
|
let mut data = Vec::new();
|
||||||
for i in 0..500_000 {
|
for i in 0..500_000 {
|
||||||
let line = format!("line {} with windows endings\r\n", i);
|
let line = format!("line {i} with windows endings\r\n");
|
||||||
data.extend_from_slice(line.as_bytes());
|
data.extend_from_slice(line.as_bytes());
|
||||||
}
|
}
|
||||||
let file_path = create_test_file(&data, temp_dir.path());
|
let file_path = create_test_file(&data, temp_dir.path());
|
||||||
@@ -240,7 +240,7 @@ fn number_fix(bencher: Bencher) {
|
|||||||
let cents = i % 100;
|
let cents = i % 100;
|
||||||
let thousands = euros / 1000;
|
let thousands = euros / 1000;
|
||||||
let remainder = euros % 1000;
|
let remainder = euros % 1000;
|
||||||
let line = format!("{}.{:03},{:02}\n", thousands, remainder, cents);
|
let line = format!("{thousands}.{remainder:03},{cents:02}\n");
|
||||||
data.extend_from_slice(line.as_bytes());
|
data.extend_from_slice(line.as_bytes());
|
||||||
}
|
}
|
||||||
let file_path = create_test_file(&data, temp_dir.path());
|
let file_path = create_test_file(&data, temp_dir.path());
|
||||||
|
|||||||
Generated
+489
-483
File diff suppressed because it is too large
Load Diff
+4
-4
@@ -12,10 +12,10 @@ console = "0.16.0"
|
|||||||
libfuzzer-sys = "0.4.7"
|
libfuzzer-sys = "0.4.7"
|
||||||
libc = "0.2.153"
|
libc = "0.2.153"
|
||||||
tempfile = "3.15.0"
|
tempfile = "3.15.0"
|
||||||
rand = { version = "0.9.0", features = ["small_rng"] }
|
rand = { version = "0.10.0" }
|
||||||
similar = "2.5.0"
|
similar = "3.0.0"
|
||||||
uucore = { version = "0.5.0", features = ["libc"] }
|
uucore = { version = "0.9.0", features = ["libc"] }
|
||||||
uufuzz = "0.5.0"
|
uufuzz = "0.9.0"
|
||||||
|
|
||||||
sed = { path = ".." }
|
sed = { path = ".." }
|
||||||
|
|
||||||
|
|||||||
+13
-22
@@ -64,6 +64,8 @@ pub struct ProcessingContext {
|
|||||||
pub parsed_block_nesting: usize,
|
pub parsed_block_nesting: usize,
|
||||||
/// Command associated with each label
|
/// Command associated with each label
|
||||||
pub label_to_command_map: HashMap<String, Rc<RefCell<Command>>>,
|
pub label_to_command_map: HashMap<String, Rc<RefCell<Command>>>,
|
||||||
|
/// Commands with a (latchable and resetable) address range
|
||||||
|
pub range_commands: Vec<Rc<RefCell<Command>>>,
|
||||||
/// True if a substitution was made as specified in the t command
|
/// True if a substitution was made as specified in the t command
|
||||||
pub substitution_made: bool,
|
pub substitution_made: bool,
|
||||||
/// Elements to append at the end of each command processing cycle
|
/// Elements to append at the end of each command processing cycle
|
||||||
@@ -84,26 +86,15 @@ pub struct StringSpace {
|
|||||||
pub has_newline: bool, // True if \n-terminated
|
pub has_newline: bool, // True if \n-terminated
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug)]
|
||||||
/// Types of address specifications that precede commands
|
/// Types of address specifications that precede commands
|
||||||
pub enum AddressType {
|
pub enum Address {
|
||||||
Re, // Line that matches regex
|
Re(Option<Regex>), // Line that matches (optional) regex
|
||||||
Line, // Specific line
|
Line(usize), // Specific line
|
||||||
RelLine, // Relative line
|
RelLine(usize), // Relative line
|
||||||
Last, // Last line
|
Last, // Last line
|
||||||
}
|
StepMatch(usize), // Lines matching specified step from first
|
||||||
|
StepEnd(usize), // Range ending at specified step from first
|
||||||
#[derive(Debug)]
|
|
||||||
/// Format of an address
|
|
||||||
pub struct Address {
|
|
||||||
pub atype: AddressType, // Address type
|
|
||||||
pub value: AddressValue, // Line number or regex
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum AddressValue {
|
|
||||||
LineNumber(usize),
|
|
||||||
Regex(Option<Regex>),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -171,12 +162,12 @@ impl ReplacementTemplate {
|
|||||||
ReplacementPart::Literal(s) => result.push_str(s),
|
ReplacementPart::Literal(s) => result.push_str(s),
|
||||||
|
|
||||||
ReplacementPart::WholeMatch => {
|
ReplacementPart::WholeMatch => {
|
||||||
result.push_str(caps.get(0)?.map(|m| m.as_str()).unwrap_or(""));
|
result.push_str(caps.get(0)?.map(|m| m.as_str()).unwrap_or_default());
|
||||||
}
|
}
|
||||||
|
|
||||||
ReplacementPart::Group(n) => {
|
ReplacementPart::Group(n) => {
|
||||||
let i: usize = (*n).try_into().unwrap();
|
let i: usize = (*n).try_into().unwrap();
|
||||||
result.push_str(caps.get(i)?.map(|m| m.as_str()).unwrap_or(""));
|
result.push_str(caps.get(i)?.map(|m| m.as_str()).unwrap_or_default());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -278,7 +269,7 @@ pub struct Command {
|
|||||||
pub addr1: Option<Address>, // Start address
|
pub addr1: Option<Address>, // Start address
|
||||||
pub addr2: Option<Address>, // End address
|
pub addr2: Option<Address>, // End address
|
||||||
pub non_select: bool, // True if '!'
|
pub non_select: bool, // True if '!'
|
||||||
pub start_line: Option<usize>, // Start line number (or None)
|
pub start_line: Option<usize>, // Start line number (or None if unlatched)
|
||||||
pub data: CommandData, // Command-specific data
|
pub data: CommandData, // Command-specific data
|
||||||
pub next: Option<Rc<RefCell<Command>>>, // Pointer to next command
|
pub next: Option<Rc<RefCell<Command>>>, // Pointer to next command
|
||||||
pub location: ScriptLocation, // Command's definition location
|
pub location: ScriptLocation, // Command's definition location
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user