diff options
| author | Aleksandr Nogikh <nogikh@google.com> | 2026-02-26 16:31:29 +0000 |
|---|---|---|
| committer | Dmitry Vyukov <dvyukov@google.com> | 2026-03-05 16:16:40 +0000 |
| commit | 140f2ff8e33f036094db3a1eb146120e2fda90d8 (patch) | |
| tree | ab5455b4820c925790d85b4d41b876faf0d23395 | |
| parent | d8e5dc287038d84868b039b985a6182b3ad4da25 (diff) | |
dashboard: automatically create AI repro jobs
If no reproducer has been found for 2 weeks since the moment of the
first crash, create a corresponding AI job.
| -rw-r--r-- | dashboard/app/ai.go | 15 | ||||
| -rw-r--r-- | dashboard/app/ai_test.go | 39 |
2 files changed, 51 insertions, 3 deletions
diff --git a/dashboard/app/ai.go b/dashboard/app/ai.go index c5dba6922..68719b723 100644 --- a/dashboard/app/ai.go +++ b/dashboard/app/ai.go @@ -589,7 +589,7 @@ func aiBugWorkflows(ctx context.Context, bug *Bug) ([]*uiWorkflow, error) { if err != nil { return nil, err } - applicable := workflowsForBug(bug, true) + applicable := workflowsForBug(ctx, bug, true) var result []*uiWorkflow for _, flow := range workflows { // Also check that the workflow is active on some syz-agent's. @@ -670,6 +670,12 @@ func bugJobCreate(ctx context.Context, workflow string, typ ai.WorkflowType, bug }) } +const ( + // In how many days after a bug is reported we create the first AI repro job + // (provided no reproducer has been found in the meanwhile). + aiReproTriggerDays = 14 +) + // autoCreateAIJobs incrementally creates AI jobs for existing bugs, returns if any new jobs were created. // // The idea is as follows. We have a predicate (workflowsForBug) which says what workflows need to be @@ -721,7 +727,7 @@ func autoCreateAIJobs(ctx context.Context) (bool, error) { } func autoCreateAIJob(ctx context.Context, bug *Bug, bugKey *db.Key) (bool, error) { - workflows := workflowsForBug(bug, false) + workflows := workflowsForBug(ctx, bug, false) if len(workflows) == 0 { return false, nil } @@ -770,7 +776,7 @@ func autoCreateAIJob(ctx context.Context, bug *Bug, bugKey *db.Key) (bool, error return len(workflows) != 0, nil } -func workflowsForBug(bug *Bug, manual bool) map[ai.WorkflowType]bool { +func workflowsForBug(ctx context.Context, bug *Bug, manual bool) map[ai.WorkflowType]bool { workflows := make(map[ai.WorkflowType]bool) typ := crash.TitleToType(bug.Title) // UAF bugs stuck in last but one reporting. @@ -782,6 +788,9 @@ func workflowsForBug(bug *Bug, manual bool) map[ai.WorkflowType]bool { if typ == crash.KCSANDataRace { workflows[ai.WorkflowAssessmentKCSAN] = true } + if bug.ReproLevel == dashapi.ReproLevelNone && timeSince(ctx, bug.FirstTime) > aiReproTriggerDays*24*time.Hour { + workflows[ai.WorkflowRepro] = true + } if manual { // Types we don't create automatically yet, but can be created manually. if typ.IsUAF() { diff --git a/dashboard/app/ai_test.go b/dashboard/app/ai_test.go index c99491781..e3cd108f0 100644 --- a/dashboard/app/ai_test.go +++ b/dashboard/app/ai_test.go @@ -530,3 +530,42 @@ func TestAIJobAutoCreate(t *testing.T) { pollResp7, _ := c.agentClient.AIJobPoll(pollReq) require.Equal(t, pollResp7.ID, "") } + +func TestAIRepro(t *testing.T) { + c := NewSpannerCtx(t) + defer c.Close() + + build := testBuild(1) + c.aiClient.UploadBuild(build) + crash := testCrash(build, 1) + c.aiClient.ReportCrash(crash) + c.aiClient.pollEmailExtID() + + pollReq := &dashapi.AIJobPollReq{ + CodeRevision: prog.GitRevision, + Workflows: []dashapi.AIWorkflow{ + {Type: ai.WorkflowRepro, Name: string(ai.WorkflowRepro)}, + }, + } + // No job should be created initially. + pollResp0, _ := c.agentClient.AIJobPoll(pollReq) + require.Equal(t, pollResp0.ID, "") + + c.advanceTime(15 * 24 * time.Hour) + pollResp2, _ := c.agentClient.AIJobPoll(pollReq) + require.NotEqual(t, pollResp2.ID, "") + + c.agentClient.AIJobDone(&dashapi.AIJobDoneReq{ + ID: pollResp2.ID, + }) + + // A second bug with a repro. + crash2 := testCrashWithRepro(build, 2) + c.aiClient.ReportCrash(crash2) + c.aiClient.pollEmailExtID() + + c.advanceTime(15 * 24 * time.Hour) + // No AI job should be created. + pollResp4, _ := c.agentClient.AIJobPoll(pollReq) + require.Equal(t, pollResp4.ID, "") +} |
