70 Commits
Author SHA1 Message Date
oech3andSylvestre Ledru d23df4c400 Publish binary from main instead of discarding 2026-01-28 08:55:25 +01:00
Diomidis SpinellisandSylvestre Ledru 8039955354 Add support for GNU ~step address ranges 2026-01-28 08:40:54 +01:00
Diomidis SpinellisandSylvestre Ledru 8a2934e519 Refactor struct + enum pair into a tagged enum
This removes invalid states and simplifies pattern matching.
It also makes it easier to add further address extensions.
2026-01-28 08:40:54 +01:00
Diomidis SpinellisandSylvestre Ledru a0d82d678d Support ranges starting from line 0
This is a GNU extension that starts the range as latched.
While at it, also fix the handling of ranges straddling independent
files.
2026-01-28 08:40:54 +01:00
Diomidis SpinellisandSylvestre Ledru 31de5d5ea0 Improve interactive output
Before this commit, the output of sed was lagging one line behind, as it
was always checking whether each line was the last one ($).  This change
modifies the behavior to make this check only when actually needed.
This brings sed's interactive behavior (when reading from a terminal) in
line with the GNU and FreeBSD implementations.
2026-01-28 08:40:54 +01:00
Daniel HofstetterandGitHub 78d3b3acf7 Merge pull request #273 from uutils/renovate/clap-4.x-lockfile
chore(deps): update rust crate clap to v4.5.55
2026-01-28 07:04:43 +01:00
renovate[bot]andGitHub d9a1cdb9a2 chore(deps): update rust crate clap to v4.5.55 2026-01-27 21:29:27 +00:00
Daniel HofstetterandGitHub 1ccd8d069a Merge pull request #272 from uutils/renovate/sysinfo-0.x
chore(deps): update rust crate sysinfo to 0.38
2026-01-25 10:55:28 +01:00
renovate[bot]andGitHub 4fb1046c54 chore(deps): update rust crate sysinfo to 0.38 2026-01-24 17:35:06 +00:00
Sylvestre LedruandGitHub b0e7940484 Merge pull request #271 from oech3/patch-1
Don't apt-get runner preinstalled utils
2026-01-24 13:27:18 +01:00
oech3andGitHub 89b7a039c9 Don't apt-get runner preinstalled utils 2026-01-24 21:18:16 +09:00
Daniel HofstetterandGitHub 398ee76b79 Merge pull request #269 from uutils/renovate/divan-4.x-lockfile
chore(deps): update rust crate divan to v4.3.0
2026-01-22 07:02:10 +01:00
renovate[bot]andGitHub ea5a3e45df chore(deps): update rust crate divan to v4.3.0 2026-01-21 17:51:43 +00:00
Sylvestre LedruandGitHub c5e6348825 Merge pull request #268 from e-kwsm/run-gnu-testsuite.sh
fix spurious failure in run-gnu-testsuite.sh
2026-01-21 13:36:21 +01:00
Eisuke Kawashima fc39ff7329 fix(run-gnu-testsuite.sh): unquote sed_script 2026-01-19 15:48:31 +09:00
Eisuke Kawashima f6d26ff00f fix(run-gnu-testsuite.sh): correct input and expected output strings
use ANSI-C strings to interpret `\n`
2026-01-19 14:55:24 +09:00
Daniel HofstetterandGitHub c6479b44ad Merge pull request #264 from uutils/renovate/chrono-0.x-lockfile
chore(deps): update rust crate chrono to v0.4.43
2026-01-15 07:10:56 +01:00
renovate[bot]andGitHub 8e3d3985e1 chore(deps): update rust crate chrono to v0.4.43 2026-01-14 22:41:30 +00:00
Sylvestre LedruandGitHub 1710d9cc56 Merge pull request #255 from sylvestre/run-gnu
run testsuite: make sure that everything is run
2026-01-11 22:36:37 +01:00
Sylvestre Ledru b26f59ab2f run testsuite: make sure that everything is run 2026-01-11 22:34:33 +01:00
Sylvestre Ledru 5d452d4174 link to the sed tracking project 2026-01-11 21:12:00 +01:00
Sylvestre Ledru ff0c1cdd06 remove artifact 2026-01-11 17:30:22 +01:00
Sylvestre LedruandGitHub 4e024a9576 Merge pull request #247 from sylvestre/gnu-ci-info
Publish comments in case of changes with the GNU test suite
2026-01-11 17:29:41 +01:00
Sylvestre Ledru d89243bdb9 Improve comparison output to show both current and reference numbers 2026-01-11 17:12:41 +01:00
Sylvestre Ledru 270f3fdce8 Add comprehensive unit tests for comparison script 2026-01-11 16:50:14 +01:00
Sylvestre Ledru 69ddd044c5 Integrate test result comparison in CI workflow 2026-01-11 16:24:17 +01:00
Sylvestre Ledru 5ff4a7c0c2 Add ignore list for intermittent test failures 2026-01-11 16:24:17 +01:00
Sylvestre Ledru ce3e4b33b5 Add automatic PR comment workflow for test comparisons 2026-01-11 16:24:17 +01:00
Sylvestre Ledru 35ea457759 Add test result comparison script 2026-01-11 16:24:17 +01:00
Sylvestre Ledru 35b6e84c16 Add unit test for issue #143 (newline behavior with -e p command) 2026-01-11 15:11:56 +01:00
vejuandSylvestre Ledru faea122f87 fixes newline bug 2026-01-11 14:54:50 +01:00
Daniel HofstetterandGitHub db58849440 Merge pull request #244 from uutils/renovate/libc-0.x-lockfile
chore(deps): update rust crate libc to v0.2.180
2026-01-08 14:37:25 +01:00
renovate[bot]andGitHub 94bd9b4f14 chore(deps): update rust crate libc to v0.2.180 2026-01-08 13:15:20 +00:00
Daniel HofstetterandGitHub d0ad8eb95d Merge pull request #246 from uutils/renovate/clap_complete-4.x-lockfile
chore(deps): update rust crate clap_complete to v4.5.65
2026-01-08 07:12:04 +01:00
renovate[bot]andGitHub df06ef663d chore(deps): update rust crate clap_complete to v4.5.65 2026-01-07 18:01:54 +00:00
Daniel HofstetterandGitHub e82023054e Merge pull request #245 from uutils/renovate/divan-4.x-lockfile
chore(deps): update rust crate divan to v4.2.1
2026-01-07 14:12:19 +01:00
renovate[bot]andGitHub 9b7b509268 chore(deps): update rust crate divan to v4.2.1 2026-01-07 12:57:56 +00:00
Daniel HofstetterandGitHub 70ce8fe410 Merge pull request #243 from uutils/renovate/clap-4.x-lockfile
chore(deps): update rust crate clap to v4.5.54
2026-01-03 07:17:31 +01:00
renovate[bot]andGitHub e16a49bb93 chore(deps): update rust crate clap to v4.5.54 2026-01-03 00:43:09 +00:00
Daniel HofstetterandGitHub a8887f83dd Merge pull request #242 from uutils/renovate/clap_complete-4.x-lockfile
chore(deps): update rust crate clap_complete to v4.5.64
2025-12-31 07:02:52 +01:00
renovate[bot]andGitHub 9667ebe8c3 chore(deps): update rust crate clap_complete to v4.5.64 2025-12-30 17:32:56 +00:00
Sylvestre LedruandGitHub 5ed30bd344 Merge pull request #226 from oech3/patch-1
Cargo.toml: Remove non standard profile
2025-12-30 11:58:51 +01:00
Sylvestre LedruandGitHub 1996ac2ae3 Merge pull request #241 from dspinellis/integration-tests
Add two complex scripts as integration tests
2025-12-30 11:58:28 +01:00
Diomidis Spinellis e40ddb1c99 Add Mandelbrot set drawing 2025-12-30 11:40:09 +02:00
Diomidis Spinellis 8c840078c5 Add Bach prelude as an integration test
Thanks to: Olga Ustiuzhanina @laserbat for providing a suitable license.
2025-12-30 11:40:09 +02:00
Diomidis Spinellis 4029ae5aba Incorporate variables in format messages
This addresses clippy warnings.
2025-12-30 11:40:09 +02:00
Daniel HofstetterandGitHub 3479e9b580 Merge pull request #240 from uutils/renovate/clap_complete-4.x-lockfile
chore(deps): update rust crate clap_complete to v4.5.63
2025-12-30 07:16:23 +01:00
renovate[bot]andGitHub 0109b369a6 chore(deps): update rust crate clap_complete to v4.5.63 2025-12-29 18:06:46 +00:00
oech3andSylvestre Ledru 77e1abd18e Cargo.toml: Remove non standard profile 2025-12-29 13:02:06 +01:00
Daniel HofstetterandGitHub 17575017d8 Merge pull request #239 from uutils/renovate/dawidd6-action-download-artifact-12.x
chore(deps): update dawidd6/action-download-artifact action to v12
2025-12-25 14:30:49 +01:00
renovate[bot]andGitHub 902611caf1 chore(deps): update dawidd6/action-download-artifact action to v12 2025-12-25 13:23:56 +00:00
Daniel HofstetterandGitHub b5a8444f88 Merge pull request #238 from uutils/renovate/tempfile-3.x-lockfile
chore(deps): update rust crate tempfile to v3.24.0
2025-12-24 07:17:40 +01:00
renovate[bot]andGitHub 200bc978b4 chore(deps): update rust crate tempfile to v3.24.0 2025-12-24 00:25:44 +00:00
Daniel HofstetterandGitHub d58547195b Merge pull request #237 from uutils/renovate/divan-4.x-lockfile
chore(deps): update rust crate divan to v4.2.0
2025-12-23 07:23:16 +01:00
renovate[bot]andGitHub ef7f8ad361 chore(deps): update rust crate divan to v4.2.0 2025-12-22 22:14:06 +00:00
Daniel HofstetterandGitHub 19f02d5683 Merge pull request #233 from uutils/renovate/major-github-artifact-actions
chore(deps): update github artifact actions (major)
2025-12-22 07:13:27 +01:00
renovate[bot]andGitHub d253a5808a chore(deps): update github artifact actions 2025-12-21 17:50:14 +00:00
Daniel HofstetterandGitHub bf47d12e37 Merge pull request #230 from uutils/renovate/actions-checkout-6.x
chore(deps): update actions/checkout action to v6
2025-12-21 13:21:44 +01:00
Daniel HofstetterandGitHub 0ea19a6fd5 Merge pull request #231 from uutils/renovate/dawidd6-action-download-artifact-11.x
chore(deps): update dawidd6/action-download-artifact action to v11
2025-12-21 13:20:43 +01:00
renovate[bot]andGitHub 55177aeca4 chore(deps): update dawidd6/action-download-artifact action to v11 2025-12-21 12:06:08 +00:00
renovate[bot]andGitHub c8b9173752 chore(deps): update actions/checkout action to v6 2025-12-21 12:06:05 +00:00
Sylvestre LedruandGitHub 3fa2227ebf Run against the GNU implementation (#223)
* Add a script to run the sed testsuite

* run the gnu testsuite in the ci

* remove useless comment
2025-12-21 13:05:37 +01:00
Sylvestre LedruandGitHub 2df1e00d8e Merge pull request #225 from cakebaker/remove_once_cell
Replace `Lazy` from `once_cell` with `LazyLock` from std
2025-12-18 17:14:06 +01:00
Sylvestre LedruandGitHub 61c405c549 Merge pull request #227 from cakebaker/test_use_tempfolder
test: write file to temp folder
2025-12-18 17:00:37 +01:00
Daniel HofstetterandGitHub 3336f93514 Merge pull request #228 from uutils/renovate/clap_complete-4.x-lockfile
chore(deps): update rust crate clap_complete to v4.5.62
2025-12-18 16:04:56 +01:00
renovate[bot]andGitHub 569f3557e5 chore(deps): update rust crate clap_complete to v4.5.62 2025-12-18 14:43:10 +00:00
Daniel Hofstetter 9ae154a26d test: write file to temp folder 2025-12-18 14:50:07 +01:00
Daniel Hofstetter 2ae5b782c7 Replace Lazy from once_cell with LazyLock from std 2025-12-18 10:18:29 +01:00
Sylvestre LedruandGitHub eddee66b6c Merge pull request #224 from cakebaker/move_lints_to_cargo_toml
Move lints from CI to `Cargo.toml`
2025-12-18 09:54:10 +01:00
Daniel Hofstetter b3ee5bfa9d Move lints from CI to Cargo.toml 2025-12-18 09:40:50 +01:00
30 changed files with 2384 additions and 420 deletions
+80
View File
@@ -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@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');
}
+284
View File
@@ -0,0 +1,284 @@
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@v12
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
+1 -2
View File
@@ -69,9 +69,8 @@ jobs:
run: |
## `cargo clippy` lint testing
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_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>
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
@@ -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
Generated
+128 -60
View File
@@ -56,7 +56,7 @@ version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
dependencies = [
"windows-sys 0.61.2",
"windows-sys 0.60.2",
]
[[package]]
@@ -67,7 +67,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
dependencies = [
"anstyle",
"once_cell_polyfill",
"windows-sys 0.61.2",
"windows-sys 0.60.2",
]
[[package]]
@@ -127,6 +127,15 @@ version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]]
name = "bstr"
version = "1.12.1"
@@ -167,9 +176,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "chrono"
version = "0.4.42"
version = "0.4.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2"
checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118"
dependencies = [
"iana-time-zone",
"num-traits",
@@ -178,18 +187,18 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.53"
version = "4.5.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8"
checksum = "3e34525d5bbbd55da2bb745d34b36121baac88d07619a9a09cfcf4a6c0832785"
dependencies = [
"clap_builder",
]
[[package]]
name = "clap_builder"
version = "4.5.53"
version = "4.5.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00"
checksum = "59a20016a20a3da95bef50ec7238dbd09baeef4311dcdd38ec15aba69812fb61"
dependencies = [
"anstream",
"anstyle",
@@ -200,9 +209,9 @@ dependencies = [
[[package]]
name = "clap_complete"
version = "4.5.61"
version = "4.5.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39615915e2ece2550c0149addac32fb5bd312c657f43845bb9088cb9c8a7c992"
checksum = "430b4dc2b5e3861848de79627b2bedc9f3342c7da5173a14eaa5d0f8dc18ae5d"
dependencies = [
"clap",
]
@@ -225,9 +234,9 @@ dependencies = [
[[package]]
name = "codspeed"
version = "4.1.0"
version = "4.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3b847e05a34be5c38f3f2a5052178a3bd32e6b5702f3ea775efde95c483a539"
checksum = "38c2eb3388ebe26b5a0ab6bf4969d9c4840143d7f6df07caa3cc851b0606cef6"
dependencies = [
"anyhow",
"cc",
@@ -243,9 +252,9 @@ dependencies = [
[[package]]
name = "codspeed-divan-compat"
version = "4.1.0"
version = "4.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0f0e9fe5eaa39995ec35e46407f7154346cc25bd1300c64c21636f3d00cb2cc"
checksum = "b2de65b7489a59709724d489070c6d05b7744039e4bf751d0a2006b90bb5593d"
dependencies = [
"clap",
"codspeed",
@@ -256,9 +265,9 @@ dependencies = [
[[package]]
name = "codspeed-divan-compat-macros"
version = "4.1.0"
version = "4.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88c8babf2a40fd2206a2e030cf020d0d58144cd56e1dc408bfba02cdefb08b4f"
checksum = "56ca01ce4fd22b8dcc6c770dcd6b74343642e842482b94e8920d14e10c57638d"
dependencies = [
"divan-macros",
"itertools",
@@ -270,9 +279,9 @@ dependencies = [
[[package]]
name = "codspeed-divan-compat-walltime"
version = "4.1.0"
version = "4.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f26092328e12a36704ffc552f379c6405dd94d3149970b79b22d371717c2aae"
checksum = "720ab9d0714718afe5f5832be6e5f5eb5ce97836e24ca7bf7042eea4308b9fb8"
dependencies = [
"cfg-if",
"clap",
@@ -311,6 +320,15 @@ version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "cpufeatures"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
dependencies = [
"libc",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.6"
@@ -336,6 +354,16 @@ version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "crypto-common"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "ctor"
version = "0.6.3"
@@ -373,6 +401,16 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
]
[[package]]
name = "displaydoc"
version = "0.2.5"
@@ -447,7 +485,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys 0.61.2",
"windows-sys 0.59.0",
]
[[package]]
@@ -527,6 +565,16 @@ dependencies = [
"thiserror",
]
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "getrandom"
version = "0.2.16"
@@ -586,6 +634,12 @@ version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "iana-time-zone"
version = "0.1.64"
@@ -598,7 +652,7 @@ dependencies = [
"js-sys",
"log",
"wasm-bindgen",
"windows-core 0.62.2",
"windows-core 0.61.2",
]
[[package]]
@@ -688,7 +742,7 @@ dependencies = [
"portable-atomic",
"portable-atomic-util",
"serde_core",
"windows-sys 0.61.2",
"windows-sys 0.59.0",
]
[[package]]
@@ -735,9 +789,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.178"
version = "0.2.180"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
[[package]]
name = "linux-raw-sys"
@@ -1086,15 +1140,15 @@ checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
[[package]]
name = "rustix"
version = "1.1.2"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e"
checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.61.2",
"windows-sys 0.59.0",
]
[[package]]
@@ -1130,10 +1184,10 @@ dependencies = [
"codspeed-divan-compat",
"ctor",
"fancy-regex",
"hex",
"libc",
"memchr",
"memmap2",
"once_cell",
"phf",
"phf_codegen",
"predicates",
@@ -1141,6 +1195,7 @@ dependencies = [
"rand",
"regex",
"rlimit",
"sha2",
"sysinfo",
"tempfile",
"terminal_size",
@@ -1199,6 +1254,17 @@ dependencies = [
"serde_core",
]
[[package]]
name = "sha2"
version = "0.10.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]]
name = "shlex"
version = "1.3.0"
@@ -1262,9 +1328,9 @@ dependencies = [
[[package]]
name = "sysinfo"
version = "0.37.2"
version = "0.38.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16607d5caffd1c07ce073528f9ed972d88db15dd44023fa57142963be3feb11f"
checksum = "fe840c5b1afe259a5657392a4dbb74473a14c8db999c3ec2f4ae812e028a94da"
dependencies = [
"libc",
"memchr",
@@ -1276,15 +1342,15 @@ dependencies = [
[[package]]
name = "tempfile"
version = "3.23.0"
version = "3.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16"
checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c"
dependencies = [
"fastrand",
"getrandom 0.3.4",
"once_cell",
"rustix",
"windows-sys 0.61.2",
"windows-sys 0.59.0",
]
[[package]]
@@ -1418,6 +1484,12 @@ dependencies = [
"rustc-hash",
]
[[package]]
name = "typenum"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
[[package]]
name = "unic-langid"
version = "0.9.6"
@@ -1513,6 +1585,12 @@ dependencies = [
"xattr",
]
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "walkdir"
version = "2.5.0"
@@ -1614,7 +1692,7 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys 0.61.2",
"windows-sys 0.59.0",
]
[[package]]
@@ -1625,24 +1703,23 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows"
version = "0.61.3"
version = "0.62.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893"
checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580"
dependencies = [
"windows-collections",
"windows-core 0.61.2",
"windows-core 0.62.2",
"windows-future",
"windows-link 0.1.3",
"windows-numerics",
]
[[package]]
name = "windows-collections"
version = "0.2.0"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8"
checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610"
dependencies = [
"windows-core 0.61.2",
"windows-core 0.62.2",
]
[[package]]
@@ -1673,12 +1750,12 @@ dependencies = [
[[package]]
name = "windows-future"
version = "0.2.1"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e"
checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb"
dependencies = [
"windows-core 0.61.2",
"windows-link 0.1.3",
"windows-core 0.62.2",
"windows-link 0.2.1",
"windows-threading",
]
@@ -1718,12 +1795,12 @@ checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-numerics"
version = "0.2.0"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1"
checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26"
dependencies = [
"windows-core 0.61.2",
"windows-link 0.1.3",
"windows-core 0.62.2",
"windows-link 0.2.1",
]
[[package]]
@@ -1780,15 +1857,6 @@ dependencies = [
"windows-targets 0.53.5",
]
[[package]]
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link 0.2.1",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
@@ -1824,11 +1892,11 @@ dependencies = [
[[package]]
name = "windows-threading"
version = "0.1.0"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6"
checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37"
dependencies = [
"windows-link 0.1.3",
"windows-link 0.2.1",
]
[[package]]
+15 -8
View File
@@ -38,16 +38,17 @@ clap_complete = "4.5"
clap_mangen = "0.2"
divan = { package = "codspeed-divan-compat", version = "4.0.5" }
fancy-regex = "0.17.0"
hex = "0.4"
libc = "0.2.153"
memchr = "2.7.4"
memmap2 = "0.9"
once_cell = "1.21.3"
phf = "0.13.0"
phf_codegen = "0.13.0"
predicates = "3.1.3"
rand = { version = "0.9", features = ["small_rng"] }
regex = "1.10.4"
sysinfo = "0.37"
sha2 = "0.10"
sysinfo = "0.38"
tempfile = "3.10.1"
textwrap = { version = "0.16.1", features = ["terminal_size"] }
terminal_size = "0.4.2"
@@ -64,7 +65,6 @@ ctor = "0.6.0"
fancy-regex = { workspace = true }
memchr = { workspace = true }
memmap2.workspace = true
once_cell = { workspace = true }
phf = { workspace = true }
predicates = { workspace = true }
regex = { workspace = true }
@@ -77,10 +77,12 @@ uucore = { workspace = true }
[dev-dependencies]
chrono = { workspace = true }
divan = { workspace = true }
hex = { workspace = true }
libc = { workspace = true }
pretty_assertions = "1"
rand = { workspace = true }
regex = { workspace = true }
sha2 = { workspace = true }
tempfile = { workspace = true }
uucore = { workspace = true, features = ["entries", "process", "signals", "benchmark"] }
uutests = "0.5.0"
@@ -116,7 +118,7 @@ lto = true
[profile.release-fast]
inherits = "release"
panic = "abort"
codegen-units = 1
codegen-units = 1 # should be moved to release without regression
# A release-like profile that is as small as possible.
[profile.release-small]
@@ -124,7 +126,12 @@ inherits = "release-fast"
opt-level = "z"
strip = true
# The profile that 'cargo dist' will build with
[profile.dist]
inherits = "release"
lto = "thin"
[lints.clippy]
default_trait_access = "warn"
manual_string_new = "warn"
cognitive_complexity = "warn"
implicit_clone = "warn"
range-plus-one = "warn"
redundant-clone = "warn"
match_bool = "warn"
semicolon_if_nothing_returned = "warn"
+36
View File
@@ -41,6 +41,31 @@ cargo run --release
The binary is named `sed` in `target/release/sed`.
## Testing
### GNU sed Compatibility Testing
Test compatibility against GNU sed using the comprehensive testsuite (47+ tests, ~10% pass rate):
```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
# Generate JSON results for CI
./util/run-gnu-testsuite.sh --json-output results.json
```
The testsuite extracts test cases from the GNU sed repository and tests them against expected outputs.
### Unit Tests
```bash
cargo test
```
## Extensions and incompatibilities
### Supported GNU extensions
* Command-line arguments can be specified in long (`--`) form.
@@ -58,6 +83,10 @@ The binary is named `sed` in `target/release/sed`.
* The `q` command can be optionally followed by an exit code.
* The `l` command can be optionally followed by the output width.
* 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
* The second address in a range can be specified as a relative address with +N.
@@ -87,6 +116,13 @@ The binary is named `sed` in `target/release/sed`.
* Labels are parsed for alphanumeric characters. The BSD version parses them
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.
![Evolution over time](https://github.com/uutils/sed-tracking/blob/main/gnu-results.svg?raw=true)
## License
sed is licensed under the MIT License - see the `LICENSE` file for details
+3 -3
View File
@@ -17,7 +17,7 @@ fn no_op_short(bencher: Bencher) {
let temp_dir = tempfile::tempdir().unwrap();
let mut data = Vec::new();
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_str = file_path.to_str().unwrap();
@@ -192,7 +192,7 @@ fn remove_cr(bencher: Bencher) {
let temp_dir = tempfile::tempdir().unwrap();
let mut data = Vec::new();
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());
}
let file_path = create_test_file(&data, temp_dir.path());
@@ -240,7 +240,7 @@ fn number_fix(bencher: Bencher) {
let cents = i % 100;
let thousands = 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());
}
let file_path = create_test_file(&data, temp_dir.path());
+11 -12
View File
@@ -579,9 +579,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.178"
version = "0.2.180"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
[[package]]
name = "libfuzzer-sys"
@@ -601,9 +601,9 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
[[package]]
name = "linux-raw-sys"
version = "0.9.4"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
[[package]]
name = "log"
@@ -923,15 +923,15 @@ checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
[[package]]
name = "rustix"
version = "1.0.7"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266"
checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.59.0",
"windows-sys 0.61.0",
]
[[package]]
@@ -951,7 +951,7 @@ dependencies = [
[[package]]
name = "sed"
version = "0.0.1"
version = "0.1.1"
dependencies = [
"assert_fs",
"clap",
@@ -961,7 +961,6 @@ dependencies = [
"fancy-regex",
"memchr",
"memmap2",
"once_cell",
"phf",
"phf_codegen",
"predicates",
@@ -1068,15 +1067,15 @@ dependencies = [
[[package]]
name = "tempfile"
version = "3.23.0"
version = "3.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16"
checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c"
dependencies = [
"fastrand",
"getrandom",
"once_cell",
"rustix",
"windows-sys 0.59.0",
"windows-sys 0.61.0",
]
[[package]]
+11 -20
View File
@@ -64,6 +64,8 @@ pub struct ProcessingContext {
pub parsed_block_nesting: usize,
/// Command associated with each label
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
pub substitution_made: bool,
/// 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
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Debug)]
/// Types of address specifications that precede commands
pub enum AddressType {
Re, // Line that matches regex
Line, // Specific line
RelLine, // Relative line
Last, // Last line
}
#[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>),
pub enum Address {
Re(Option<Regex>), // Line that matches (optional) regex
Line(usize), // Specific line
RelLine(usize), // Relative line
Last, // Last line
StepMatch(usize), // Lines matching specified step from first
StepEnd(usize), // Range ending at specified step from first
}
#[derive(Debug)]
@@ -278,7 +269,7 @@ pub struct Command {
pub addr1: Option<Address>, // Start address
pub addr2: Option<Address>, // End address
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 next: Option<Rc<RefCell<Command>>>, // Pointer to next command
pub location: ScriptLocation, // Command's definition location
+297 -161
View File
File diff suppressed because it is too large Load Diff
+87 -88
View File
@@ -61,7 +61,6 @@ pub struct MmapLineCursor<'a> {
pub struct NextMmapLine<'a> {
pub content: &'a [u8],
pub full_span: &'a [u8],
pub is_last_line: bool,
}
#[cfg(unix)]
@@ -76,7 +75,7 @@ impl<'a> MmapLineCursor<'a> {
}
/// Return the next line, if available, or None.
fn get_line(&mut self) -> io::Result<Option<NextMmapLine<'_>>> {
fn get_line(&mut self) -> io::Result<Option<NextMmapLine<'a>>> {
if self.pos >= self.data.len() {
return Ok(None);
}
@@ -101,12 +100,12 @@ impl<'a> MmapLineCursor<'a> {
full_span
};
let is_last_line = self.pos >= self.data.len();
Ok(Some(NextMmapLine {
content,
full_span,
is_last_line,
}))
Ok(Some(NextMmapLine { content, full_span }))
}
/// Return true if the previously returned line was the last one.
fn last_line(&mut self) -> io::Result<bool> {
Ok(self.pos >= self.data.len())
}
}
@@ -126,9 +125,8 @@ impl ReadLineCursor {
}
}
/// If a line is available, return it, its \n termination,
/// and next line availability, otherwise return None.
fn get_line(&mut self) -> io::Result<Option<(String, bool, bool)>> {
/// If a line is available, return it and its \n termination.
fn get_line(&mut self) -> io::Result<Option<(String, bool)>> {
self.buffer.clear();
// read_line *includes* the '\n' if present
let bytes_read = self.reader.read_line(&mut self.buffer)?;
@@ -142,8 +140,14 @@ impl ReadLineCursor {
self.buffer.pop();
}
let line = std::mem::take(&mut self.buffer);
let is_last_line = self.reader.fill_buf()?.is_empty();
Ok(Some((line, has_newline, is_last_line)))
Ok(Some((line, has_newline)))
}
/// Return true if the previously returned line was the last one.
fn last_line(&mut self) -> io::Result<bool> {
// FIXME(rust-lang#86423): Replace with BufRead::has_data_left()
// when/if method becomes stable.
Ok(self.reader.fill_buf()?.is_empty())
}
}
@@ -466,21 +470,15 @@ impl<'a> LineReader<'a> {
line_reader_read_input(file)
}
/// Return the next line, if available and also the availability
/// of another one, or None at end of file.
pub fn get_line(&mut self) -> io::Result<Option<(IOChunk<'_>, bool)>> {
/// Return the next line, if available.
pub fn get_line(&mut self) -> io::Result<Option<IOChunk<'a>>> {
match self {
#[cfg(unix)]
LineReader::MmapInput { cursor, .. } => {
// Obtain fields to prevent borrowing issues.
let fast_copy = cursor.fast_copy.clone();
let base = cursor.data.as_ptr();
if let Some(NextMmapLine {
content,
full_span,
is_last_line,
}) = cursor.get_line()?
{
if let Some(NextMmapLine { content, full_span }) = cursor.get_line()? {
let chunk = IOChunk::from_content(IOChunkContent::MmapInput {
fast_copy,
base,
@@ -488,17 +486,17 @@ impl<'a> LineReader<'a> {
full_span,
});
Ok(Some((chunk, is_last_line)))
Ok(Some(chunk))
} else {
Ok(None)
}
}
LineReader::ReadInput(cursor) => {
if let Some((line, _has_newline, is_last_line)) = cursor.get_line()? {
if let Some((line, _has_newline)) = cursor.get_line()? {
let chunk =
IOChunk::from_content(IOChunkContent::new_owned(line, _has_newline));
Ok(Some((chunk, is_last_line)))
Ok(Some(chunk))
} else {
Ok(None)
}
@@ -508,6 +506,19 @@ impl<'a> LineReader<'a> {
LineReader::_Phantom(_) => unreachable!("_Phantom should never be constructed"),
}
}
/// Return true if the previously returned line was the last one.
pub fn last_line(&mut self) -> io::Result<bool> {
match self {
#[cfg(unix)]
LineReader::MmapInput { cursor, .. } => cursor.last_line(),
LineReader::ReadInput(cursor) => cursor.last_line(),
#[cfg(not(unix))]
LineReader::_Phantom(_) => unreachable!("_Phantom should never be constructed"),
}
}
}
// Define a trait combining two: workaround for Rust's corresponding inability.
@@ -1048,7 +1059,7 @@ mod tests {
let mut out = OutputBuffer::new(Box::new(Box::new(out_file)));
// Drain reader → writer
while let Some((chunk, _last_line)) = reader.get_line()? {
while let Some(chunk) = reader.get_line()? {
out.write_chunk(&chunk)?;
}
out.flush()?;
@@ -1084,7 +1095,7 @@ mod tests {
let mut out = OutputBuffer::new(Box::new(out_file));
// Read the first mmap line ("zero\n") and write it
if let Some((chunk, _last_line)) = reader.get_line()? {
if let Some(chunk) = reader.get_line()? {
out.write_chunk(&chunk)?;
}
@@ -1092,7 +1103,7 @@ mod tests {
out.write_str("middle\n")?;
// Read the second mmap line ("one\n") and write it
if let Some((chunk, _last_line)) = reader.get_line()? {
if let Some(chunk) = reader.get_line()? {
out.write_chunk(&chunk)?;
}
@@ -1137,7 +1148,7 @@ mod tests {
// Wrap it in your OutputBuffer and run the loop:
let mut out = OutputBuffer::new(Box::new(out_file));
let mut nline = 0;
while let Some((chunk, _last_line)) = reader.get_line()? {
while let Some(chunk) = reader.get_line()? {
out.write_chunk(&chunk)?;
nline += 1;
}
@@ -1176,7 +1187,7 @@ mod tests {
// Wrap it in your OutputBuffer and run the loop:
let mut out = OutputBuffer::new(Box::new(out_file));
let mut nline = 0;
while let Some((chunk, _last_line)) = reader.get_line()? {
while let Some(chunk) = reader.get_line()? {
out.write_chunk(&chunk)?;
nline += 1;
}
@@ -1211,7 +1222,7 @@ mod tests {
// Wrap it in your OutputBuffer and run the loop:
let mut out = OutputBuffer::new(Box::new(out_file));
let mut nline = 0;
while let Some((chunk, _last_line)) = reader.get_line()? {
while let Some(chunk) = reader.get_line()? {
out.write_chunk(&chunk)?;
nline += 1;
}
@@ -1246,7 +1257,7 @@ mod tests {
// Wrap it in your OutputBuffer and run the loop:
let mut out = OutputBuffer::new(Box::new(out_file));
let mut nline = 0;
while let Some((chunk, _last_line)) = reader.get_line()? {
while let Some(chunk) = reader.get_line()? {
out.write_chunk(&chunk)?;
nline += 1;
}
@@ -1288,7 +1299,7 @@ mod tests {
// Wrap it in OutputBuffer and run the loop:
let mut out = OutputBuffer::new(Box::new(out_file));
let mut nline_written = 0;
while let Some((chunk, _last_line)) = reader.get_line()? {
while let Some(chunk) = reader.get_line()? {
out.write_chunk(&chunk)?;
nline_written += 1;
}
@@ -1319,52 +1330,46 @@ mod tests {
let mut reader = LineReader::open_stream(&path)?;
// Verify the reader's operation
if let Some((
IOChunk {
content:
IOChunkContent::Owned {
content,
has_newline,
..
},
utf8_verified,
..
},
last_line,
)) = reader.get_line()?
if let Some(IOChunk {
content:
IOChunkContent::Owned {
content,
has_newline,
..
},
utf8_verified,
..
}) = reader.get_line()?
{
assert_eq!(content, "first line");
assert_eq!(content.len(), 10);
assert!(has_newline);
assert!(!utf8_verified.get());
assert!(!last_line);
assert!(!reader.last_line().unwrap());
} else {
panic!("Expected IOChunkContent::Owned");
}
if let Some((
IOChunk {
content:
IOChunkContent::Owned {
content,
has_newline,
..
},
..
},
last_line,
)) = reader.get_line()?
if let Some(IOChunk {
content:
IOChunkContent::Owned {
content,
has_newline,
..
},
..
}) = reader.get_line()?
{
assert_eq!(content, "second line");
assert!(has_newline);
assert!(!last_line);
assert!(!reader.last_line().unwrap());
} else {
panic!("Expected IOChunkContent::Owned");
}
if let Some((content, last_line)) = reader.get_line()? {
if let Some(content) = reader.get_line()? {
assert_eq!(content.as_str().unwrap(), "last line");
assert!(last_line);
assert!(reader.last_line().unwrap());
} else {
panic!("Expected IOChunk");
}
@@ -1386,52 +1391,46 @@ mod tests {
let mut reader = LineReader::open(&path)?;
// Verify the reader's operation
if let Some((
IOChunk {
content:
IOChunkContent::MmapInput {
content, full_span, ..
},
utf8_verified,
..
},
last_line,
)) = reader.get_line()?
if let Some(IOChunk {
content:
IOChunkContent::MmapInput {
content, full_span, ..
},
utf8_verified,
..
}) = reader.get_line()?
{
assert_eq!(content, b"first line");
assert_eq!(content.len(), 10);
assert_eq!(full_span, b"first line\n");
assert!(!utf8_verified.get());
assert!(!last_line);
assert!(!reader.last_line().unwrap());
} else {
panic!("Expected IOChunkContent::MapInput");
}
if let Some((
IOChunk {
content:
IOChunkContent::MmapInput {
content, full_span, ..
},
utf8_verified,
..
},
last_line,
)) = reader.get_line()?
if let Some(IOChunk {
content:
IOChunkContent::MmapInput {
content, full_span, ..
},
utf8_verified,
..
}) = reader.get_line()?
{
assert_eq!(content, b"second line");
assert_eq!(full_span, b"second line\n");
assert!(!utf8_verified.get());
assert!(!last_line);
assert!(!reader.last_line().unwrap());
} else {
panic!("Expected IOChunkContent::MapInput");
}
if let Some((content, last_line)) = reader.get_line()? {
if let Some(content) = reader.get_line()? {
assert_eq!(content.as_bytes(), b"last line");
assert_eq!(content.as_str().unwrap(), "last line");
assert!(content.utf8_verified.get());
assert!(last_line);
assert!(reader.last_line().unwrap());
// Cached version
assert_eq!(content.as_str().unwrap(), "last line");
} else {
+4 -3
View File
@@ -15,12 +15,12 @@ use fancy_regex::{
CaptureMatches as FancyCaptureMatches, Captures as FancyCaptures, Regex as FancyRegex,
};
use memchr::memmem;
use once_cell::sync::Lazy;
use regex::Regex as RustRegex;
use regex::bytes::{
CaptureMatches as ByteCaptureMatches, Captures as ByteCaptures, Regex as ByteRegex,
};
use std::error::Error;
use std::sync::LazyLock;
use uucore::error::{UResult, USimpleError};
use crate::sed::fast_io::IOChunk;
@@ -32,7 +32,8 @@ use crate::sed::fast_io::IOChunk;
// For example, r"\\1" and r"[\1]" will match, whereas only a number
// after an odd number of backslashes and outside a character class
// should match.
static NEEDS_FANCY_RE: Lazy<RustRegex> = Lazy::new(|| regex::Regex::new(r"\\[1-9]").unwrap());
static NEEDS_FANCY_RE: LazyLock<RustRegex> =
LazyLock::new(|| regex::Regex::new(r"\\[1-9]").unwrap());
/// All characters signifying that the match must be handled by an RE
/// rather than by plain string pattern matching.
@@ -41,7 +42,7 @@ static NEEDS_FANCY_RE: Lazy<RustRegex> = Lazy::new(|| regex::Regex::new(r"\\[1-9
// matching, because Regex always constructs an automaton and needs
// to handle state transitions, whereas plain string matching can
// use tailored CPU string or vectored instructions.
static NEEDS_RE: Lazy<RustRegex> = Lazy::new(|| {
static NEEDS_RE: LazyLock<RustRegex> = LazyLock::new(|| {
regex::Regex::new(
r"(?x) # Turn on verbose mode
( ^ # Non-escaped: i.e. at BOL
+1
View File
@@ -216,6 +216,7 @@ fn build_context(matches: &ArgMatches) -> ProcessingContext {
hold: StringSpace::default(),
parsed_block_nesting: 0,
label_to_command_map: HashMap::new(),
range_commands: Vec::new(),
substitution_made: false,
append_elements: Vec::new(),
}
+84 -63
View File
@@ -9,8 +9,7 @@
// file that was distributed with this source code.
use crate::sed::command::{
Address, AddressType, AddressValue, AppendElement, Command, CommandData, InputAction,
ProcessingContext, Transliteration,
Address, AppendElement, Command, CommandData, InputAction, ProcessingContext, Transliteration,
};
use crate::sed::error_handling::{ScriptLocation, input_runtime_error};
use crate::sed::fast_io::{IOChunk, LineReader, OutputBuffer};
@@ -40,30 +39,21 @@ macro_rules! extract_variant {
/// Return true if the passed address matches the current I/O context.
fn match_address(
addr: &Address,
reader: &mut LineReader,
pattern: &mut IOChunk,
context: &mut ProcessingContext,
location: &ScriptLocation,
) -> UResult<bool> {
match addr.atype {
AddressType::Re => {
if let AddressValue::Regex(ref re) = addr.value {
let regex = re_or_saved_re(re, context, location)?;
match regex.is_match(pattern) {
Ok(result) => Ok(result),
Err(e) => input_runtime_error(location, context, e.to_string()),
}
} else {
Ok(false)
match addr {
Address::Re(re) => {
let regex = re_or_saved_re(re, context, location)?;
match regex.is_match(pattern) {
Ok(result) => Ok(result),
Err(e) => input_runtime_error(location, context, e.to_string()),
}
}
AddressType::Line => {
if let AddressValue::LineNumber(lineno) = addr.value {
Ok(context.line_number == lineno)
} else {
Ok(false)
}
}
Address::Line(lineno) => Ok(context.line_number == *lineno),
// Recognize "$" as the last line of last file. This is consistent
// with the original 7th Research Edition implementation:
@@ -71,7 +61,7 @@ fn match_address(
// The FreeBSD version checked for subsequent empty files, but this
// can lead to destructive reads (e.g. from named pipes),
// and is probably an overkill.
AddressType::Last => Ok(context.last_line && (context.last_file || context.separate)),
Address::Last => Ok(reader.last_line()? && (context.last_file || context.separate)),
_ => panic!("invalid address type in match_address"),
}
@@ -81,63 +71,66 @@ fn match_address(
/// Return true if the command applies to the given pattern.
fn applies(
command: &mut Command,
reader: &mut LineReader,
pattern: &mut IOChunk,
context: &mut ProcessingContext,
) -> UResult<bool> {
let linenum = context.line_number;
let result = if command.addr1.is_none() && command.addr2.is_none() {
// No address
Ok(true)
} else if let Some(addr2) = &command.addr2 {
// Two addresses
if let Some(start) = command.start_line {
match addr2.atype {
AddressType::RelLine => {
if let AddressValue::LineNumber(n) = addr2.value {
if linenum - start <= n {
Ok(true)
} else {
command.start_line = None;
Ok(false)
}
} else {
// Range is already latched active.
match addr2 {
Address::RelLine(n) => {
if linenum - start > *n {
command.start_line = None;
Ok(false)
} else {
Ok(true)
}
}
_ => {
if match_address(addr2, pattern, context, &command.location)? {
Address::Line(n) => {
// Special case: already ended
if linenum > *n {
command.start_line = None;
context.last_address = true;
Ok(true)
} else if addr2.atype == AddressType::Line {
if let AddressValue::LineNumber(n) = addr2.value {
if linenum > n {
command.start_line = None;
Ok(false)
} else {
Ok(true)
}
} else {
Ok(true)
}
Ok(false)
} else {
Ok(true)
}
}
Address::StepMatch(step) => Ok((linenum - start).is_multiple_of(*step)),
Address::StepEnd(step) => {
// Inclusive end on multiple of step
if linenum.is_multiple_of(*step) {
command.start_line = None;
}
Ok(true)
}
_ => {
if match_address(addr2, reader, pattern, context, &command.location)? {
command.start_line = None;
context.last_address = true;
}
Ok(true)
}
}
} else if let Some(addr1) = &command.addr1 {
if match_address(addr1, pattern, context, &command.location)? {
match addr2.atype {
AddressType::Line => {
if let AddressValue::LineNumber(n) = addr2.value {
if linenum >= n {
context.last_address = true;
} else {
command.start_line = Some(linenum);
}
// See if latch must start.
if match_address(addr1, reader, pattern, context, &command.location)? {
match addr2 {
Address::Line(n) => {
if linenum >= *n {
context.last_address = true;
} else {
command.start_line = Some(linenum);
}
}
AddressType::RelLine => {
if let AddressValue::LineNumber(0) = addr2.value {
Address::RelLine(n) => {
if *n == 0 {
context.last_address = true;
} else {
command.start_line = Some(linenum);
@@ -155,9 +148,17 @@ fn applies(
Ok(false)
}
} else if let Some(addr1) = &command.addr1 {
Ok(match_address(addr1, pattern, context, &command.location)?)
// Single address
Ok(match_address(
addr1,
reader,
pattern,
context,
&command.location,
)?)
} else {
Ok(false)
// All allowed cases have been covered by the above logic.
panic!("impossible address combination");
};
if command.non_select {
@@ -431,8 +432,7 @@ fn process_file(
context: &mut ProcessingContext,
) -> UResult<()> {
// Loop over the input lines as pattern space.
'lines: while let Some((mut pattern, last_line)) = reader.get_line()? {
context.last_line = last_line;
'lines: while let Some(mut pattern) = reader.get_line()? {
context.line_number += 1;
context.substitution_made = false;
// Set the script command from which to start.
@@ -455,7 +455,7 @@ fn process_file(
while let Some(command_rc) = current.clone() {
let mut command = command_rc.borrow_mut();
if !applies(&mut command, &mut pattern, context)? {
if !applies(&mut command, reader, &mut pattern, context)? {
// Advance to next command
current = command.next.clone();
continue;
@@ -494,7 +494,7 @@ fn process_file(
// At range end replace pattern space with text and
// start the next cycle.
pattern.clear();
if command.addr2.is_none() || context.last_address || context.last_line {
if command.addr2.is_none() || context.last_address || reader.last_line()? {
let text = extract_variant!(command, Text);
output.write_str(text.as_ref())?;
}
@@ -567,6 +567,10 @@ fn process_file(
'p' => {
// Write the pattern space to standard output.
write_chunk(output, context, &pattern)?;
if !pattern.is_newline_terminated() {
// !chomped equivalent
output.write_str("\n")?; // explicit \n
}
}
'P' => {
// Output pattern space, up to the first \n.
@@ -675,6 +679,21 @@ fn process_file(
Ok(())
}
/// Mark all address ranges non-active (and 0-starting ones as active).
fn reset_latched_address_ranges(range_commands: &mut [Rc<RefCell<Command>>]) {
for cmd_rc in range_commands.iter() {
let mut cmd = cmd_rc.borrow_mut();
cmd.start_line =
// Check for address-spec line 0 pre-latch extension.
if let Some(addr1) = &cmd.addr1 && matches!(addr1, Address::Line(0)) {
Some(0)
} else {
None
};
}
}
/// Process all input files
pub fn process_all_files(
commands: Option<Rc<RefCell<Command>>>,
@@ -692,9 +711,11 @@ pub fn process_all_files(
.map_err_context(|| format!("error opening input file {}", path.quote()))?;
let output = in_place.begin(path)?;
if context.separate {
if index == 0 || context.separate {
context.line_number = 0;
reset_latched_address_ranges(&mut context.range_commands);
}
context.input_name = path.quote().to_string();
process_file(&commands, &mut reader, output, context)?;
+106
View File
@@ -14,6 +14,7 @@ use std::io::{Read, Write};
#[cfg(unix)]
use assert_fs::fixture::{FileWriteStr, PathChild};
use sha2::{Digest, Sha256};
use tempfile::NamedTempFile;
use uutests::new_ucmd;
@@ -225,6 +226,15 @@ check_output!(addr_escaped_delimiter, ["-n", "\\_l1\\_7_p", LINES1]);
check_output!(addr_range_numeric, ["-n", "1,4p", LINES1]);
check_output!(addr_range_to_last, ["-n", "1,$p", LINES1, LINES2]);
check_output!(addr_range_to_pattern, ["-n", "1,/l2_9/p", LINES1, LINES2]);
check_output!(
addr_range_straddle,
["-n", "/l1_3/,/l2_3/p", LINES1, LINES2]
);
check_output!(
addr_range_separate,
["-n", "--separate", "/l1_3/,/l2_3/p", LINES1, LINES2]
);
check_output!(addr_range_from_zero_to_pattern, ["-n", "0,/_1/p", LINES1]);
check_output!(addr_pattern_to_last, ["-n", "/4/,$p", LINES1, LINES2]);
check_output!(addr_pattern_to_straddle, ["-n", "/4/,20p", LINES1, LINES2]);
check_output!(addr_pattern_to_pattern, ["-n", "/4/,/10/p", LINES1, LINES2]);
@@ -259,6 +269,10 @@ check_output!(
);
check_output!(addr_empty_re_reuse, ["-n", "/_2/,//p", LINES1, LINES2]);
check_output!(addr_simple_negation, ["-e", r"4,12!s/^/^/", LINES1]);
check_output!(addr_range_even, ["-n", "0~2p", LINES1]);
check_output!(addr_range_odd, ["-n", "1~2p", LINES1]);
check_output!(addr_range_step_zero, ["-n", "10~0p", LINES1]);
check_output!(addr_range_end_multiple, ["-n", "/l1_2/,~10p", LINES1]);
////////////////////////////////////////////////////////////
// Substitution: s
@@ -987,6 +1001,42 @@ check_output!(pi, ["-f", "script/math.sed", "input/pi"]);
// Solve the Towers of Hanoi puzzle
check_output!(hanoi, ["-f", "script/hanoi.sed", "input/hanoi"]);
////////////////////////////////////////////////////////////
// Long-running scripts
// Test with cargo test -- --ignored
// Check the output of Bach's prelude in C major from WTC book I.
// Run with cargo test test_bach_prelude_matches -- --ignored.
#[test]
#[ignore] // Slow; produces 5.8 MB of raw audio.
fn test_bach_prelude_matches() {
let res = new_ucmd!()
.args(&["-E", "-f", "script/bach.sed"])
.pipe_in("\n")
.succeeds();
// Compare SHA-256 output against GNU sed output.
let mut hasher = Sha256::new();
hasher.update(res.stdout());
let digest = hasher.finalize();
let got = hex::encode(digest);
assert_eq!(
got,
"c4e50d6791a60692745e958dc48d43a40bccea2ce5cea31b7125a40604cc3219"
);
}
// Draw the Mandelbrot set.
#[ignore] // Slow; takes > 15" on an i7 CPU
#[test]
fn test_mandelbrod() {
new_ucmd!()
.args(&["-En", "-f", "script/mandelbrot.sed", "input/newline"])
.succeeds()
.stdout_is_fixture("output/mandelbrot");
}
////////////////////////////////////////////////////////////
// Error handling
#[test]
@@ -1016,6 +1066,51 @@ fn test_undefined_label() {
.stderr_is("sed: <script argument 1>:1:1: error: undefined label `foo'\n");
}
#[test]
fn test_addr0_non_posix() {
new_ucmd!()
.args(&["--posix", "0,/foo/p"])
.fails()
.code_is(1)
.stderr_is("sed: <script argument 1>:1:2: error: address 0 is invalid in POSIX mode\n");
}
#[test]
fn test_addr0_second_required() {
new_ucmd!()
.args(&["0p"])
.fails()
.code_is(1)
.stderr_is("sed: <script argument 1>:1:2: error: address 0 requires a second address\n");
}
#[test]
fn test_addr0_second_re_only() {
new_ucmd!()
.args(&["0,4p"])
.fails()
.code_is(1)
.stderr_is("sed: <script argument 1>:1:4: error: address 0 can only be used with a regular expression or ~step\n");
}
#[test]
fn test_step_match_non_posix() {
new_ucmd!()
.args(&["--posix", "3~2p"])
.fails()
.code_is(1)
.stderr_is("sed: <script argument 1>:1:3: error: ~step is invalid in POSIX mode\n");
}
#[test]
fn test_step_end_non_posix() {
new_ucmd!()
.args(&["--posix", "3,~2p"])
.fails()
.code_is(1)
.stderr_is("sed: <script argument 1>:1:4: error: ~step is invalid in POSIX mode\n");
}
// The following test diverse ways in which regexes are matched.
// Search for 'regex\.' to find them in the code.
#[test]
@@ -1080,3 +1175,14 @@ fn test_missing_address_re() {
.code_is(2)
.stderr_is("sed: <script argument 1>:2:3: 'input/lines1':1 error: no previous regular expression\n");
}
////////////////////////////////////////////////////////////
// Test for issue #143: Missing newline in output with `-e p`
#[test]
fn test_print_command_adds_newline() {
new_ucmd!()
.args(&["-e", "p"])
.pipe_in("foo")
.succeeds()
.stdout_is("foo\nfoo");
}
+1
View File
@@ -0,0 +1 @@
+9
View File
@@ -0,0 +1,9 @@
l1_2
l1_3
l1_4
l1_5
l1_6
l1_7
l1_8
l1_9
l1_10
+7
View File
@@ -0,0 +1,7 @@
l1_2
l1_4
l1_6
l1_8
l1_10
l1_12
l1_14
@@ -0,0 +1 @@
l1_1

Some files were not shown because too many files have changed in this diff Show More