| // Copyright 2023 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package relui |
| |
| import ( |
| "context" |
| "database/sql" |
| "errors" |
| "flag" |
| "strings" |
| "testing" |
| "time" |
| |
| "github.com/google/go-cmp/cmp" |
| "github.com/google/uuid" |
| "go.chromium.org/luci/auth" |
| buildbucketpb "go.chromium.org/luci/buildbucket/proto" |
| "go.chromium.org/luci/grpc/prpc" |
| "go.chromium.org/luci/hardcoded/chromeinfra" |
| "golang.org/x/build/internal/relui/db" |
| "golang.org/x/build/internal/task" |
| "golang.org/x/build/internal/workflow" |
| ) |
| |
| func TestAwaitFunc(t *testing.T) { |
| cases := []struct { |
| desc string |
| want map[string]interface{} |
| wantErr bool |
| wantCancel bool |
| }{ |
| { |
| desc: "success", |
| want: map[string]interface{}{"await": true}, |
| }, |
| { |
| desc: "error", |
| wantErr: true, |
| }, |
| { |
| desc: "cancel", |
| wantCancel: true, |
| wantErr: true, |
| }, |
| } |
| for _, c := range cases { |
| t.Run(c.desc, func(t *testing.T) { |
| ctx, cancel := context.WithCancel(context.Background()) |
| defer cancel() |
| |
| didWork := make(chan struct{}, 2) |
| success := make(chan interface{}) |
| done := make(chan interface{}) |
| wd := workflow.New() |
| |
| awaitFunc := func(ctx *workflow.TaskContext) error { |
| _, err := task.AwaitCondition(ctx, 10*time.Millisecond, func() (int, bool, error) { |
| select { |
| case <-success: |
| if c.wantCancel { |
| cancel() |
| return 0, false, ctx.Err() |
| } else if c.wantErr { |
| return 0, false, errors.New("someError") |
| } |
| return 0, true, nil |
| case <-ctx.Done(): |
| return 0, false, ctx.Err() |
| case didWork <- struct{}{}: |
| return 0, false, nil |
| } |
| }) |
| return err |
| } |
| await := workflow.Action0(wd, "AwaitFunc", awaitFunc) |
| truth := workflow.Task0(wd, "truth", func(_ context.Context) (bool, error) { return true, nil }, workflow.After(await)) |
| workflow.Output(wd, "await", truth) |
| |
| w, err := workflow.Start(wd, nil) |
| if err != nil { |
| t.Fatalf("workflow.Start(%v, %v) = %v, %v, wanted no error", wd, nil, w, err) |
| } |
| go func() { |
| if c.wantErr { |
| runToFailure(t, ctx, w, "AwaitFunc", &verboseListener{t: t}) |
| } else { |
| outputs, err := runWorkflow(t, ctx, w, nil) |
| if err != nil { |
| t.Errorf("runworkflow() = _, %v", err) |
| } |
| if diff := cmp.Diff(c.want, outputs); diff != "" { |
| t.Errorf("runWorkflow() mismatch (-want +got):\n%s", diff) |
| } |
| } |
| close(done) |
| }() |
| |
| select { |
| case <-time.After(5 * time.Second): |
| t.Error("AwaitFunc() never called f, wanted at least one call") |
| case <-didWork: |
| // AwaitFunc() called f successfully. |
| } |
| select { |
| case <-done: |
| t.Errorf("AwaitFunc() finished early, wanted it to still be looping") |
| case <-didWork: |
| close(success) |
| } |
| <-done |
| }) |
| } |
| } |
| |
| func TestCheckTaskApproved(t *testing.T) { |
| ctx, cancel := context.WithCancel(context.Background()) |
| defer cancel() |
| |
| hourAgo := time.Now().Add(-1 * time.Hour) |
| p := testDB(ctx, t) |
| q := db.New(p) |
| |
| wf := db.CreateWorkflowParams{ |
| ID: uuid.New(), |
| Params: nullString(`{"farewell": "bye", "greeting": "hello"}`), |
| Name: nullString(`echo`), |
| CreatedAt: hourAgo, |
| UpdatedAt: hourAgo, |
| } |
| if _, err := q.CreateWorkflow(ctx, wf); err != nil { |
| t.Fatalf("CreateWorkflow(_, %v) = _, %v, wanted no error", wf, err) |
| } |
| gtg := db.CreateTaskParams{ |
| WorkflowID: wf.ID, |
| Name: "approve please", |
| Finished: true, |
| Error: nullString("internal explosion"), |
| CreatedAt: hourAgo, |
| UpdatedAt: hourAgo, |
| } |
| if _, err := q.CreateTask(ctx, gtg); err != nil { |
| t.Fatalf("CreateTask(_, %v) = _, %v, wanted no error", gtg, err) |
| } |
| tctx := &workflow.TaskContext{Context: ctx, WorkflowID: wf.ID, TaskName: gtg.Name} |
| |
| got, err := checkTaskApproved(tctx, p) |
| if err != nil || got { |
| t.Errorf("checkTaskApproved(_, %v, %q) = %t, %v wanted %t, %v", p, gtg.Name, got, err, false, nil) |
| } |
| tp := db.TaskParams{WorkflowID: wf.ID, Name: gtg.Name} |
| task, err := q.Task(ctx, tp) |
| if err != nil { |
| t.Fatalf("q.Task(_, %v) = %v, %v, wanted no error", tp, task, err) |
| } |
| if !task.ReadyForApproval { |
| t.Errorf("task.ReadyForApproval = %v, wanted %v", task.ReadyForApproval, true) |
| } |
| |
| atp := db.ApproveTaskParams{ |
| WorkflowID: wf.ID, |
| Name: gtg.Name, |
| ApprovedAt: sql.NullTime{Time: time.Now(), Valid: true}, |
| } |
| _, err = q.ApproveTask(ctx, atp) |
| if err != nil { |
| t.Errorf("q.ApproveTask(_, %v) = _, %v, wanted no error", atp, err) |
| } |
| |
| got, err = checkTaskApproved(tctx, p) |
| if err != nil || !got { |
| t.Errorf("checkTaskApproved(_, %v, %q) = %t, %v wanted %t, %v", p, gtg.Name, got, err, true, nil) |
| } |
| } |
| |
| func runWorkflow(t *testing.T, ctx context.Context, w *workflow.Workflow, listener workflow.Listener) (map[string]interface{}, error) { |
| ctx, cancel := context.WithTimeout(ctx, 10*time.Second) |
| defer cancel() |
| t.Helper() |
| if listener == nil { |
| listener = &verboseListener{t: t} |
| } |
| return w.Run(ctx, listener) |
| } |
| |
| var flagRelevantBuildersMajor = flag.Int("relevant-builders-major", 0, "TestReadRelevantBuildersLive's readRelevantBuilders major version") |
| |
| func TestReadRelevantBuildersLive(t *testing.T) { |
| if !testing.Verbose() || flag.Lookup("test.run").Value.String() != "^TestReadRelevantBuildersLive$" { |
| t.Skip("not running a live test requiring manual verification if not explicitly requested with go test -v -run=^TestReadRelevantBuildersLive$") |
| } else if *flagRelevantBuildersMajor == 0 { |
| t.Fatal("-relevant-builders-major flag must specify a non-zero major version") |
| } |
| |
| ctx := &workflow.TaskContext{Context: context.Background(), Logger: &testLogger{t, ""}} |
| luciHTTPClient, err := auth.NewAuthenticator(ctx, auth.SilentLogin, chromeinfra.DefaultAuthOptions()).Client() |
| if err != nil { |
| t.Fatal("auth.NewAuthenticator:", err) |
| } |
| buildersClient := buildbucketpb.NewBuildersClient(&prpc.Client{ |
| C: luciHTTPClient, |
| Host: "cr-buildbucket.appspot.com", |
| }) |
| tasks := BuildReleaseTasks{ |
| BuildBucketClient: &task.RealBuildBucketClient{BuildersClient: buildersClient}, |
| } |
| got, err := tasks.readRelevantBuilders(ctx, *flagRelevantBuildersMajor, task.KindMajor) |
| if err != nil { |
| t.Fatal("readRelevantBuilders:", err) |
| } |
| t.Logf("relevant builders:\n%s", strings.Join(got, "\n")) |
| } |