| // Copyright 2026 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package script |
| |
| import ( |
| "context" |
| "fmt" |
| "internal/testenv" |
| "io" |
| "os" |
| "os/exec" |
| "runtime" |
| "slices" |
| "strings" |
| "testing" |
| "time" |
| ) |
| |
| func TestInterruptCmd(t *testing.T) { |
| if runtime.GOOS == "js" || runtime.GOOS == "wasip1" { |
| t.Skip(runtime.GOOS + " does not support executables") |
| } |
| |
| const msg = "Hello world\n" |
| |
| if os.Getenv("GO_WANT_HELPER_PROCESS") == "1" { |
| fmt.Printf(msg) |
| time.Sleep(24 * time.Hour) // sleep forever |
| os.Exit(3) |
| } |
| |
| exe := testenv.Executable(t) |
| cmd := testenv.CommandContext(t, t.Context(), exe, "-test.run=^TestInterruptCmd$") |
| cmd = testenv.CleanCmdEnv(cmd) |
| cmd.Env = append(cmd.Env, "GO_WANT_HELPER_PROCESS=1") |
| |
| stdout, err := cmd.StdoutPipe() |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| if err := cmd.Start(); err != nil { |
| t.Fatal(err) |
| } |
| |
| buf := make([]byte, len(msg)) |
| n, err := io.ReadFull(stdout, buf) |
| if n != len(buf) || err != nil || string(buf) != msg { |
| t.Fatalf("ReadFull = %d, %v, %q", n, err, buf[:n]) |
| } |
| |
| interruptCmdErr := make(chan error) |
| go func() { |
| interruptCmdErr <- InterruptCmd(cmd) |
| }() |
| |
| err = cmd.Wait() |
| if err == nil { |
| t.Fatal("expected Wait failure") |
| } else if err, ok := err.(*exec.ExitError); ok { |
| checkInterruptCmdError(t, err) |
| } else { |
| t.Fatalf("unexpected error while running executable: %s\n%s", err, string(buf)) |
| } |
| |
| err = <-interruptCmdErr |
| if err != nil { |
| t.Errorf("InterruptCmd failed: %v", err) |
| } |
| } |
| |
| // checkInterruptCmdError calls t.Fatal if err is interrupt cmd error. |
| func checkInterruptCmdError(t *testing.T, err error) { |
| errstr := err.Error() |
| if runtime.GOOS == "plan9" { |
| expectedPrefixError := "exit status: " |
| if !strings.HasPrefix(errstr, expectedPrefixError) { |
| t.Fatalf("unexpected error prefix while exiting executable: got=%q, want=%q", errstr, expectedPrefixError) |
| } |
| expectedSuffixError := ": interrupt'" |
| if !strings.HasSuffix(errstr, expectedSuffixError) { |
| t.Fatalf("unexpected error suffix while exiting executable: got=%q, want=%q", errstr, expectedSuffixError) |
| } |
| return |
| } |
| |
| expectedError := "signal: interrupt" |
| if runtime.GOOS == "windows" { |
| expectedError = "exit status 1" |
| } |
| if errstr != expectedError { |
| t.Fatalf("unexpected error while exiting executable: got=%q, want=%q", errstr, expectedError) |
| } |
| } |
| |
| func FuzzQuoteArgs(f *testing.F) { |
| state, err := NewState(context.Background(), f.TempDir(), nil /* env */) |
| if err != nil { |
| f.Fatalf("failed to create state: %v", err) |
| } |
| |
| f.Add("foo") |
| f.Add(`"foo"`) |
| f.Add(`'foo'`) |
| f.Fuzz(func(t *testing.T, s string) { |
| give := []string{s} |
| quoted := quoteArgs(give) |
| cmd, err := parse("file.txt", 42, "cmd "+quoted) |
| if err != nil { |
| t.Fatalf("quoteArgs(%q) = %q cannot be parsed: %v", give, quoted, err) |
| } |
| args := expandArgs(state, cmd.rawArgs, nil /* regexpArgs */) |
| |
| if !slices.Equal(give, args) { |
| t.Fatalf("quoteArgs failed to round-trip.\ninput:\n\t%#q\nquoted:\n\t%q\nparsed:\n\t%#q", give, quoted, args) |
| } |
| }) |
| } |