diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index 5d2ef354..a5f7f6f3 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -105,9 +105,39 @@ jobs: echo "No fuzz targets found in ${{ matrix.pkg }}, skipping" exit 0 fi + # fuzz_run wraps a single fuzz invocation and treats "context deadline exceeded" + # at the fuzz time boundary as success. When -fuzztime=30s expires, Go's internal + # fuzz coordinator cancels any in-flight iteration and emits "context deadline + # exceeded" with no file:line reference — this is not a real test failure. + # Real failures always include a file:line reference (e.g. foo_test.go:42:). + # + # We use `tee` rather than output=$(...) so that: + # 1. Progress lines appear in the Actions log in real time (output=$(...) buffers + # everything and bash -e exits the function before we ever reach echo "$output"). + # 2. PIPESTATUS[0] captures go test's exit code even when the pipe itself exits 0. + fuzz_run() { + local func="$1" + local tmpfile exit_code + tmpfile=$(mktemp) + go test -fuzz="^${func}$" -fuzztime=30s ${{ matrix.pkg }} -timeout 90s 2>&1 | tee "$tmpfile" || true + exit_code=${PIPESTATUS[0]} + if [ $exit_code -ne 0 ]; then + # Check whether any line has a file:line reference (real assertion failure). + # Coordinator boundary timeouts produce " context deadline exceeded" with + # no file:line, so they will NOT match this pattern. + if grep -qE '[[:space:]]+[^[:space:]]+_test\.go:[0-9]+:' "$tmpfile"; then + rm -f "$tmpfile" + echo "FAIL: $func — test assertion failure detected" >&2 + return $exit_code + fi + echo "NOTE: $func — fuzz coordinator boundary timeout (expected at fuzz time limit, not a failure)" + fi + rm -f "$tmpfile" + return 0 + } for FUNC in $FUZZ_FUNCS; do echo "Fuzzing $FUNC..." - go test -fuzz="^${FUNC}$" -fuzztime=30s ${{ matrix.pkg }} -timeout 300s + fuzz_run "$FUNC" done # Save corpus @@ -159,9 +189,39 @@ jobs: echo "No differential fuzz targets found in ${{ matrix.pkg }}, skipping" exit 0 fi + # fuzz_run wraps a single fuzz invocation and treats "context deadline exceeded" + # at the fuzz time boundary as success. When -fuzztime=30s expires, Go's internal + # fuzz coordinator cancels any in-flight iteration and emits "context deadline + # exceeded" with no file:line reference — this is not a real test failure. + # Real failures always include a file:line reference (e.g. foo_test.go:42:). + # + # We use `tee` rather than output=$(...) so that: + # 1. Progress lines appear in the Actions log in real time (output=$(...) buffers + # everything and bash -e exits the function before we ever reach echo "$output"). + # 2. PIPESTATUS[0] captures go test's exit code even when the pipe itself exits 0. + fuzz_run() { + local func="$1" + local tmpfile exit_code + tmpfile=$(mktemp) + go test -fuzz="^${func}$" -fuzztime=30s ${{ matrix.pkg }} -timeout 90s 2>&1 | tee "$tmpfile" || true + exit_code=${PIPESTATUS[0]} + if [ $exit_code -ne 0 ]; then + # Check whether any line has a file:line reference (real assertion failure). + # Coordinator boundary timeouts produce " context deadline exceeded" with + # no file:line, so they will NOT match this pattern. + if grep -qE '[[:space:]]+[^[:space:]]+_test\.go:[0-9]+:' "$tmpfile"; then + rm -f "$tmpfile" + echo "FAIL: $func — test assertion failure detected" >&2 + return $exit_code + fi + echo "NOTE: $func — fuzz coordinator boundary timeout (expected at fuzz time limit, not a failure)" + fi + rm -f "$tmpfile" + return 0 + } for FUNC in $FUZZ_FUNCS; do echo "Fuzzing $FUNC..." - go test -fuzz="^${FUNC}$" -fuzztime=30s ${{ matrix.pkg }} -timeout 300s + fuzz_run "$FUNC" done - name: Save fuzz corpus diff --git a/builtins/internal/procinfo/procinfo_windows.go b/builtins/internal/procinfo/procinfo_windows.go index 54865ebb..3de675be 100644 --- a/builtins/internal/procinfo/procinfo_windows.go +++ b/builtins/internal/procinfo/procinfo_windows.go @@ -62,16 +62,18 @@ func getSession(ctx context.Context, procPath string) ([]ProcInfo, error) { selfPID := os.Getpid() ancestors := make(map[int]bool) + visited := make(map[int]bool) cur := selfPID for cur > 0 { + if visited[cur] { + break // cycle detected in PPID chain + } + visited[cur] = true ancestors[cur] = true p, ok := byPID[cur] if !ok { break } - if p.PPID == cur { - break // avoid infinite loop for PID 0 - } cur = p.PPID } diff --git a/builtins/ping/ping_fuzz_test.go b/builtins/ping/ping_fuzz_test.go index ddace195..f1184adf 100644 --- a/builtins/ping/ping_fuzz_test.go +++ b/builtins/ping/ping_fuzz_test.go @@ -61,6 +61,9 @@ func FuzzPingFlags(f *testing.F) { f.Add("-h", "") f.Fuzz(func(t *testing.T, flag, value string) { + if t.Context().Err() != nil { + return + } // Only allow characters that are safe to pass unquoted in a shell script. // Using an allowlist is more robust than a denylist: any character not // explicitly permitted here could cause shell parse errors or command @@ -76,7 +79,7 @@ func FuzzPingFlags(f *testing.F) { return } - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 3*time.Second) defer cancel() var script string @@ -87,6 +90,9 @@ func FuzzPingFlags(f *testing.F) { } _, _, code := cmdRunCtxFuzz(ctx, t, script) + if t.Context().Err() != nil { + return + } if code != 0 && code != 1 { t.Errorf("unexpected exit code %d for script: %s", code, script) } @@ -122,6 +128,9 @@ func FuzzPingHostname(f *testing.F) { f.Add("no-such-host-xyzzy.invalid") f.Fuzz(func(t *testing.T, hostname string) { + if t.Context().Err() != nil { + return + } // Only allow characters that are safe to pass unquoted as a shell // argument. An allowlist is more robust than a denylist because the // shell parser has many special characters and we cannot enumerate @@ -144,11 +153,14 @@ func FuzzPingHostname(f *testing.F) { // 1s is enough for fast DNS + socket attempt; shorter than the // 3s default to keep CI fuzz runs from stalling on unresolvable // hostnames when the corpus grows to include slow-DNS entries. - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 1*time.Second) defer cancel() script := fmt.Sprintf("ping -c 1 -W 500ms -- %s", hostname) _, _, code := cmdRunCtxFuzz(ctx, t, script) + if t.Context().Err() != nil { + return + } if code != 0 && code != 1 { t.Errorf("unexpected exit code %d for hostname: %q", code, hostname) } diff --git a/builtins/tests/cat/cat_differential_fuzz_test.go b/builtins/tests/cat/cat_differential_fuzz_test.go index 23f2d7b4..c8cb885d 100644 --- a/builtins/tests/cat/cat_differential_fuzz_test.go +++ b/builtins/tests/cat/cat_differential_fuzz_test.go @@ -75,6 +75,9 @@ func FuzzCatDifferential(f *testing.F) { var counter atomic.Int64 f.Fuzz(func(t *testing.T, input []byte) { + if t.Context().Err() != nil { + return + } if len(input) > 64*1024 { return } @@ -86,17 +89,11 @@ func FuzzCatDifferential(f *testing.F) { t.Fatal(err) } - // Use context.Background() (not t.Context()) so the fuzz engine's - // cancellation does not kill the command mid-run; each iteration still - // enforces its own 5 s deadline. - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() // safety net if t.Fatal fires before explicit cancel rshellOut, rshellErr, rshellCode := cmdRunCtx(ctx, t, "cat input.txt", dir) cancel() - // If the fuzz engine's budget expired (t.Context(), not the per-command - // context above), bail out without comparing — partial output would cause - // false failures. if t.Context().Err() != nil { return } diff --git a/builtins/tests/cat/cat_fuzz_test.go b/builtins/tests/cat/cat_fuzz_test.go index fdb75219..6eef2a06 100644 --- a/builtins/tests/cat/cat_fuzz_test.go +++ b/builtins/tests/cat/cat_fuzz_test.go @@ -63,6 +63,9 @@ func FuzzCat(f *testing.F) { var counter atomic.Int64 f.Fuzz(func(t *testing.T, input []byte) { + if t.Context().Err() != nil { + return + } if len(input) > 1<<20 { return } @@ -75,10 +78,13 @@ func FuzzCat(f *testing.F) { t.Fatal(err) } - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() // safety net if t.Fatal fires before explicit cancel stdout, _, code := cmdRunCtx(ctx, t, "cat input.txt", dir) cancel() + if t.Context().Err() != nil { + return + } if code != 0 && code != 1 { t.Errorf("unexpected exit code %d", code) } @@ -114,6 +120,9 @@ func FuzzCatNumberLines(f *testing.F) { var counter atomic.Int64 f.Fuzz(func(t *testing.T, input []byte) { + if t.Context().Err() != nil { + return + } if len(input) > 1<<20 { return } @@ -126,10 +135,13 @@ func FuzzCatNumberLines(f *testing.F) { t.Fatal(err) } - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() // safety net if t.Fatal fires before explicit cancel _, _, code := cmdRunCtx(ctx, t, "cat -n input.txt", dir) cancel() + if t.Context().Err() != nil { + return + } if code != 0 && code != 1 { t.Errorf("cat -n unexpected exit code %d", code) } @@ -163,6 +175,9 @@ func FuzzCatDisplayFlags(f *testing.F) { var counter atomic.Int64 f.Fuzz(func(t *testing.T, input []byte, flagV, flagE, flagT bool) { + if t.Context().Err() != nil { + return + } if len(input) > 1<<20 { return } @@ -188,10 +203,13 @@ func FuzzCatDisplayFlags(f *testing.F) { flags += " -T" } - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() // safety net if t.Fatal fires before explicit cancel _, _, code := cmdRunCtx(ctx, t, "cat"+flags+" input.bin", dir) cancel() + if t.Context().Err() != nil { + return + } if code != 0 && code != 1 { t.Errorf("cat%s unexpected exit code %d", flags, code) } @@ -214,6 +232,9 @@ func FuzzCatStdin(f *testing.F) { var counter atomic.Int64 f.Fuzz(func(t *testing.T, input []byte) { + if t.Context().Err() != nil { + return + } if len(input) > 1<<20 { return } @@ -226,10 +247,13 @@ func FuzzCatStdin(f *testing.F) { t.Fatal(err) } - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() // safety net if t.Fatal fires before explicit cancel stdout, _, code := cmdRunCtx(ctx, t, "cat < stdin.txt", dir) cancel() + if t.Context().Err() != nil { + return + } if code != 0 && code != 1 { t.Errorf("cat stdin unexpected exit code %d", code) } diff --git a/builtins/tests/cut/cut_fuzz_test.go b/builtins/tests/cut/cut_fuzz_test.go index 6b7e1220..a6f87a5e 100644 --- a/builtins/tests/cut/cut_fuzz_test.go +++ b/builtins/tests/cut/cut_fuzz_test.go @@ -74,6 +74,9 @@ func FuzzCutFields(f *testing.F) { var counter atomic.Int64 f.Fuzz(func(t *testing.T, input []byte, fieldSpec string) { + if t.Context().Err() != nil { + return + } if len(input) > 1<<20 { return } @@ -97,10 +100,13 @@ func FuzzCutFields(f *testing.F) { t.Fatal(err) } - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() // safety net if t.Fatal fires before explicit cancel _, _, code := cmdRunCtxFuzz(ctx, t, fmt.Sprintf("cut -f %s input.txt", fieldSpec), dir) cancel() + if t.Context().Err() != nil { + return + } if code != 0 && code != 1 { t.Errorf("cut -f %s unexpected exit code %d", fieldSpec, code) } @@ -143,6 +149,9 @@ func FuzzCutBytes(f *testing.F) { var counter atomic.Int64 f.Fuzz(func(t *testing.T, input []byte, byteSpec string) { + if t.Context().Err() != nil { + return + } if len(input) > 1<<20 { return } @@ -165,10 +174,13 @@ func FuzzCutBytes(f *testing.F) { t.Fatal(err) } - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() // safety net if t.Fatal fires before explicit cancel _, _, code := cmdRunCtxFuzz(ctx, t, fmt.Sprintf("cut -b %s input.txt", byteSpec), dir) cancel() + if t.Context().Err() != nil { + return + } if code != 0 && code != 1 { t.Errorf("cut -b %s unexpected exit code %d", byteSpec, code) } @@ -201,6 +213,9 @@ func FuzzCutDelimiter(f *testing.F) { var counter atomic.Int64 f.Fuzz(func(t *testing.T, input []byte, delim string, fieldSpec string) { + if t.Context().Err() != nil { + return + } if len(input) > 1<<20 { return } @@ -231,11 +246,14 @@ func FuzzCutDelimiter(f *testing.F) { t.Fatal(err) } - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() // safety net if t.Fatal fires before explicit cancel script := fmt.Sprintf("cut -d '%s' -f %s input.txt", delim, fieldSpec) _, _, code := cmdRunCtxFuzz(ctx, t, script, dir) cancel() + if t.Context().Err() != nil { + return + } if code != 0 && code != 1 { t.Errorf("cut -d '%s' -f %s unexpected exit code %d", delim, fieldSpec, code) } @@ -268,6 +286,9 @@ func FuzzCutComplement(f *testing.F) { var counter atomic.Int64 f.Fuzz(func(t *testing.T, input []byte, byteSpec string) { + if t.Context().Err() != nil { + return + } if len(input) > 1<<20 { return } @@ -290,10 +311,13 @@ func FuzzCutComplement(f *testing.F) { t.Fatal(err) } - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() // safety net if t.Fatal fires before explicit cancel _, _, code := cmdRunCtxFuzz(ctx, t, fmt.Sprintf("cut --complement -b %s input.txt", byteSpec), dir) cancel() + if t.Context().Err() != nil { + return + } if code != 0 && code != 1 { t.Errorf("cut --complement -b %s unexpected exit code %d", byteSpec, code) } @@ -320,6 +344,9 @@ func FuzzCutStdin(f *testing.F) { var counter atomic.Int64 f.Fuzz(func(t *testing.T, input []byte) { + if t.Context().Err() != nil { + return + } if len(input) > 1<<20 { return } @@ -331,10 +358,13 @@ func FuzzCutStdin(f *testing.F) { t.Fatal(err) } - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() // safety net if t.Fatal fires before explicit cancel _, _, code := cmdRunCtxFuzz(ctx, t, "cut -f 1 < stdin.txt", dir) cancel() + if t.Context().Err() != nil { + return + } if code != 0 && code != 1 { t.Errorf("cut stdin unexpected exit code %d", code) } diff --git a/builtins/tests/echo/echo_fuzz_test.go b/builtins/tests/echo/echo_fuzz_test.go index 1b35fd98..98064ce1 100644 --- a/builtins/tests/echo/echo_fuzz_test.go +++ b/builtins/tests/echo/echo_fuzz_test.go @@ -41,6 +41,9 @@ func FuzzEcho(f *testing.F) { var counter atomic.Int64 f.Fuzz(func(t *testing.T, arg string) { + if t.Context().Err() != nil { + return + } if len(arg) > 1000 { return } @@ -60,10 +63,13 @@ func FuzzEcho(f *testing.F) { dir, cleanup := testutil.FuzzIterDir(t, baseDir, &counter) defer cleanup() - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() // safety net if t.Fatal fires before explicit cancel _, _, code := fuzzRunCtx(ctx, t, "echo '"+arg+"'", dir) cancel() + if t.Context().Err() != nil { + return + } if code != 0 { t.Errorf("echo unexpected exit code %d", code) } @@ -113,6 +119,9 @@ func FuzzEchoEscapes(f *testing.F) { var counter atomic.Int64 f.Fuzz(func(t *testing.T, arg string) { + if t.Context().Err() != nil { + return + } if len(arg) > 1000 { return } @@ -132,10 +141,13 @@ func FuzzEchoEscapes(f *testing.F) { dir, cleanup := testutil.FuzzIterDir(t, baseDir, &counter) defer cleanup() - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() // safety net if t.Fatal fires before explicit cancel _, _, code := fuzzRunCtx(ctx, t, "echo -e '"+arg+"'", dir) cancel() + if t.Context().Err() != nil { + return + } if code != 0 { t.Errorf("echo -e unexpected exit code %d", code) } @@ -155,6 +167,9 @@ func FuzzEchoFlagInteraction(f *testing.F) { var counter atomic.Int64 f.Fuzz(func(t *testing.T, arg string, flagN, flagE, flagBigE bool) { + if t.Context().Err() != nil { + return + } if len(arg) > 500 { return } @@ -188,10 +203,13 @@ func FuzzEchoFlagInteraction(f *testing.F) { dir, cleanup := testutil.FuzzIterDir(t, baseDir, &counter) defer cleanup() - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() // safety net if t.Fatal fires before explicit cancel _, _, code := fuzzRunCtx(ctx, t, "echo"+flags+" '"+arg+"'", dir) cancel() + if t.Context().Err() != nil { + return + } if code != 0 { t.Errorf("echo%s unexpected exit code %d", flags, code) } diff --git a/builtins/tests/grep/grep_fuzz_test.go b/builtins/tests/grep/grep_fuzz_test.go index 15d73e25..de507b14 100644 --- a/builtins/tests/grep/grep_fuzz_test.go +++ b/builtins/tests/grep/grep_fuzz_test.go @@ -58,6 +58,9 @@ func FuzzGrepFileContent(f *testing.F) { var counter atomic.Int64 f.Fuzz(func(t *testing.T, input []byte, pattern string) { + if t.Context().Err() != nil { + return + } if len(input) > 1<<20 { return } @@ -88,11 +91,14 @@ func FuzzGrepFileContent(f *testing.F) { t.Fatal(err) } - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() // safety net if t.Fatal fires before explicit cancel script := "grep '" + pattern + "' input.txt" _, _, code := cmdRunCtx(ctx, t, script, dir) cancel() + if t.Context().Err() != nil { + return + } if code != 0 && code != 1 && code != 2 { t.Errorf("grep unexpected exit code %d", code) } @@ -132,6 +138,9 @@ func FuzzGrepPatterns(f *testing.F) { var counter atomic.Int64 f.Fuzz(func(t *testing.T, input []byte, pattern string) { + if t.Context().Err() != nil { + return + } if len(input) > 1<<20 { return } @@ -161,10 +170,13 @@ func FuzzGrepPatterns(f *testing.F) { t.Fatal(err) } - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() // safety net if t.Fatal fires before explicit cancel _, _, code := cmdRunCtx(ctx, t, "grep '"+pattern+"' input.txt", dir) cancel() + if t.Context().Err() != nil { + return + } if code != 0 && code != 1 && code != 2 { t.Errorf("grep pattern %q unexpected exit code %d", pattern, code) } @@ -187,6 +199,9 @@ func FuzzGrepStdin(f *testing.F) { var counter atomic.Int64 f.Fuzz(func(t *testing.T, input []byte) { + if t.Context().Err() != nil { + return + } if len(input) > 1<<20 { return } @@ -199,10 +214,13 @@ func FuzzGrepStdin(f *testing.F) { t.Fatal(err) } - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() // safety net if t.Fatal fires before explicit cancel _, _, code := cmdRunCtx(ctx, t, "grep '.' < stdin.txt", dir) cancel() + if t.Context().Err() != nil { + return + } if code != 0 && code != 1 && code != 2 { t.Errorf("grep stdin unexpected exit code %d", code) } @@ -244,6 +262,9 @@ func FuzzGrepFixedStrings(f *testing.F) { var counter atomic.Int64 f.Fuzz(func(t *testing.T, input []byte, pattern string) { + if t.Context().Err() != nil { + return + } if len(input) > 1<<20 { return } @@ -270,10 +291,13 @@ func FuzzGrepFixedStrings(f *testing.F) { t.Fatal(err) } - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() // safety net if t.Fatal fires before explicit cancel _, _, code := cmdRunCtx(ctx, t, "grep -F '"+pattern+"' input.txt", dir) cancel() + if t.Context().Err() != nil { + return + } if code != 0 && code != 1 && code != 2 { t.Errorf("grep -F unexpected exit code %d", code) } @@ -304,6 +328,9 @@ func FuzzGrepFlags(f *testing.F) { var counter atomic.Int64 f.Fuzz(func(t *testing.T, input []byte, caseInsensitive, invertMatch, countOnly, quiet bool, afterCtx, beforeCtx int64) { + if t.Context().Err() != nil { + return + } if len(input) > 1<<20 { return } @@ -322,7 +349,7 @@ func FuzzGrepFlags(f *testing.F) { t.Fatal(err) } - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() // safety net if t.Fatal fires before explicit cancel flags := "" if caseInsensitive { @@ -347,6 +374,9 @@ func FuzzGrepFlags(f *testing.F) { script := "grep" + flags + " 'a' input.txt" _, _, code := cmdRunCtx(ctx, t, script, dir) cancel() + if t.Context().Err() != nil { + return + } if code != 0 && code != 1 && code != 2 { t.Errorf("grep%s unexpected exit code %d", flags, code) } diff --git a/builtins/tests/head/head_differential_fuzz_test.go b/builtins/tests/head/head_differential_fuzz_test.go index f5cebaaf..595bdfb7 100644 --- a/builtins/tests/head/head_differential_fuzz_test.go +++ b/builtins/tests/head/head_differential_fuzz_test.go @@ -76,6 +76,9 @@ func FuzzHeadDifferentialLines(f *testing.F) { var counter atomic.Int64 f.Fuzz(func(t *testing.T, input []byte, n int64) { + if t.Context().Err() != nil { + return + } if len(input) > 64*1024 { return } @@ -92,17 +95,11 @@ func FuzzHeadDifferentialLines(f *testing.F) { nStr := fmt.Sprintf("%d", n) - // Use context.Background() (not t.Context()) so the fuzz engine's - // cancellation does not kill the command mid-run; each iteration still - // enforces its own 5 s deadline. - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() // safety net if t.Fatal fires before explicit cancel rshellOut, rshellErr, rshellCode := cmdRunCtx(ctx, t, fmt.Sprintf("head -n %s input.txt", nStr), dir) cancel() - // If the fuzz engine's budget expired (t.Context(), not the per-command - // context above), bail out without comparing — partial output would cause - // false failures. if t.Context().Err() != nil { return } @@ -143,6 +140,9 @@ func FuzzHeadDifferentialBytes(f *testing.F) { var counter atomic.Int64 f.Fuzz(func(t *testing.T, input []byte, n int64) { + if t.Context().Err() != nil { + return + } if len(input) > 64*1024 { return } @@ -159,17 +159,11 @@ func FuzzHeadDifferentialBytes(f *testing.F) { nStr := fmt.Sprintf("%d", n) - // Use context.Background() (not t.Context()) so the fuzz engine's - // cancellation does not kill the command mid-run; each iteration still - // enforces its own 5 s deadline. - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() // safety net if t.Fatal fires before explicit cancel rshellOut, rshellErr, rshellCode := cmdRunCtx(ctx, t, fmt.Sprintf("head -c %s input.txt", nStr), dir) cancel() - // If the fuzz engine's budget expired (t.Context(), not the per-command - // context above), bail out without comparing — partial output would cause - // false failures. if t.Context().Err() != nil { return } diff --git a/builtins/tests/head/head_fuzz_test.go b/builtins/tests/head/head_fuzz_test.go index 90e6026d..4ebbf1a4 100644 --- a/builtins/tests/head/head_fuzz_test.go +++ b/builtins/tests/head/head_fuzz_test.go @@ -56,6 +56,9 @@ func FuzzHeadLines(f *testing.F) { var counter atomic.Int64 f.Fuzz(func(t *testing.T, input []byte, n int64) { + if t.Context().Err() != nil { + return + } if len(input) > 1<<20 { return } @@ -74,10 +77,13 @@ func FuzzHeadLines(f *testing.F) { t.Fatal(err) } - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() // safety net if t.Fatal fires before explicit cancel stdout, _, code := cmdRunCtx(ctx, t, fmt.Sprintf("head -n %d input.txt", n), dir) cancel() + if t.Context().Err() != nil { + return + } if code != 0 && code != 1 { t.Errorf("unexpected exit code %d", code) } @@ -120,6 +126,9 @@ func FuzzHeadBytes(f *testing.F) { var counter atomic.Int64 f.Fuzz(func(t *testing.T, input []byte, n int64) { + if t.Context().Err() != nil { + return + } if len(input) > 1<<20 { return } @@ -138,10 +147,13 @@ func FuzzHeadBytes(f *testing.F) { t.Fatal(err) } - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() // safety net if t.Fatal fires before explicit cancel stdout, _, code := cmdRunCtx(ctx, t, fmt.Sprintf("head -c %d input.txt", n), dir) cancel() + if t.Context().Err() != nil { + return + } if code != 0 && code != 1 { t.Errorf("unexpected exit code %d", code) } @@ -171,6 +183,9 @@ func FuzzHeadStdin(f *testing.F) { var counter atomic.Int64 f.Fuzz(func(t *testing.T, input []byte, n int64) { + if t.Context().Err() != nil { + return + } if len(input) > 1<<20 { return } @@ -189,10 +204,13 @@ func FuzzHeadStdin(f *testing.F) { t.Fatal(err) } - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() // safety net if t.Fatal fires before explicit cancel _, _, code := cmdRunCtx(ctx, t, fmt.Sprintf("head -n %d < stdin.txt", n), dir) cancel() + if t.Context().Err() != nil { + return + } if code != 0 && code != 1 { t.Errorf("unexpected exit code %d (stdin mode)", code) } diff --git a/builtins/tests/ip/ip_fuzz_test.go b/builtins/tests/ip/ip_fuzz_test.go index bdd7f891..4173aae6 100644 --- a/builtins/tests/ip/ip_fuzz_test.go +++ b/builtins/tests/ip/ip_fuzz_test.go @@ -129,6 +129,9 @@ func FuzzIPSubcommand(f *testing.F) { f.Add("addr lst dev lo") f.Fuzz(func(t *testing.T, subcmd string) { + if t.Context().Err() != nil { + return + } if len(subcmd) > 1024 { return } @@ -145,12 +148,15 @@ func FuzzIPSubcommand(f *testing.F) { } } - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() // safety net if t.Fatal fires before explicit cancel script := "ip " + subcmd _, _, code := cmdRunCtxFuzz(ctx, t, script) timedOut := ctx.Err() == context.DeadlineExceeded // capture before cancel() cancel() + if t.Context().Err() != nil { + return + } if code == -1 { return // shell/parse error before the builtin ran — not our bug } @@ -203,6 +209,9 @@ func FuzzIPFlags(f *testing.F) { f.Add("--brief", "link show dev lo") f.Fuzz(func(t *testing.T, flags, subcmd string) { + if t.Context().Err() != nil { + return + } if len(flags)+len(subcmd) > 512 { return } @@ -217,7 +226,7 @@ func FuzzIPFlags(f *testing.F) { } } - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() // safety net if t.Fatal fires before explicit cancel script := "ip " + flags if subcmd != "" { @@ -226,6 +235,9 @@ func FuzzIPFlags(f *testing.F) { _, _, code := cmdRunCtxFuzz(ctx, t, script) timedOut := ctx.Err() == context.DeadlineExceeded // capture before cancel() cancel() + if t.Context().Err() != nil { + return + } if code == -1 { return // shell/parse error before the builtin ran — not our bug } diff --git a/builtins/tests/ls/ls_fuzz_test.go b/builtins/tests/ls/ls_fuzz_test.go index 52c43732..acc5c16b 100644 --- a/builtins/tests/ls/ls_fuzz_test.go +++ b/builtins/tests/ls/ls_fuzz_test.go @@ -53,6 +53,9 @@ func FuzzLsFlags(f *testing.F) { var counter atomic.Int64 f.Fuzz(func(t *testing.T, filename string, flagL, flagA, flagR, flagS, flagF bool) { + if t.Context().Err() != nil { + return + } if len(filename) == 0 || len(filename) > 100 { return } @@ -94,10 +97,13 @@ func FuzzLsFlags(f *testing.F) { flags += " -F" } - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() // safety net if t.Fatal fires before explicit cancel _, _, code := cmdRunCtx(ctx, t, "ls"+flags, dir) cancel() + if t.Context().Err() != nil { + return + } if code != 0 && code != 1 { t.Errorf("ls%s unexpected exit code %d", flags, code) } @@ -121,6 +127,9 @@ func FuzzLsRecursive(f *testing.F) { var counter atomic.Int64 f.Fuzz(func(t *testing.T, depth int64) { + if t.Context().Err() != nil { + return + } // Cap at 10 to avoid hitting OS max path length (creating 256+ nested // "sub" directories exceeds filesystem limits on most platforms). // The maxRecursionDepth=256 limit is tested in ls_pentest_test.go instead. @@ -142,10 +151,13 @@ func FuzzLsRecursive(f *testing.F) { current = subdir } - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() // safety net if t.Fatal fires before explicit cancel _, _, code := cmdRunCtx(ctx, t, "ls -R", dir) cancel() + if t.Context().Err() != nil { + return + } if code != 0 && code != 1 { t.Errorf("ls -R unexpected exit code %d", code) } @@ -180,6 +192,9 @@ func FuzzLsHumanReadable(f *testing.F) { var counter atomic.Int64 f.Fuzz(func(t *testing.T, fileSize int64) { + if t.Context().Err() != nil { + return + } // Clamp to 1 MiB to avoid slow file creation. if fileSize < 0 || fileSize > 1<<20 { return @@ -201,10 +216,13 @@ func FuzzLsHumanReadable(f *testing.F) { } fh.Close() - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() // safety net if t.Fatal fires before explicit cancel _, _, code := cmdRunCtx(ctx, t, "ls -lh testfile.bin", dir) cancel() + if t.Context().Err() != nil { + return + } if code != 0 && code != 1 { t.Errorf("ls -lh unexpected exit code %d", code) } @@ -228,6 +246,9 @@ func FuzzLsMultipleFiles(f *testing.F) { var counter atomic.Int64 f.Fuzz(func(t *testing.T, flagL, flagA, flagT, flagS bool) { + if t.Context().Err() != nil { + return + } dir, cleanup := testutil.FuzzIterDir(t, tmpRoot, &counter) defer cleanup() @@ -259,10 +280,13 @@ func FuzzLsMultipleFiles(f *testing.F) { flags += " -S" } - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() // safety net if t.Fatal fires before explicit cancel _, _, code := cmdRunCtx(ctx, t, "ls"+flags, dir) cancel() + if t.Context().Err() != nil { + return + } if code != 0 && code != 1 { t.Errorf("ls%s unexpected exit code %d", flags, code) } diff --git a/builtins/tests/ps/ps_fuzz_test.go b/builtins/tests/ps/ps_fuzz_test.go index 679a8fb7..94b81b00 100644 --- a/builtins/tests/ps/ps_fuzz_test.go +++ b/builtins/tests/ps/ps_fuzz_test.go @@ -69,12 +69,15 @@ func FuzzPSPidList(f *testing.F) { f.Add(" 1 , 2 ") f.Fuzz(func(t *testing.T, pidList string) { + if t.Context().Err() != nil { + return + } // Bound input length to avoid overly long strings. if len(pidList) > 256 { return } - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() parser := syntax.NewParser() @@ -114,6 +117,9 @@ func FuzzPSFlags(f *testing.F) { f.Add("--unknownflag") f.Fuzz(func(t *testing.T, flags string) { + if t.Context().Err() != nil { + return + } if len(flags) > 64 { return } @@ -124,7 +130,7 @@ func FuzzPSFlags(f *testing.F) { } } - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() script := "ps " + flags diff --git a/builtins/tests/ss/ss_fuzz_test.go b/builtins/tests/ss/ss_fuzz_test.go index 7090b157..a75d1852 100644 --- a/builtins/tests/ss/ss_fuzz_test.go +++ b/builtins/tests/ss/ss_fuzz_test.go @@ -98,6 +98,9 @@ func FuzzSSFlags(f *testing.F) { f.Add("-an extra_arg_1 extra_arg_2") f.Fuzz(func(t *testing.T, flags string) { + if t.Context().Err() != nil { + return + } // Cap input length to prevent shell-parse errors from excessively long strings. if len(flags) > 256 { return @@ -138,10 +141,13 @@ func FuzzSSFlags(f *testing.F) { // testutil.RunScriptCtx) shows the input in the CI log. t.Logf("script: %s", script) - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() // safety net if t.Fatal fires before explicit cancel _, _, code := cmdRunCtxFuzzSS(ctx, t, script) cancel() + if t.Context().Err() != nil { + return + } if code != 0 && code != 1 { t.Errorf("unexpected exit code %d for flags %q", code, flags) } diff --git a/builtins/tests/strings_cmd/strings_fuzz_test.go b/builtins/tests/strings_cmd/strings_fuzz_test.go index 9f484544..513f18f0 100644 --- a/builtins/tests/strings_cmd/strings_fuzz_test.go +++ b/builtins/tests/strings_cmd/strings_fuzz_test.go @@ -81,6 +81,9 @@ func FuzzStrings(f *testing.F) { var counter atomic.Int64 f.Fuzz(func(t *testing.T, input []byte) { + if t.Context().Err() != nil { + return + } if len(input) > 1<<20 { return } @@ -92,10 +95,13 @@ func FuzzStrings(f *testing.F) { t.Fatal(err) } - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() // safety net if t.Fatal fires before explicit cancel _, _, code := cmdRunCtx(ctx, t, "strings input.bin", dir) cancel() + if t.Context().Err() != nil { + return + } if code != 0 && code != 1 { t.Errorf("strings unexpected exit code %d", code) } @@ -130,6 +136,9 @@ func FuzzStringsMinLen(f *testing.F) { var counter atomic.Int64 f.Fuzz(func(t *testing.T, input []byte, minLen int64) { + if t.Context().Err() != nil { + return + } if len(input) > 1<<20 { return } @@ -144,10 +153,13 @@ func FuzzStringsMinLen(f *testing.F) { t.Fatal(err) } - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() // safety net if t.Fatal fires before explicit cancel _, _, code := cmdRunCtx(ctx, t, fmt.Sprintf("strings -n %d input.bin", minLen), dir) cancel() + if t.Context().Err() != nil { + return + } if code != 0 && code != 1 { t.Errorf("strings -n %d unexpected exit code %d", minLen, code) } @@ -179,6 +191,9 @@ func FuzzStringsRadix(f *testing.F) { var counter atomic.Int64 f.Fuzz(func(t *testing.T, input []byte, radix string) { + if t.Context().Err() != nil { + return + } // Allow up to 2 MiB so the large-offset corpus seeds (~1 MiB) execute. if len(input) > 2<<20 { return @@ -194,10 +209,13 @@ func FuzzStringsRadix(f *testing.F) { t.Fatal(err) } - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() // safety net if t.Fatal fires before explicit cancel _, _, code := cmdRunCtx(ctx, t, fmt.Sprintf("strings -t %s input.bin", radix), dir) cancel() + if t.Context().Err() != nil { + return + } if code != 0 && code != 1 { t.Errorf("strings -t %s unexpected exit code %d", radix, code) } @@ -221,6 +239,9 @@ func FuzzStringsStdin(f *testing.F) { var counter atomic.Int64 f.Fuzz(func(t *testing.T, input []byte) { + if t.Context().Err() != nil { + return + } if len(input) > 1<<20 { return } @@ -232,10 +253,13 @@ func FuzzStringsStdin(f *testing.F) { t.Fatal(err) } - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() // safety net if t.Fatal fires before explicit cancel _, _, code := cmdRunCtx(ctx, t, "strings < stdin.bin", dir) cancel() + if t.Context().Err() != nil { + return + } if code != 0 && code != 1 { t.Errorf("strings stdin unexpected exit code %d", code) } diff --git a/builtins/tests/tail/tail_differential_fuzz_test.go b/builtins/tests/tail/tail_differential_fuzz_test.go index 09943915..f4873623 100644 --- a/builtins/tests/tail/tail_differential_fuzz_test.go +++ b/builtins/tests/tail/tail_differential_fuzz_test.go @@ -71,6 +71,9 @@ func FuzzTailDifferential(f *testing.F) { f.Add(bytes.Repeat([]byte("line\n"), 20), int64(5)) f.Fuzz(func(t *testing.T, input []byte, n int64) { + if t.Context().Err() != nil { + return + } if len(input) > 64*1024 { return } @@ -85,11 +88,15 @@ func FuzzTailDifferential(f *testing.F) { nStr := fmt.Sprintf("%d", n) - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() // safety net if t.Fatal fires before explicit cancel rshellOut, rshellErr, rshellCode := cmdRunCtx(ctx, t, fmt.Sprintf("tail -n %s input.txt", nStr), dir) cancel() + if t.Context().Err() != nil { + return + } + if isSandboxError(rshellErr) { t.Skip("skipping: sandbox restriction") } diff --git a/builtins/tests/testcmd/testcmd_fuzz_test.go b/builtins/tests/testcmd/testcmd_fuzz_test.go index a051108c..2c306239 100644 --- a/builtins/tests/testcmd/testcmd_fuzz_test.go +++ b/builtins/tests/testcmd/testcmd_fuzz_test.go @@ -59,6 +59,9 @@ func FuzzTestStringOps(f *testing.F) { baseDir := f.TempDir() f.Fuzz(func(t *testing.T, left, right, op string) { + if t.Context().Err() != nil { + return + } if len(left) > 100 || len(right) > 100 { return } @@ -84,11 +87,14 @@ func FuzzTestStringOps(f *testing.F) { return } - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() // safety net if t.Fatal fires before explicit cancel script := fmt.Sprintf("test '%s' %s '%s'", left, op, right) _, _, code := cmdRunCtx(ctx, t, script, baseDir) cancel() + if t.Context().Err() != nil { + return + } if code != 0 && code != 1 && code != 2 { t.Errorf("test string op unexpected exit code %d", code) } @@ -121,6 +127,9 @@ func FuzzTestIntegerOps(f *testing.F) { baseDir := f.TempDir() f.Fuzz(func(t *testing.T, left, right int64, op string) { + if t.Context().Err() != nil { + return + } switch op { case "-eq", "-ne", "-lt", "-le", "-gt", "-ge": default: @@ -131,11 +140,14 @@ func FuzzTestIntegerOps(f *testing.F) { return } - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() // safety net if t.Fatal fires before explicit cancel script := fmt.Sprintf("test %d %s %d", left, op, right) _, _, code := cmdRunCtx(ctx, t, script, baseDir) cancel() + if t.Context().Err() != nil { + return + } if code != 0 && code != 1 && code != 2 { t.Errorf("test %d %s %d unexpected exit code %d", left, op, right, code) } @@ -162,6 +174,9 @@ func FuzzTestFileOps(f *testing.F) { var counter atomic.Int64 f.Fuzz(func(t *testing.T, op string, createFile bool) { + if t.Context().Err() != nil { + return + } switch op { case "-e", "-f", "-d", "-s", "-r", "-w", "-x", "-h", "-L", "-p": default: @@ -182,11 +197,14 @@ func FuzzTestFileOps(f *testing.F) { } } - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() // safety net if t.Fatal fires before explicit cancel script := fmt.Sprintf("test %s %s", op, target) _, _, code := cmdRunCtx(ctx, t, script, dir) cancel() + if t.Context().Err() != nil { + return + } if code != 0 && code != 1 && code != 2 { t.Errorf("test %s unexpected exit code %d", op, code) } @@ -216,6 +234,9 @@ func FuzzTestStringUnary(f *testing.F) { baseDir := f.TempDir() f.Fuzz(func(t *testing.T, arg, op string) { + if t.Context().Err() != nil { + return + } if len(arg) > 200 { return } @@ -235,11 +256,14 @@ func FuzzTestStringUnary(f *testing.F) { } } - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() // safety net if t.Fatal fires before explicit cancel script := fmt.Sprintf("test %s '%s'", op, arg) _, _, code := cmdRunCtx(ctx, t, script, baseDir) cancel() + if t.Context().Err() != nil { + return + } if code != 0 && code != 1 && code != 2 { t.Errorf("test %s unexpected exit code %d", op, code) } @@ -280,6 +304,9 @@ func FuzzTestNesting(f *testing.F) { baseDir := f.TempDir() f.Fuzz(func(t *testing.T, expr string) { + if t.Context().Err() != nil { + return + } // Keep expressions short to avoid slow evaluation on CI where // fuzz workers have limited CPU; long expressions with many // tokens can cause the shell interpreter to exceed the @@ -321,11 +348,14 @@ func FuzzTestNesting(f *testing.F) { } } - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() // safety net if t.Fatal fires before explicit cancel script := fmt.Sprintf("test %s", expr) _, _, code := cmdRunCtx(ctx, t, script, baseDir) cancel() + if t.Context().Err() != nil { + return + } if code != 0 && code != 1 && code != 2 { t.Errorf("test %q unexpected exit code %d", expr, code) } diff --git a/builtins/tests/uniq/uniq_fuzz_test.go b/builtins/tests/uniq/uniq_fuzz_test.go index c2568ad5..701f27c5 100644 --- a/builtins/tests/uniq/uniq_fuzz_test.go +++ b/builtins/tests/uniq/uniq_fuzz_test.go @@ -64,6 +64,9 @@ func FuzzUniq(f *testing.F) { var counter atomic.Int64 f.Fuzz(func(t *testing.T, input []byte) { + if t.Context().Err() != nil { + return + } if len(input) > 1<<20 { return } @@ -75,10 +78,13 @@ func FuzzUniq(f *testing.F) { t.Fatal(err) } - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() // safety net if t.Fatal fires before explicit cancel _, _, code := fuzzRunCtx(ctx, t, "uniq input.txt", dir) cancel() + if t.Context().Err() != nil { + return + } if code != 0 && code != 1 { t.Errorf("uniq unexpected exit code %d", code) } @@ -103,6 +109,9 @@ func FuzzUniqCount(f *testing.F) { var counter atomic.Int64 f.Fuzz(func(t *testing.T, input []byte) { + if t.Context().Err() != nil { + return + } if len(input) > 1<<20 { return } @@ -114,10 +123,13 @@ func FuzzUniqCount(f *testing.F) { t.Fatal(err) } - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() // safety net if t.Fatal fires before explicit cancel _, _, code := fuzzRunCtx(ctx, t, "uniq -c input.txt", dir) cancel() + if t.Context().Err() != nil { + return + } if code != 0 && code != 1 { t.Errorf("uniq -c unexpected exit code %d", code) } @@ -151,6 +163,9 @@ func FuzzUniqFlags(f *testing.F) { var counter atomic.Int64 f.Fuzz(func(t *testing.T, input []byte, repeated, ignoreCase, unique, nulDelim bool, skipFields, skipChars, checkChars int64) { + if t.Context().Err() != nil { + return + } if len(input) > 1<<20 { return } @@ -194,10 +209,13 @@ func FuzzUniqFlags(f *testing.F) { flags += fmt.Sprintf(" -w %d", checkChars) } - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() // safety net if t.Fatal fires before explicit cancel _, _, code := fuzzRunCtx(ctx, t, "uniq"+flags+" input.txt", dir) cancel() + if t.Context().Err() != nil { + return + } if code != 0 && code != 1 { t.Errorf("uniq%s unexpected exit code %d", flags, code) } @@ -216,6 +234,9 @@ func FuzzUniqStdin(f *testing.F) { var counter atomic.Int64 f.Fuzz(func(t *testing.T, input []byte) { + if t.Context().Err() != nil { + return + } if len(input) > 1<<20 { return } @@ -227,10 +248,13 @@ func FuzzUniqStdin(f *testing.F) { t.Fatal(err) } - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() // safety net if t.Fatal fires before explicit cancel _, _, code := fuzzRunCtx(ctx, t, "uniq < stdin.txt", dir) cancel() + if t.Context().Err() != nil { + return + } if code != 0 && code != 1 { t.Errorf("uniq stdin unexpected exit code %d", code) } diff --git a/builtins/tests/wc/wc_fuzz_test.go b/builtins/tests/wc/wc_fuzz_test.go index 70ea6039..ec50880a 100644 --- a/builtins/tests/wc/wc_fuzz_test.go +++ b/builtins/tests/wc/wc_fuzz_test.go @@ -58,6 +58,9 @@ func FuzzWc(f *testing.F) { var counter atomic.Int64 f.Fuzz(func(t *testing.T, input []byte) { + if t.Context().Err() != nil { + return + } if len(input) > 1<<20 { return } @@ -70,10 +73,13 @@ func FuzzWc(f *testing.F) { t.Fatal(err) } - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() // safety net if t.Fatal fires before explicit cancel _, _, code := cmdRunCtx(ctx, t, "wc input.txt", dir) cancel() + if t.Context().Err() != nil { + return + } if code != 0 && code != 1 { t.Errorf("wc unexpected exit code %d", code) } @@ -96,6 +102,9 @@ func FuzzWcLines(f *testing.F) { var counter atomic.Int64 f.Fuzz(func(t *testing.T, input []byte) { + if t.Context().Err() != nil { + return + } if len(input) > 1<<20 { return } @@ -108,10 +117,13 @@ func FuzzWcLines(f *testing.F) { t.Fatal(err) } - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() // safety net if t.Fatal fires before explicit cancel _, _, code := cmdRunCtx(ctx, t, "wc -l input.txt", dir) cancel() + if t.Context().Err() != nil { + return + } if code != 0 && code != 1 { t.Errorf("wc -l unexpected exit code %d", code) } @@ -132,6 +144,9 @@ func FuzzWcBytes(f *testing.F) { var counter atomic.Int64 f.Fuzz(func(t *testing.T, input []byte) { + if t.Context().Err() != nil { + return + } if len(input) > 1<<20 { return } @@ -144,10 +159,13 @@ func FuzzWcBytes(f *testing.F) { t.Fatal(err) } - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() // safety net if t.Fatal fires before explicit cancel _, _, code := cmdRunCtx(ctx, t, "wc -c input.txt", dir) cancel() + if t.Context().Err() != nil { + return + } if code != 0 && code != 1 { t.Errorf("wc -c unexpected exit code %d", code) } @@ -175,6 +193,9 @@ func FuzzWcChars(f *testing.F) { var counter atomic.Int64 f.Fuzz(func(t *testing.T, input []byte) { + if t.Context().Err() != nil { + return + } if len(input) > 1<<20 { return } @@ -187,10 +208,13 @@ func FuzzWcChars(f *testing.F) { t.Fatal(err) } - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() // safety net if t.Fatal fires before explicit cancel _, _, code := cmdRunCtx(ctx, t, "wc -m input.txt", dir) cancel() + if t.Context().Err() != nil { + return + } if code != 0 && code != 1 { t.Errorf("wc -m unexpected exit code %d", code) } @@ -212,6 +236,9 @@ func FuzzWcStdin(f *testing.F) { var counter atomic.Int64 f.Fuzz(func(t *testing.T, input []byte) { + if t.Context().Err() != nil { + return + } // Keep the limit modest: stdin redirection goes through the full // shell interpreter pipeline, which is slower than direct file // reads. A 64 KiB cap prevents CI timeouts on resource-constrained @@ -228,10 +255,13 @@ func FuzzWcStdin(f *testing.F) { t.Fatal(err) } - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() // safety net if t.Fatal fires before explicit cancel _, _, code := cmdRunCtx(ctx, t, "wc < stdin.txt", dir) cancel() + if t.Context().Err() != nil { + return + } if code != 0 && code != 1 { t.Errorf("wc stdin unexpected exit code %d", code) } diff --git a/interp/tests/cmdsubst_fuzz_test.go b/interp/tests/cmdsubst_fuzz_test.go index a6f82213..264490d4 100644 --- a/interp/tests/cmdsubst_fuzz_test.go +++ b/interp/tests/cmdsubst_fuzz_test.go @@ -26,6 +26,9 @@ func FuzzCmdSubstEcho(f *testing.F) { f.Add("日本語") f.Fuzz(func(t *testing.T, arg string) { + if t.Context().Err() != nil { + return + } if len(arg) > 1000 { return } @@ -43,10 +46,13 @@ func FuzzCmdSubstEcho(f *testing.F) { } dir := t.TempDir() - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() _, _, code := cmdSubstRunCtx(ctx, t, `x=$(echo '`+arg+`'); echo "$x"`, dir) + if t.Context().Err() != nil { + return + } if code != 0 && code != 1 { t.Errorf("unexpected exit code %d for arg %q", code, arg) } @@ -60,6 +66,9 @@ func FuzzCmdSubstNested(f *testing.F) { f.Add("") f.Fuzz(func(t *testing.T, arg string) { + if t.Context().Err() != nil { + return + } if len(arg) > 500 { return } @@ -83,10 +92,13 @@ func FuzzCmdSubstNested(f *testing.F) { } dir := t.TempDir() - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() _, _, code := cmdSubstRunCtx(ctx, t, `echo $(echo '`+arg+`')`, dir) + if t.Context().Err() != nil { + return + } if code != 0 && code != 1 { t.Errorf("unexpected exit code %d for arg %q", code, arg) } @@ -101,6 +113,9 @@ func FuzzSubshellCommands(f *testing.F) { f.Add("hello world") f.Fuzz(func(t *testing.T, arg string) { + if t.Context().Err() != nil { + return + } if len(arg) > 500 { return } @@ -117,10 +132,13 @@ func FuzzSubshellCommands(f *testing.F) { } dir := t.TempDir() - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) defer cancel() _, _, code := subshellRunCtx(ctx, t, `(echo '`+arg+`')`, dir) + if t.Context().Err() != nil { + return + } if code != 0 && code != 1 { t.Errorf("unexpected exit code %d for arg %q", code, arg) }