package pty import ( "bytes" "errors" "os" "path/filepath" "strconv" "strings" "syscall" "testing" "time" ) func TestStartUsesWorkDir(t *testing.T) { dir := t.TempDir() p, err := Start([]string{"sh", "-c", "pwd"}, nil, dir, 80, 24) if err != nil { t.Fatalf("Start: %v", err) } defer p.Close() var out bytes.Buffer buf := make([]byte, 256) deadline := time.Now().Add(5 * time.Second) for time.Now().Before(deadline) { n, err := p.Read(buf) if n > 0 { out.Write(buf[:n]) if strings.Contains(out.String(), dir) { break } } if err != nil { break } } _ = p.Wait() if got := strings.TrimSpace(out.String()); got != dir { t.Fatalf("pwd output = %q, want %q", got, dir) } } func TestCloseKillsProcessGroup(t *testing.T) { dir := t.TempDir() pidFile := filepath.Join(dir, "sleep.pid") env := append(os.Environ(), "PIDFILE="+pidFile) p, err := Start([]string{"sh", "-c", "sleep 30 & echo $! > \"$PIDFILE\"; wait"}, env, "", 80, 24) if err != nil { t.Fatalf("Start: %v", err) } deadline := time.Now().Add(5 * time.Second) var childPID int for time.Now().Before(deadline) { b, err := os.ReadFile(pidFile) if err == nil { childPID, _ = strconv.Atoi(strings.TrimSpace(string(b))) if childPID > 0 { break } } time.Sleep(20 * time.Millisecond) } if childPID <= 0 { _ = p.Close() t.Fatalf("background child pid was not written") } if err := p.Close(); err != nil { t.Fatalf("Close: %v", err) } _ = p.Wait() deadline = time.Now().Add(5 * time.Second) for time.Now().Before(deadline) { err := syscall.Kill(childPID, 0) if errors.Is(err, syscall.ESRCH) { return } time.Sleep(20 * time.Millisecond) } t.Fatalf("background child pid %d still exists after PTY.Close", childPID) }