summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-03-28 11:40:59 +0200
committerPaul Buetow <paul@buetow.org>2026-03-28 11:40:59 +0200
commit118170f8f3735a9573a2a72b83d6aa2af63be85a (patch)
tree269f3eabb37bb624799b0ca2a7fe545f83451878
parent2c1930adf4355b7d0b113f3a2f9573c4f961420e (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.go38
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())
}