diff options
| author | Paul Buetow <paul@buetow.org> | 2026-03-28 11:40:59 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-03-28 11:40:59 +0200 |
| commit | 118170f8f3735a9573a2a72b83d6aa2af63be85a (patch) | |
| tree | 269f3eabb37bb624799b0ca2a7fe545f83451878 | |
| parent | 2c1930adf4355b7d0b113f3a2f9573c4f961420e (diff) | |
fix: prevent integration test task leaks and fix cleanup reliability
Two related issues with the integration test cleanup:
1. deleteTask used the test's context, which could be cancelled if the
test had already timed out — causing deferred cleanup to silently fail
and leak Taskwarrior tasks across runs. Fixed by using a fresh
background context with a short timeout inside deleteTask.
2. No mechanism existed to clean up tasks leaked by previous runs, which
could accumulate and pollute subsequent test runs. Added
cleanupOrphanedIntegrationTasks() called from TestMain that bulk-deletes
all project:hexai +integrationtest pending tasks using taskwarrior's
"all" confirmation answer for efficiency.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
| -rw-r--r-- | integrationtests/ask_test.go | 38 |
1 files changed, 36 insertions, 2 deletions
diff --git a/integrationtests/ask_test.go b/integrationtests/ask_test.go index 483dc7c..0ce36fe 100644 --- a/integrationtests/ask_test.go +++ b/integrationtests/ask_test.go @@ -147,8 +147,15 @@ func extractTaskIDFromAddOutput(output string) string { return strings.TrimSpace(output) } -func deleteTask(ctx context.Context, uuid string) { - runTaskWithStdin(ctx, []string{"uuid:" + uuid, "delete"}, "yes\n") +// deleteTask removes the task identified by uuid from Taskwarrior. It always +// uses a fresh background context with a short timeout so that deferred cleanup +// calls succeed even when the calling test's context has already been cancelled +// (e.g. after a timeout). The ctx parameter is accepted for backwards +// compatibility but intentionally ignored. +func deleteTask(_ context.Context, uuid string) { + cleanupCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + runTaskWithStdin(cleanupCtx, []string{"uuid:" + uuid, "delete"}, "yes\n") } func listTasksWithTag(ctx context.Context, tag string) []askcli.TaskExport { @@ -268,6 +275,30 @@ func aliasCachePath(t *testing.T, cacheRoot string) string { return filepath.Join(cacheRoot, "hexai", "ask", "task-aliases-v1.json") } +// cleanupOrphanedIntegrationTasks deletes any tasks with the +integrationtest +// tag that were left behind by previous test runs (e.g. when a test timed out +// before its deferred deleteTask could complete, or when the process was +// killed). Running this at the start of TestMain keeps the Taskwarrior +// database clean and prevents orphaned tasks from polluting subsequent runs. +// +// A bulk deletion approach is used to handle large numbers of orphaned tasks +// efficiently: taskwarrior's "all" confirmation answer deletes all matching +// tasks in a single invocation rather than one call per task. +func cleanupOrphanedIntegrationTasks() { + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + // "all" as stdin answers taskwarrior's per-task confirmation prompts with + // "delete all matching tasks", so the entire set is removed in one shot. + runTaskWithStdin(ctx, []string{ + "rc.verbose=nothing", + "project:hexai", + "+integrationtest", + "status:pending", + "delete", + }, "all\n") +} + func TestMain(m *testing.M) { repoRoot = findRepoRoot() if repoRoot == "" { @@ -282,6 +313,9 @@ func TestMain(m *testing.M) { fmt.Fprintf(os.Stderr, "failed to build ask binary: %v\n%s\n", err, out) os.Exit(1) } + // Remove any tasks left over from previous integration test runs to avoid + // state pollution across runs. + cleanupOrphanedIntegrationTasks() os.Exit(m.Run()) } |
