Commit 46b836b

Eric Bower  ·  2026-09-15 07:43:34 -0400 EDT
parent 61d5525
fix: clear cache after successful run
4 files changed,  +157, -2
+47, -2
......@@ -106,7 +106,25 @@ func newPipelineUIState(repo, branch, commit, jobID string, stats *RepoStats) *P
106106 return ui
107107 }
108108
109+// PruneUnexecuted removes tasks that were expected from previous stats but never actually ran.
110+func (ui *PipelineUIState) PruneUnexecuted() {
111+ ui.mu.Lock()
112+ defer ui.mu.Unlock()
113+
114+ var newOrder []string
115+ for _, name := range ui.TaskOrder {
116+ task := ui.Tasks[name]
117+ if task != nil && task.Status == "expected" {
118+ delete(ui.Tasks, name)
119+ } else {
120+ newOrder = append(newOrder, name)
121+ }
122+ }
123+ ui.TaskOrder = newOrder
124+}
125+
109126 // ---------------------------------------------------------
127+
110128 // UI Helpers & Formatters
111129 // ---------------------------------------------------------
112130
......@@ -420,7 +438,6 @@ func (ui *PipelineUIState) RenderSummary(reportPath string, exitCode int) string
420438 defer ui.mu.Unlock()
421439
422440 var sb strings.Builder
423- totalTasks := len(ui.TaskOrder)
424441 failedCount := 0
425442 var failedTasks []*TaskUIState
426443
......@@ -443,9 +460,15 @@ func (ui *PipelineUIState) RenderSummary(reportPath string, exitCode int) string
443460 wallStr := formatShortDuration(elapsed)
444461
445462 if exitCode == 0 && failedCount == 0 {
446- fmt.Fprintf(&sb, "✔ All %d tasks succeeded (total time: %s)\n\n", totalTasks, wallStr)
463+ var succeededTasks []*TaskUIState
447464 for _, name := range ui.TaskOrder {
448465 task := ui.Tasks[name]
466+ if task != nil && task.Status == "success" {
467+ succeededTasks = append(succeededTasks, task)
468+ }
469+ }
470+ fmt.Fprintf(&sb, "✔ All %d tasks succeeded (total time: %s)\n\n", len(succeededTasks), wallStr)
471+ for _, task := range succeededTasks {
449472 avgStr := ""
450473 if task.PredictedDur > 0 {
451474 avgStr = fmt.Sprintf("(avg %s", formatShortDuration(task.PredictedDur))
......@@ -864,8 +887,30 @@ func waitAndReportUI(cfg *Cfg, log *slog.Logger, repoName, jobID, domain, eventT
864887
865888 exitCode, overallStatus := resolveJobExitCode(finalSessions)
866889
890+ if overallStatus == "success" {
891+ ctrl.ui.PruneUnexecuted()
892+ }
893+
867894 // Update historical stats.json
868895 if stats != nil {
896+ if overallStatus == "success" {
897+ currentTasks := make(map[string]bool)
898+ for _, s := range finalSessions {
899+ if s.Short != "runner" && s.Short != "" {
900+ currentTasks[s.Short] = true
901+ }
902+ }
903+ ctrl.ui.mu.Lock()
904+ for name, t := range ctrl.ui.Tasks {
905+ if t.Status != "expected" {
906+ currentTasks[name] = true
907+ }
908+ }
909+ ctrl.ui.mu.Unlock()
910+
911+ pruneStaleTasks(stats, currentTasks)
912+ }
913+
869914 for _, s := range finalSessions {
870915 if s.Short == "runner" || s.Short == "" {
871916 continue
+65, -0
......@@ -429,3 +429,68 @@ func TestController_RedrawNoDrift(t *testing.T) {
429429 t.Errorf("expected rendered=2, got %d", ctrl.rendered)
430430 }
431431 }
432+
433+func TestUI_PruneUnexecutedTasksOnSuccess(t *testing.T) {
434+ stats := &RepoStats{
435+ Repo: "pgit",
436+ Tasks: map[string]*TaskStats{
437+ "-d": {AvgDurationMs: 0},
438+ "fmt": {AvgDurationMs: 1000},
439+ "lint": {AvgDurationMs: 500},
440+ "test": {AvgDurationMs: 3000},
441+ },
442+ }
443+
444+ ui := newPipelineUIState("pgit", "main", "abc1234", "job1", stats)
445+ if len(ui.TaskOrder) != 4 {
446+ t.Fatalf("expected 4 tasks initialized from stats, got %d", len(ui.TaskOrder))
447+ }
448+
449+ // fmt, lint, test succeeded in this run; -d was never executed
450+ ui.Tasks["fmt"].Status = "success"
451+ ui.Tasks["fmt"].Duration = 1 * time.Second
452+ ui.Tasks["lint"].Status = "success"
453+ ui.Tasks["lint"].Duration = 500 * time.Millisecond
454+ ui.Tasks["test"].Status = "success"
455+ ui.Tasks["test"].Duration = 2 * time.Second
456+
457+ ui.PruneUnexecuted()
458+
459+ if _, exists := ui.Tasks["-d"]; exists {
460+ t.Errorf("expected '-d' to be removed from ui.Tasks")
461+ }
462+ if len(ui.TaskOrder) != 3 {
463+ t.Errorf("expected 3 tasks in ui.TaskOrder, got %d", len(ui.TaskOrder))
464+ }
465+
466+ summary := ui.RenderSummary("/tmp/report.html", 0)
467+ if !strings.Contains(summary, "All 3 tasks succeeded") {
468+ t.Errorf("expected summary to state All 3 tasks succeeded, got:\n%s", summary)
469+ }
470+ if strings.Contains(summary, "-d") {
471+ t.Errorf("expected summary NOT to contain '-d', got:\n%s", summary)
472+ }
473+}
474+
475+func TestUI_RenderSummary_IgnoresExpectedTasksOnSuccess(t *testing.T) {
476+ stats := &RepoStats{
477+ Repo: "pgit",
478+ Tasks: map[string]*TaskStats{
479+ "-d": {AvgDurationMs: 0},
480+ "test": {AvgDurationMs: 3000},
481+ },
482+ }
483+
484+ ui := newPipelineUIState("pgit", "main", "abc1234", "job2", stats)
485+ ui.Tasks["test"].Status = "success"
486+ ui.Tasks["test"].Duration = 1 * time.Second
487+ // -d remains with Status == "expected"
488+
489+ summary := ui.RenderSummary("/tmp/report.html", 0)
490+ if !strings.Contains(summary, "All 1 tasks succeeded") {
491+ t.Errorf("expected 'All 1 tasks succeeded', got:\n%s", summary)
492+ }
493+ if strings.Contains(summary, "-d") {
494+ t.Errorf("expected summary NOT to contain '-d', got:\n%s", summary)
495+ }
496+}
+12, -0
......@@ -163,3 +163,15 @@ func computeOutcomeTransition(prev *TaskStats, currentExitCode int) string {
163163 }
164164 return ""
165165 }
166+
167+// pruneStaleTasks removes tasks from stats that were not executed in the current run.
168+func pruneStaleTasks(stats *RepoStats, executedTasks map[string]bool) {
169+ if stats == nil || stats.Tasks == nil {
170+ return
171+ }
172+ for name := range stats.Tasks {
173+ if !executedTasks[name] {
174+ delete(stats.Tasks, name)
175+ }
176+ }
177+}
+33, -0
......@@ -218,3 +218,36 @@ func TestStats_OutcomeTransitions(t *testing.T) {
218218 t.Errorf("expected 'new_failure', got '%s'", trans)
219219 }
220220 }
221+
222+func TestStats_PruneStaleTasks(t *testing.T) {
223+ stats := &RepoStats{
224+ Repo: "testrepo",
225+ Tasks: map[string]*TaskStats{
226+ "-d": {AvgDurationMs: 100},
227+ "fmt": {AvgDurationMs: 500},
228+ "lint": {AvgDurationMs: 1200},
229+ "test": {AvgDurationMs: 3000},
230+ },
231+ }
232+
233+ // Current run only ran fmt, lint, test (task "-d" was removed)
234+ currentTasks := map[string]bool{
235+ "fmt": true,
236+ "lint": true,
237+ "test": true,
238+ }
239+
240+ pruneStaleTasks(stats, currentTasks)
241+
242+ if _, exists := stats.Tasks["-d"]; exists {
243+ t.Errorf("expected '-d' to be pruned from stats.Tasks")
244+ }
245+ if len(stats.Tasks) != 3 {
246+ t.Errorf("expected 3 tasks remaining, got %d", len(stats.Tasks))
247+ }
248+ for _, name := range []string{"fmt", "lint", "test"} {
249+ if _, exists := stats.Tasks[name]; !exists {
250+ t.Errorf("expected task %s to remain in stats.Tasks", name)
251+ }
252+ }
253+}