golang.org/x/build@v0.0.0-20240506185731-218518f32b70/internal/relui/workflows_test.go (about) 1 // Copyright 2023 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package relui 6 7 import ( 8 "context" 9 "database/sql" 10 "errors" 11 "flag" 12 "strings" 13 "testing" 14 "time" 15 16 "github.com/google/go-cmp/cmp" 17 "github.com/google/uuid" 18 "go.chromium.org/luci/auth" 19 buildbucketpb "go.chromium.org/luci/buildbucket/proto" 20 "go.chromium.org/luci/grpc/prpc" 21 "go.chromium.org/luci/hardcoded/chromeinfra" 22 "golang.org/x/build/internal/relui/db" 23 "golang.org/x/build/internal/task" 24 "golang.org/x/build/internal/workflow" 25 ) 26 27 func TestAwaitFunc(t *testing.T) { 28 cases := []struct { 29 desc string 30 want map[string]interface{} 31 wantErr bool 32 wantCancel bool 33 }{ 34 { 35 desc: "success", 36 want: map[string]interface{}{"await": true}, 37 }, 38 { 39 desc: "error", 40 wantErr: true, 41 }, 42 { 43 desc: "cancel", 44 wantCancel: true, 45 wantErr: true, 46 }, 47 } 48 for _, c := range cases { 49 t.Run(c.desc, func(t *testing.T) { 50 ctx, cancel := context.WithCancel(context.Background()) 51 defer cancel() 52 53 didWork := make(chan struct{}, 2) 54 success := make(chan interface{}) 55 done := make(chan interface{}) 56 wd := workflow.New() 57 58 awaitFunc := func(ctx *workflow.TaskContext) error { 59 _, err := task.AwaitCondition(ctx, 10*time.Millisecond, func() (int, bool, error) { 60 select { 61 case <-success: 62 if c.wantCancel { 63 cancel() 64 return 0, false, ctx.Err() 65 } else if c.wantErr { 66 return 0, false, errors.New("someError") 67 } 68 return 0, true, nil 69 case <-ctx.Done(): 70 return 0, false, ctx.Err() 71 case didWork <- struct{}{}: 72 return 0, false, nil 73 } 74 }) 75 return err 76 } 77 await := workflow.Action0(wd, "AwaitFunc", awaitFunc) 78 truth := workflow.Task0(wd, "truth", func(_ context.Context) (bool, error) { return true, nil }, workflow.After(await)) 79 workflow.Output(wd, "await", truth) 80 81 w, err := workflow.Start(wd, nil) 82 if err != nil { 83 t.Fatalf("workflow.Start(%v, %v) = %v, %v, wanted no error", wd, nil, w, err) 84 } 85 go func() { 86 if c.wantErr { 87 runToFailure(t, ctx, w, "AwaitFunc", &verboseListener{t: t}) 88 } else { 89 outputs, err := runWorkflow(t, ctx, w, nil) 90 if err != nil { 91 t.Errorf("runworkflow() = _, %v", err) 92 } 93 if diff := cmp.Diff(c.want, outputs); diff != "" { 94 t.Errorf("runWorkflow() mismatch (-want +got):\n%s", diff) 95 } 96 } 97 close(done) 98 }() 99 100 select { 101 case <-time.After(5 * time.Second): 102 t.Error("AwaitFunc() never called f, wanted at least one call") 103 case <-didWork: 104 // AwaitFunc() called f successfully. 105 } 106 select { 107 case <-done: 108 t.Errorf("AwaitFunc() finished early, wanted it to still be looping") 109 case <-didWork: 110 close(success) 111 } 112 <-done 113 }) 114 } 115 } 116 117 func TestCheckTaskApproved(t *testing.T) { 118 ctx, cancel := context.WithCancel(context.Background()) 119 defer cancel() 120 121 hourAgo := time.Now().Add(-1 * time.Hour) 122 p := testDB(ctx, t) 123 q := db.New(p) 124 125 wf := db.CreateWorkflowParams{ 126 ID: uuid.New(), 127 Params: nullString(`{"farewell": "bye", "greeting": "hello"}`), 128 Name: nullString(`echo`), 129 CreatedAt: hourAgo, 130 UpdatedAt: hourAgo, 131 } 132 if _, err := q.CreateWorkflow(ctx, wf); err != nil { 133 t.Fatalf("CreateWorkflow(_, %v) = _, %v, wanted no error", wf, err) 134 } 135 gtg := db.CreateTaskParams{ 136 WorkflowID: wf.ID, 137 Name: "approve please", 138 Finished: true, 139 Error: nullString("internal explosion"), 140 CreatedAt: hourAgo, 141 UpdatedAt: hourAgo, 142 } 143 if _, err := q.CreateTask(ctx, gtg); err != nil { 144 t.Fatalf("CreateTask(_, %v) = _, %v, wanted no error", gtg, err) 145 } 146 tctx := &workflow.TaskContext{Context: ctx, WorkflowID: wf.ID, TaskName: gtg.Name} 147 148 got, err := checkTaskApproved(tctx, p) 149 if err != nil || got { 150 t.Errorf("checkTaskApproved(_, %v, %q) = %t, %v wanted %t, %v", p, gtg.Name, got, err, false, nil) 151 } 152 tp := db.TaskParams{WorkflowID: wf.ID, Name: gtg.Name} 153 task, err := q.Task(ctx, tp) 154 if err != nil { 155 t.Fatalf("q.Task(_, %v) = %v, %v, wanted no error", tp, task, err) 156 } 157 if !task.ReadyForApproval { 158 t.Errorf("task.ReadyForApproval = %v, wanted %v", task.ReadyForApproval, true) 159 } 160 161 atp := db.ApproveTaskParams{ 162 WorkflowID: wf.ID, 163 Name: gtg.Name, 164 ApprovedAt: sql.NullTime{Time: time.Now(), Valid: true}, 165 } 166 _, err = q.ApproveTask(ctx, atp) 167 if err != nil { 168 t.Errorf("q.ApproveTask(_, %v) = _, %v, wanted no error", atp, err) 169 } 170 171 got, err = checkTaskApproved(tctx, p) 172 if err != nil || !got { 173 t.Errorf("checkTaskApproved(_, %v, %q) = %t, %v wanted %t, %v", p, gtg.Name, got, err, true, nil) 174 } 175 } 176 177 func runWorkflow(t *testing.T, ctx context.Context, w *workflow.Workflow, listener workflow.Listener) (map[string]interface{}, error) { 178 ctx, cancel := context.WithTimeout(ctx, 10*time.Second) 179 defer cancel() 180 t.Helper() 181 if listener == nil { 182 listener = &verboseListener{t: t} 183 } 184 return w.Run(ctx, listener) 185 } 186 187 var flagRelevantBuildersMajor = flag.Int("relevant-builders-major", 0, "TestReadRelevantBuildersLive's readRelevantBuilders major version") 188 189 func TestReadRelevantBuildersLive(t *testing.T) { 190 if !testing.Verbose() || flag.Lookup("test.run").Value.String() != "^TestReadRelevantBuildersLive$" { 191 t.Skip("not running a live test requiring manual verification if not explicitly requested with go test -v -run=^TestReadRelevantBuildersLive$") 192 } else if *flagRelevantBuildersMajor == 0 { 193 t.Fatal("-relevant-builders-major flag must specify a non-zero major version") 194 } 195 196 ctx := &workflow.TaskContext{Context: context.Background(), Logger: &testLogger{t, ""}} 197 luciHTTPClient, err := auth.NewAuthenticator(ctx, auth.SilentLogin, chromeinfra.DefaultAuthOptions()).Client() 198 if err != nil { 199 t.Fatal("auth.NewAuthenticator:", err) 200 } 201 buildersClient := buildbucketpb.NewBuildersClient(&prpc.Client{ 202 C: luciHTTPClient, 203 Host: "cr-buildbucket.appspot.com", 204 }) 205 tasks := BuildReleaseTasks{ 206 BuildBucketClient: &task.RealBuildBucketClient{BuildersClient: buildersClient}, 207 } 208 got, err := tasks.readRelevantBuilders(ctx, *flagRelevantBuildersMajor, task.KindMajor) 209 if err != nil { 210 t.Fatal("readRelevantBuilders:", err) 211 } 212 t.Logf("relevant builders:\n%s", strings.Join(got, "\n")) 213 }