go.fuchsia.dev/infra@v0.0.0-20240507153436-9b593402251b/cmd/ftxtest/run.go (about) 1 // Copyright 2023 The Fuchsia Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 package main 5 6 import ( 7 "context" 8 "errors" 9 "fmt" 10 "os" 11 "strings" 12 13 "github.com/maruel/subcommands" 14 "go.chromium.org/luci/auth" 15 "go.chromium.org/luci/luciexe/build" 16 ftxproto "go.fuchsia.dev/infra/cmd/ftxtest/proto" 17 ) 18 19 func cmdRun(authOpts auth.Options) *subcommands.Command { 20 return &subcommands.Command{ 21 UsageLine: "run", 22 ShortDesc: "runs test", 23 LongDesc: "Runs test based on luciexe protocol.", 24 CommandRun: func() subcommands.CommandRun { 25 r := &runImpl{} 26 r.Init(authOpts) 27 return r 28 }, 29 } 30 } 31 32 type runImpl struct { 33 commonFlags 34 35 subcommands.CommandRunBase 36 } 37 38 func (r *runImpl) Init(defaultAuthOpts auth.Options) { 39 r.commonFlags.Init(defaultAuthOpts) 40 } 41 42 func (r *runImpl) Run(a subcommands.Application, args []string, env subcommands.Env) int { 43 if err := r.commonFlags.Parse(); err != nil { 44 fmt.Fprintf(os.Stderr, "Error parsing common flags: %v\n", err) 45 return 1 46 } 47 r.luciexeInit() 48 // luciexe uses os.exit to exit. 49 return 0 50 } 51 52 func (r *runImpl) luciexeInit() { 53 buildInput := &ftxproto.InputProperties{} 54 buildOutput := &ftxproto.OutputProperties{} 55 var writeOutputProps func(*ftxproto.OutputProperties) 56 build.Main(buildInput, &writeOutputProps, nil, func(bbCtx context.Context, extraArgs []string, state *build.State) error { 57 state.SetSummaryMarkdown(buildInput.Name) 58 ctx := context.Background() 59 defer writeOutputProps(buildOutput) 60 swarming, err := r.authenticateStep(ctx, bbCtx, buildInput) 61 if err != nil { 62 err = fmt.Errorf("authenticateStep: %v", err) 63 state.SetSummaryMarkdown(fmt.Sprintf("%v: %v", buildInput.Name, err)) 64 return err 65 } 66 taskId, err := r.launchTaskStep(bbCtx, swarming, buildInput, state) 67 if err != nil { 68 err = fmt.Errorf("launchTaskStep: %v", err) 69 state.SetSummaryMarkdown(fmt.Sprintf("%v: %v", buildInput.Name, err)) 70 return err 71 } 72 if err := r.linksStep(bbCtx, buildInput, taskId); err != nil { 73 err = fmt.Errorf("linksStep: %v", err) 74 state.SetSummaryMarkdown(fmt.Sprintf("%v: %v", buildInput.Name, err)) 75 return err 76 } 77 if err := r.waitTaskStep(bbCtx, swarming, taskId); err != nil { 78 err = fmt.Errorf("waitTaskStep: %v", err) 79 state.SetSummaryMarkdown(fmt.Sprintf("%v: %v", buildInput.Name, err)) 80 return err 81 } 82 if err := r.resultStep(ctx, bbCtx, swarming, taskId, buildInput, buildOutput); err != nil { 83 err = fmt.Errorf("resultStep: %v", err) 84 state.SetSummaryMarkdown(fmt.Sprintf("%v: %v", buildInput.Name, err)) 85 return err 86 } 87 return nil 88 }) 89 } 90 91 func (r *runImpl) authenticateStep(ctx context.Context, bbCtx context.Context, buildInput *ftxproto.InputProperties) (*Swarming, error) { 92 step, bbCtx := build.StartStep(bbCtx, "Authenticate") 93 authenticator := auth.NewAuthenticator(ctx, auth.SilentLogin, r.parsedAuthOpts) 94 httpClient, err := authenticator.Client() 95 if err != nil { 96 fmt.Fprintf(os.Stderr, "You need to login first by running:\n") 97 fmt.Fprintf(os.Stderr, " luci-auth login -scopes %q\n", strings.Join(r.parsedAuthOpts.Scopes, " ")) 98 return nil, errors.New("Not logged in.") 99 } 100 cas, err := NewCAS(ctx, r.parsedAuthOpts, instance(buildInput)) 101 if err != nil { 102 return nil, fmt.Errorf("NewCAS: %v", err) 103 } 104 swarming, err := NewSwarming(httpClient, instance(buildInput), cas) 105 if err != nil { 106 step.End(err) 107 return nil, fmt.Errorf("NewSwarming: %v", err) 108 } 109 step.End(nil) 110 return swarming, nil 111 } 112 113 func (r *runImpl) launchTaskStep(bbCtx context.Context, swarming *Swarming, buildInput *ftxproto.InputProperties, state *build.State) (string, error) { 114 step, bbCtx := build.StartStep(bbCtx, "Launch Swarming Task") 115 if len(buildInput.Name) == 0 { 116 err := errors.New("Name is required.") 117 step.End(err) 118 return "", err 119 } 120 parentTaskId := "" 121 if state.Build() != nil && state.Build().Infra != nil && state.Build().Infra.Swarming != nil { 122 parentTaskId = state.Build().Infra.Swarming.TaskId 123 } else { 124 parentTaskId = state.Build().GetInfra().GetBackend().GetTask().GetId().GetId() 125 } 126 task, err := swarming.LaunchTask(buildInput, parentTaskId) 127 if err != nil { 128 step.End(err) 129 return "", fmt.Errorf("LaunchTask: %v", err) 130 } 131 step.End(nil) 132 return task.TaskId, nil 133 } 134 135 func (r *runImpl) linksStep(bbCtx context.Context, buildInput *ftxproto.InputProperties, taskId string) error { 136 step, bbCtx := build.StartStep(bbCtx, "Links") 137 md := fmt.Sprintf("* [swarming task](https://%s.appspot.com/task?id=%s)", instance(buildInput), taskId) 138 if spongeId, ok := buildInput.Metadata["GUITAR_SPONGE_ID"]; ok { 139 md = fmt.Sprintf("%s\n* [sponge](http://sponge/%s)", md, spongeId) 140 } 141 step.SetSummaryMarkdown(md) 142 step.End(nil) 143 return nil 144 } 145 146 func (r *runImpl) waitTaskStep(bbCtx context.Context, swarming *Swarming, taskId string) error { 147 step, bbCtx := build.StartStep(bbCtx, "Wait Task Completion") 148 if err := swarming.WaitTask(taskId); err != nil { 149 step.End(err) 150 return err 151 } 152 step.End(nil) 153 return nil 154 } 155 156 func (r *runImpl) resultStep(ctx context.Context, bbCtx context.Context, swarming *Swarming, taskId string, buildInput *ftxproto.InputProperties, buildOutput *ftxproto.OutputProperties) error { 157 step, bbCtx := build.StartStep(bbCtx, "Result") 158 if err := swarming.CheckTestFailure(ctx, taskId, buildInput, buildOutput); err != nil { 159 step.End(err) 160 return err 161 } 162 step.End(nil) 163 return nil 164 } 165 166 func instance(buildInput *ftxproto.InputProperties) string { 167 if buildInput.External { 168 return "chromium-swarm" 169 } else { 170 return "chrome-swarming" 171 } 172 }