-
Notifications
You must be signed in to change notification settings - Fork 0
Implement tr builtin command #35
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
AlexandreYang
wants to merge
37
commits into
main
Choose a base branch
from
dd/implement-tr-builtin
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
37 commits
Select commit
Hold shift + click to select a range
f73a022
Revert "remove resources"
AlexandreYang ab1acae
Implement tr builtin command
datadog-prod-us1-3[bot] ee26051
Reapply "remove resources"
AlexandreYang 0892434
Merge main into dd/implement-tr-builtin
AlexandreYang b51e13b
Address review comments on tr builtin
AlexandreYang 5adf341
Hide spurious --COMPLEMENT long flag to match GNU tr
AlexandreYang 7d76133
Fix equivalence class backslash escapes
datadog-official[bot] d4ef7d7
Fix skill name in fix-ci-tests SKILL.md
AlexandreYang 852cbea
Fix Windows CI failures in ls long_format and sandbox tests
AlexandreYang 45c36ee
Address review comments: STRING2 validation and empty operand handling
AlexandreYang a9ae0a8
Add skip_assert_against_bash to YAML scenarios
datadog-prod-us1-6[bot] 1a51818
Add bounds guard to rptErrMsg to prevent potential out-of-bounds panic
AlexandreYang 6c35f36
Validate case class alignment and escaped equiv class length
AlexandreYang faa1fd2
Use errors.Is for EOF checks per codebase convention
datadog-prod-us1-6[bot] fabbfe7
Reject STRING2 ending with character class when STRING1 is longer
AlexandreYang bceed6f
Merge branch 'main' into dd/implement-tr-builtin
AlexandreYang da8720d
update README.md
AlexandreYang 9025943
Add ambiguous octal escape warning and remove unnecessary skip_assert…
AlexandreYang 262187b
Fix YAML scenario: replace banned echo -n
datadog-prod-us1-5[bot] 00c2b30
Skip case-class alignment check when complement flag is used
AlexandreYang 68ce3af
Restore exact stderr assertion and remove unnecessary skip_assert_aga…
AlexandreYang 0187a3a
Address review comments: bounds comment and missing stderr assertion
AlexandreYang 1547f4e
Merge branch 'main' into dd/implement-tr-builtin
AlexandreYang 8034c23
Merge branch 'main' into dd/implement-tr-builtin
AlexandreYang d05beb9
Address PR review comments on tr builtin
AlexandreYang 98f3b03
Fix linter diagnostics in tr builtin
AlexandreYang 7207a7d
Merge branch 'main' into dd/implement-tr-builtin
AlexandreYang 0f57ea5
Address review comments: refactor I/O loop, defensive copy, simplify …
AlexandreYang 4bfd0ca
Remove dead `name` field from caseClassPos struct
AlexandreYang a768c30
Fix tr repeat parsing edge cases
AlexandreYang 10ae9d3
Address review comments: skip_assert_against_bash, negative-zero repe…
AlexandreYang 86eff2e
Remove unnecessary skip_assert_against_bash from 8 tr error scenarios…
AlexandreYang c4eafa4
Address remaining review comments: clamp fill-repeat needed count and…
AlexandreYang 73d7bf6
Simplify needed clamp using max() builtin
AlexandreYang f520198
Address review: allow [c*n] in STRING1, repeats in -ds STRING2, rejec…
AlexandreYang 049aafb
Fix misindented comment on complemented-class rejection
AlexandreYang 12e4d8a
revert .claude/skills/code-review/SKILL.md
AlexandreYang File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,194 @@ | ||
| // Unless explicitly stated otherwise all files in this repository are licensed | ||
| // under the Apache License Version 2.0. | ||
| // This product includes software developed at Datadog (https://www.datadoghq.com/). | ||
| // Copyright 2026-present Datadog, Inc. | ||
|
|
||
| // Exploratory pentest for the tr builtin. | ||
| // | ||
| // These tests probe edge cases in flag/argument handling, special input | ||
| // patterns, memory safety boundaries, and behaviour-matching with GNU tr. | ||
|
|
||
| package tr_test | ||
|
|
||
| import ( | ||
| "context" | ||
| "strings" | ||
| "testing" | ||
| "time" | ||
|
|
||
| "github.com/stretchr/testify/assert" | ||
|
|
||
| "github.com/DataDog/rshell/interp" | ||
| ) | ||
|
|
||
| const pentestTimeout = 10 * time.Second | ||
|
|
||
| func mustNotHang(t *testing.T, f func()) { | ||
| t.Helper() | ||
| done := make(chan struct{}) | ||
| go func() { | ||
| defer close(done) | ||
| f() | ||
| }() | ||
| select { | ||
| case <-done: | ||
| case <-time.After(pentestTimeout): | ||
| t.Fatalf("operation did not complete within %s", pentestTimeout) | ||
| } | ||
| } | ||
|
|
||
| // --- Flag and argument injection --- | ||
|
|
||
| func TestTrPentestUnknownLongFlag(t *testing.T) { | ||
| _, stderr, code := runScript(t, "tr --no-such-flag a b", t.TempDir()) | ||
| assert.Equal(t, 1, code) | ||
| assert.Contains(t, stderr, "tr:") | ||
| } | ||
|
|
||
| func TestTrPentestDoubleDashOperands(t *testing.T) { | ||
| stdout, _, code := trRun(t, "aha", "-- ah -H") | ||
| assert.Equal(t, 0, code) | ||
| assert.Equal(t, "-H-", stdout) | ||
| } | ||
|
|
||
| func TestTrPentestBasicTranslation(t *testing.T) { | ||
| stdout, _, code := trRun(t, "abc", "a X") | ||
| assert.Equal(t, 0, code) | ||
| assert.Equal(t, "Xbc", stdout) | ||
| } | ||
|
|
||
| // --- Empty/whitespace operands --- | ||
|
|
||
| func TestTrPentestEmptySet1(t *testing.T) { | ||
| dir := t.TempDir() | ||
| writeFile(t, dir, "in.txt", "abc") | ||
| _, _, code := runScript(t, `cat in.txt | tr '' '[.*]'`, dir, interp.AllowedPaths([]string{dir})) | ||
| assert.Equal(t, 0, code) | ||
| } | ||
|
|
||
| func TestTrPentestEmptySet2WithTruncate(t *testing.T) { | ||
| dir := t.TempDir() | ||
| writeFile(t, dir, "in.txt", "abc") | ||
| _, _, code := runScript(t, `cat in.txt | tr -t abc ''`, dir, interp.AllowedPaths([]string{dir})) | ||
| assert.Equal(t, 0, code) | ||
| } | ||
|
|
||
| // --- Context timeout on infinite source --- | ||
|
|
||
| func TestTrPentestContextTimeout(t *testing.T) { | ||
| mustNotHang(t, func() { | ||
| ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) | ||
| defer cancel() | ||
| _, _, _ = runScriptCtx(ctx, t, "tr a b", t.TempDir()) | ||
| }) | ||
| } | ||
|
|
||
| // --- Large repetitive input --- | ||
|
|
||
| func TestTrPentestLargeInput(t *testing.T) { | ||
| dir := t.TempDir() | ||
| large := strings.Repeat("a", 100000) | ||
| writeFile(t, dir, "in.txt", large) | ||
| stdout, _, code := runScript(t, "cat in.txt | tr a b", dir, interp.AllowedPaths([]string{dir})) | ||
| assert.Equal(t, 0, code) | ||
| expected := strings.Repeat("b", 100000) | ||
| assert.Equal(t, expected, stdout) | ||
| } | ||
|
|
||
| // --- Repeat construct edge cases --- | ||
|
|
||
| func TestTrPentestRepeatFillEmptyCount(t *testing.T) { | ||
| stdout, _, code := trRun(t, "abcdef", "abcdef '1[x*]2'") | ||
| assert.Equal(t, 0, code) | ||
| assert.Equal(t, "1xxxx2", stdout) | ||
| } | ||
|
|
||
| func TestTrPentestRepeatOctal(t *testing.T) { | ||
| stdout, _, code := trRun(t, "abc", "abc '[b*3]'") | ||
| assert.Equal(t, 0, code) | ||
| assert.Equal(t, "bbb", stdout) | ||
| } | ||
|
|
||
| func TestTrPentestInvalidRepeatCount(t *testing.T) { | ||
| dir := t.TempDir() | ||
| writeFile(t, dir, "in.txt", "") | ||
| _, stderr, code := runScript(t, "cat in.txt | tr 'a' '[b*c]'", dir, interp.AllowedPaths([]string{dir})) | ||
| assert.Equal(t, 1, code) | ||
| assert.Contains(t, stderr, "invalid repeat count") | ||
| } | ||
|
|
||
| // --- Character class edge cases --- | ||
|
|
||
| func TestTrPentestInvalidClassName(t *testing.T) { | ||
| _, stderr, code := trRun(t, "", "-d '[:bogus:]'") | ||
| assert.Equal(t, 1, code) | ||
| assert.Contains(t, stderr, "invalid character class") | ||
| } | ||
|
|
||
| func TestTrPentestDeleteAlnumDotsRemain(t *testing.T) { | ||
| stdout, _, code := trRun(t, ".abc123.", "-d '[:alnum:]'") | ||
| assert.Equal(t, 0, code) | ||
| assert.Equal(t, "..", stdout) | ||
| } | ||
|
|
||
| // --- Backslash escape edge cases --- | ||
|
|
||
| func TestTrPentestOctalThreeDigit(t *testing.T) { | ||
| stdout, _, code := trRun(t, "XYZ", `XYZ '\0\11\77'`) | ||
| assert.Equal(t, 0, code) | ||
| assert.Equal(t, "\000\t?", stdout) | ||
| } | ||
|
|
||
| func TestTrPentestUnrecognizedEscape(t *testing.T) { | ||
| stdout, _, code := trRun(t, "q", `q '\q'`) | ||
| assert.Equal(t, 0, code) | ||
| assert.Equal(t, "q", stdout) | ||
| } | ||
|
|
||
| // --- Combined flags --- | ||
|
|
||
| func TestTrPentestDeleteSqueezeCombined(t *testing.T) { | ||
| stdout, _, code := trRun(t, "a.b.c $$$$code\\", `-ds a-z '$.'`) | ||
| assert.Equal(t, 0, code) | ||
| assert.Equal(t, ". $\\", stdout) | ||
| } | ||
|
|
||
| func TestTrPentestComplementSqueeze(t *testing.T) { | ||
| stdout, _, code := trRun(t, "aaBBcDcc", "-sc a-z") | ||
| assert.Equal(t, 0, code) | ||
| assert.Equal(t, "aaBcDcc", stdout) | ||
| } | ||
|
|
||
| // --- Equivalence class edge cases --- | ||
|
|
||
| func TestTrPentestMultiCharEquiv(t *testing.T) { | ||
| _, stderr, code := trRun(t, "", "-d '[=aa=]'") | ||
| assert.Equal(t, 1, code) | ||
| assert.Contains(t, stderr, "equivalence class operand must be a single character") | ||
| } | ||
|
|
||
| // --- Pipe integration --- | ||
|
|
||
| func TestTrPentestPipeChain(t *testing.T) { | ||
| dir := t.TempDir() | ||
| writeFile(t, dir, "in.txt", "Hello World 123") | ||
| stdout, _, code := runScript(t, "cat in.txt | tr A-Z a-z | tr -d '[:digit:]'", dir, interp.AllowedPaths([]string{dir})) | ||
| assert.Equal(t, 0, code) | ||
| assert.Equal(t, "hello world ", stdout) | ||
| } | ||
|
|
||
| // --- Complement with delete --- | ||
|
|
||
| func TestTrPentestComplementDeleteKeepAlpha(t *testing.T) { | ||
| stdout, _, code := trRun(t, "abc123!@#", "-dc '[:alpha:]'") | ||
| assert.Equal(t, 0, code) | ||
| assert.Equal(t, "abc", stdout) | ||
| } | ||
|
|
||
| // --- Big C flag --- | ||
|
|
||
| func TestTrPentestBigCFlag(t *testing.T) { | ||
| stdout, _, code := trRun(t, "Phone: 01234 567890", "-d -C 0-9") | ||
| assert.Equal(t, 0, code) | ||
| assert.Equal(t, "01234567890", stdout) | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.