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  }