test: make task1 bounds oracles exact

This commit is contained in:
2026-08-11 02:06:49 +02:00
parent 2f34a37d13
commit 3564817409
2 changed files with 231 additions and 40 deletions
+113 -17
View File
@@ -3,6 +3,7 @@ package main
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
@@ -135,37 +136,127 @@ func TestRunWorkspacePublicDispatchExitMatrix(t *testing.T) {
func TestRunWorkspaceBoundsFinalJSONEncoding(t *testing.T) {
fixture := newCLIFixture(t, "")
fixture.setEnvironment(t)
result := fmt.Sprintf(`{"schemaVersion":1,"status":"succeeded","code":"ok","workspaceId":"psd","workspaceRevision":"%s","descriptorBlob":"%s","operation":"inspect","completedStages":[],"warnings":[%q]}`, strings.Repeat("0", 40), strings.Repeat("a", 40), strings.Repeat(`"`, 524000))
t.Setenv("THOTHCTL_FAKE_WORKSPACE_RESULT", result)
var stdout, stderr bytes.Buffer
if code := run(context.Background(), []string{"--installation", fixture.installationPath, "workspace", "inspect", "--workspace", "psd", "--json"}, &stdout, &stderr); code != 1 {
t.Fatalf("exit = %d, want operational failure 1", code)
}
if stdout.Len() > 1<<20 {
t.Fatalf("stdout length = %d, exceeds 1 MiB", stdout.Len())
for _, tc := range []struct {
name string
extraBytes int
wantCode int
}{
{name: "exact final limit", wantCode: 0},
{name: "one encoded byte over", extraBytes: 1, wantCode: 1},
} {
t.Run(tc.name, func(t *testing.T) {
payload, encodedLength := boundedWorkspaceResultPayload(t, (1<<20)+tc.extraBytes)
if len(payload) >= 1<<20 {
t.Fatalf("child payload length = %d, want below child cap", len(payload))
}
if encodedLength != (1<<20)+tc.extraBytes {
t.Fatalf("final encoded length = %d, want %d", encodedLength, (1<<20)+tc.extraBytes)
}
writeWorkspaceResultFile(t, fixture, payload)
var stdout, stderr bytes.Buffer
code := run(context.Background(), []string{"--installation", fixture.installationPath, "workspace", "inspect", "--workspace", "psd", "--json"}, &stdout, &stderr)
if code != tc.wantCode {
t.Fatalf("exit = %d, want %d (stdout=%d stderr=%q)", code, tc.wantCode, stdout.Len(), stderr.String())
}
if tc.extraBytes == 0 {
if stdout.Len() != 1<<20 || stderr.Len() != 0 {
t.Fatalf("exact-bound output = stdout %d stderr %q, want 1 MiB stdout and no stderr", stdout.Len(), stderr.String())
}
} else if stdout.Len() != 0 || stderr.String() != "thothctl: workspace result exceeds output limit\n" {
t.Fatalf("over-bound output = stdout %d stderr %q", stdout.Len(), stderr.String())
}
})
}
}
func TestRunWorkspaceBoundsFinalHumanEncoding(t *testing.T) {
fixture := newCLIFixture(t, "")
fixture.setEnvironment(t)
result := fmt.Sprintf(`{"schemaVersion":1,"status":"succeeded","code":"ok","workspaceId":"psd","workspaceRevision":"%s","descriptorBlob":"%s","operation":"inspect","completedStages":[%q]}`, strings.Repeat("0", 40), strings.Repeat("a", 40), strings.Repeat("x", (1<<20)-100))
t.Setenv("THOTHCTL_FAKE_WORKSPACE_RESULT", result)
payload := encodeWorkspaceResultForTest(workspaceops.Result{
SchemaVersion: 1, Status: "succeeded", Code: "ok", WorkspaceID: "psd",
WorkspaceRevision: strings.Repeat("0", 40), DescriptorBlob: strings.Repeat("a", 40),
Operation: "inspect", CompletedStages: []string{},
})
writeWorkspaceResultFile(t, fixture, payload)
var stdout, stderr bytes.Buffer
_ = run(context.Background(), []string{"--installation", fixture.installationPath, "workspace", "inspect", "--workspace", "psd"}, &stdout, &stderr)
if stdout.Len() > 1<<20 {
t.Fatalf("stdout length = %d, exceeds 1 MiB", stdout.Len())
code := run(context.Background(), []string{"--installation", fixture.installationPath, "workspace", "inspect", "--workspace", "psd"}, &stdout, &stderr)
if code != 0 || stderr.Len() != 0 {
t.Fatalf("successful child result was not rendered: exit=%d stderr=%q", code, stderr.String())
}
if !strings.HasPrefix(stdout.String(), "Workspace psd: succeeded (ok)\n") {
t.Fatalf("human output = %q, want successful workspace rendering", stdout.String())
}
// A human rendering can only be larger than the child JSON when it is supplied directly;
// exercise the same bounded public writer without making the child hit its 1 MiB cap.
large := workspaceops.Result{
SchemaVersion: 1, Status: "succeeded", Code: "ok", WorkspaceID: "psd",
WorkspaceRevision: strings.Repeat("0", 40), DescriptorBlob: strings.Repeat("a", 40),
Operation: "inspect", CompletedStages: []string{strings.Repeat("x", 1<<20)},
}
var bounded bytes.Buffer
renderWorkspaceHuman(&boundedWriter{dst: &bounded, maximum: 1 << 20}, large)
if bounded.Len() != 1<<20 {
t.Fatalf("bounded human output length = %d, want exactly 1 MiB", bounded.Len())
}
}
func TestRunBoundsParseErrorStderr(t *testing.T) {
fixture := newCLIFixture(t, "")
fixture.setEnvironment(t)
unknownAction := strings.Repeat("x", 70<<10)
var stdout, stderr bytes.Buffer
_ = run(context.Background(), []string{"--installation", fixture.installationPath, "workspace", "inspect", "--workspace", "psd", "--unknown", strings.Repeat("x", 70<<10)}, &stdout, &stderr)
if stderr.Len() > 64<<10 {
t.Fatalf("stderr length = %d, exceeds 64 KiB", stderr.Len())
code := run(context.Background(), []string{"--installation", fixture.installationPath, "workspace", unknownAction, "--workspace", "psd"}, &stdout, &stderr)
if code != 2 {
t.Fatalf("exit = %d, want usage exit 2", code)
}
if stdout.Len() != 0 || stderr.Len() != 64<<10 {
t.Fatalf("bounded parse error = stdout %d stderr %d, want 0 and exactly 64 KiB", stdout.Len(), stderr.Len())
}
if !strings.HasPrefix(stderr.String(), "thothctl: unknown workspace command: workspace ") || !strings.HasSuffix(stderr.String(), "x") {
t.Fatalf("parse error was not the truncated unknown action: prefix/suffix mismatch")
}
}
func encodeWorkspaceResultForTest(result workspaceops.Result) []byte {
var encoded bytes.Buffer
encoder := json.NewEncoder(&encoded)
encoder.SetEscapeHTML(false)
if err := encoder.Encode(result); err != nil {
panic(err)
}
return encoded.Bytes()
}
func boundedWorkspaceResultPayload(t *testing.T, finalLength int) ([]byte, int) {
t.Helper()
result := workspaceops.Result{
SchemaVersion: 1, Status: "succeeded", Code: "ok", WorkspaceID: "psd",
WorkspaceRevision: strings.Repeat("0", 40), DescriptorBlob: strings.Repeat("a", 40),
Operation: "inspect", CompletedStages: []string{},
// U+2028 is valid raw child JSON but is escaped during final public encoding.
Warnings: []string{strings.Repeat("\u2028", 1000)},
}
encoded := encodeWorkspaceResultForTest(result)
if len(encoded) >= finalLength {
t.Fatalf("base encoded result length = %d, cannot reach target %d", len(encoded), finalLength)
}
result.Warnings[0] += strings.Repeat("x", finalLength-len(encoded))
encoded = encodeWorkspaceResultForTest(result)
if len(encoded) != finalLength {
t.Fatalf("final encoded result length = %d, want %d", len(encoded), finalLength)
}
child := bytes.ReplaceAll(encoded, []byte(`\u2028`), []byte("\u2028"))
return child, len(encoded)
}
func writeWorkspaceResultFile(t *testing.T, fixture cliFixture, payload []byte) {
t.Helper()
path := filepath.Join(fixture.root, "workspace-result.json")
if err := os.WriteFile(path, payload, 0o600); err != nil {
t.Fatal(err)
}
t.Setenv("THOTHCTL_FAKE_WORKSPACE_RESULT_FILE", path)
}
func TestRunWorkspaceOperationalFailureExitsOne(t *testing.T) {
@@ -840,7 +931,11 @@ case " $* " in
printf '%s\n' '{"volumes":{"settings":{}},"services":{"core":{"image":"thothii-core:local","environment":{"THT_LLM_URL":"https://llm.example.invalid"}}}}'
fi ;;
*" run --rm --no-deps --no-TTY workspace-maintenance "*)
printf '%s\n' "${THOTHCTL_FAKE_WORKSPACE_RESULT:-}"
workspace_result="${THOTHCTL_FAKE_WORKSPACE_RESULT:-}"
if [ -n "${THOTHCTL_FAKE_WORKSPACE_RESULT_FILE:-}" ]; then
workspace_result=$(/bin/cat "$THOTHCTL_FAKE_WORKSPACE_RESULT_FILE")
fi
printf '%s\n' "$workspace_result"
exit "${THOTHCTL_FAKE_WORKSPACE_EXIT:-0}" ;;
*" run --rm --no-deps --no-TTY session-migrate "*)
if [ "${THOTHCTL_FAKE_MIGRATION_EXIT:-0}" -ne 0 ]; then
@@ -898,6 +993,7 @@ func (f cliFixture) setEnvContents(t *testing.T, env string) {
t.Setenv("THOTHCTL_FAKE_MIGRATION_FAILURE", "")
t.Setenv("THOTHCTL_FAKE_MIGRATION_EXIT", "0")
t.Setenv("THOTHCTL_FAKE_WORKSPACE_RESULT", "")
t.Setenv("THOTHCTL_FAKE_WORKSPACE_RESULT_FILE", "")
t.Setenv("THOTHCTL_FAKE_WORKSPACE_EXIT", "0")
}
@@ -7,7 +7,8 @@ import (
"errors"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"testing"
"time"
@@ -32,72 +33,166 @@ func TestOwnedJobPolicyKillsDescendantsOnClose(t *testing.T) {
}
// TestWindowsProcessHelper is both a test helper and the child process used by the runtime
// ownership regression below. The child writes a marker only if it outlives its parent job.
// ownership regression below. The grandchild records its PID before the parent can exit. The
// parent then either exits, blocks for cancellation, or overflows stdout.
func TestWindowsProcessHelper(t *testing.T) {
mode := os.Getenv("THOTHCTL_WINDOWS_HELPER")
if mode == "" {
return
}
marker := os.Getenv("THOTHCTL_WINDOWS_MARKER")
if mode == "child" {
time.Sleep(2 * time.Second)
_ = os.WriteFile(marker, []byte("survived"), 0o600)
return
pidFile := os.Getenv("THOTHCTL_WINDOWS_PID_FILE")
releaseFile := os.Getenv("THOTHCTL_WINDOWS_RELEASE_FILE")
if mode == "grandchild" {
if err := os.WriteFile(pidFile, []byte(strconv.Itoa(os.Getpid())), 0o600); err != nil {
t.Fatal(err)
}
for {
time.Sleep(time.Hour)
}
}
child := exec.Command(os.Args[0], "-test.run=TestWindowsProcessHelper", "--")
child.Env = append(os.Environ(), "THOTHCTL_WINDOWS_HELPER=child", "THOTHCTL_WINDOWS_MARKER="+marker)
child.Env = append(os.Environ(),
"THOTHCTL_WINDOWS_HELPER=grandchild",
"THOTHCTL_WINDOWS_PID_FILE="+pidFile,
)
if err := child.Start(); err != nil {
t.Fatal(err)
}
waitForWindowsPIDFile(t, pidFile)
waitForWindowsReleaseFile(t, releaseFile)
if mode == "exit" {
return
}
if mode == "cancel" {
for {
time.Sleep(time.Second)
time.Sleep(time.Hour)
}
}
for {
_, _ = os.Stdout.Write([]byte("overflow\n"))
}
}
func waitForWindowsPIDFile(t *testing.T, path string) {
t.Helper()
deadline := time.Now().Add(5 * time.Second)
for time.Now().Before(deadline) {
contents, err := os.ReadFile(path)
if err == nil {
pid, parseErr := strconv.Atoi(strings.TrimSpace(string(contents)))
if parseErr == nil && pid > 0 {
return
}
}
time.Sleep(time.Millisecond)
}
t.Fatalf("grandchild PID was not recorded in %s", path)
}
func waitForWindowsReleaseFile(t *testing.T, path string) {
t.Helper()
deadline := time.Now().Add(5 * time.Second)
for time.Now().Before(deadline) {
if _, err := os.Stat(path); err == nil {
return
}
time.Sleep(time.Millisecond)
}
t.Fatalf("test did not release Windows helper via %s", path)
}
func openWindowsProcessForTest(t *testing.T, pid int) windows.Handle {
t.Helper()
const access = windows.SYNCHRONIZE | windows.PROCESS_QUERY_LIMITED_INFORMATION
handle, err := windows.OpenProcess(access, false, uint32(pid))
if err != nil {
t.Fatalf("OpenProcess(%d): %v", pid, err)
}
return handle
}
func assertWindowsProcessTerminated(t *testing.T, handle windows.Handle, pid int) {
t.Helper()
const stillActive = 259
status, err := windows.WaitForSingleObject(handle, uint32((5*time.Second)/time.Millisecond))
if err != nil {
t.Fatalf("WaitForSingleObject(%d): %v", pid, err)
}
if status != windows.WAIT_OBJECT_0 {
t.Fatalf("WaitForSingleObject(%d) = %#x, want signaled terminated process", pid, status)
}
var exitCode uint32
if err := windows.GetExitCodeProcess(handle, &exitCode); err != nil {
t.Fatalf("GetExitCodeProcess(%d): %v", pid, err)
}
if exitCode == stillActive {
t.Fatalf("GetExitCodeProcess(%d) still reports STILL_ACTIVE", pid)
}
}
func TestOwnedProcessTerminatesDescendantOnOverflowCancelAndParentExit(t *testing.T) {
for _, mode := range []string{"overflow", "cancel", "exit"} {
t.Run(mode, func(t *testing.T) {
marker := filepath.Join(t.TempDir(), "descendant-marker")
pidFile, err := os.CreateTemp("", "thothctl-windows-grandchild-*.pid")
if err != nil {
t.Fatal(err)
}
pidPath := pidFile.Name()
_ = pidFile.Close()
_ = os.Remove(pidPath)
defer os.Remove(pidPath)
releaseFile, err := os.CreateTemp("", "thothctl-windows-release-*")
if err != nil {
t.Fatal(err)
}
releasePath := releaseFile.Name()
_ = releaseFile.Close()
_ = os.Remove(releasePath)
defer os.Remove(releasePath)
t.Setenv("THOTHCTL_WINDOWS_HELPER", mode)
t.Setenv("THOTHCTL_WINDOWS_MARKER", marker)
t.Setenv("THOTHCTL_WINDOWS_PID_FILE", pidPath)
t.Setenv("THOTHCTL_WINDOWS_RELEASE_FILE", releasePath)
ctx := context.Background()
var cancel context.CancelFunc
if mode == "cancel" {
ctx, cancel = context.WithCancel(ctx)
defer cancel()
}
result := make(chan error, 1)
go func() {
_, err := runBoundedWindows(ctx, os.Args[0], []string{"-test.run=TestWindowsProcessHelper", "--"}, nil, CaptureLimits{StdoutBytes: 1024, StderrBytes: 1024})
result <- err
_, runErr := runBoundedWindows(ctx, os.Args[0], []string{"-test.run=TestWindowsProcessHelper", "--"}, nil, CaptureLimits{StdoutBytes: 1024, StderrBytes: 1024})
result <- runErr
}()
waitForWindowsPIDFile(t, pidPath)
contents, err := os.ReadFile(pidPath)
if err != nil {
t.Fatal(err)
}
pid, err := strconv.Atoi(strings.TrimSpace(string(contents)))
if err != nil {
t.Fatal(err)
}
handle := openWindowsProcessForTest(t, pid)
defer windows.CloseHandle(handle)
if err := os.WriteFile(releasePath, []byte("release"), 0o600); err != nil {
t.Fatal(err)
}
if mode == "cancel" {
time.Sleep(100 * time.Millisecond)
cancel()
}
select {
case err := <-result:
if mode == "overflow" && !errors.Is(err, ErrOutputLimit) {
t.Fatalf("overflow error = %v, want ErrOutputLimit", err)
case runErr := <-result:
if mode == "overflow" && !errors.Is(runErr, ErrOutputLimit) {
t.Fatalf("overflow error = %v, want ErrOutputLimit", runErr)
}
if mode == "cancel" && !errors.Is(err, context.Canceled) {
t.Fatalf("cancel error = %v, want context.Canceled", err)
if mode == "cancel" && !errors.Is(runErr, context.Canceled) {
t.Fatalf("cancel error = %v, want context.Canceled", runErr)
}
case <-time.After(5 * time.Second):
t.Fatal("owned process did not terminate")
}
time.Sleep(150 * time.Millisecond)
if _, err := os.Stat(marker); !errors.Is(err, os.ErrNotExist) {
t.Fatalf("descendant marker error = %v; descendant survived %s termination", err, mode)
}
assertWindowsProcessTerminated(t, handle, pid)
})
}
}