| // Copyright 2023 The Fuchsia 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 main |
| |
| import ( |
| "context" |
| "errors" |
| "fmt" |
| "os" |
| "strings" |
| |
| "github.com/maruel/subcommands" |
| "go.chromium.org/luci/auth" |
| "go.chromium.org/luci/luciexe/build" |
| ftxproto "go.fuchsia.dev/infra/cmd/ftxtest/proto" |
| ) |
| |
| func cmdRun(authOpts auth.Options) *subcommands.Command { |
| return &subcommands.Command{ |
| UsageLine: "run", |
| ShortDesc: "runs test", |
| LongDesc: "Runs test based on luciexe protocol.", |
| CommandRun: func() subcommands.CommandRun { |
| r := &runImpl{} |
| r.Init(authOpts) |
| return r |
| }, |
| } |
| } |
| |
| type runImpl struct { |
| commonFlags |
| |
| subcommands.CommandRunBase |
| } |
| |
| func (r *runImpl) Init(defaultAuthOpts auth.Options) { |
| r.commonFlags.Init(defaultAuthOpts) |
| } |
| |
| func (r *runImpl) Run(a subcommands.Application, args []string, env subcommands.Env) int { |
| if err := r.commonFlags.Parse(); err != nil { |
| fmt.Fprintf(os.Stderr, "Error parsing common flags: %v\n", err) |
| return 1 |
| } |
| r.luciexeInit() |
| // luciexe uses os.exit to exit. |
| return 0 |
| } |
| |
| func (r *runImpl) luciexeInit() { |
| buildInput := &ftxproto.InputProperties{} |
| buildOutput := &ftxproto.OutputProperties{} |
| var writeOutputProps func(*ftxproto.OutputProperties) |
| build.Main(buildInput, &writeOutputProps, nil, func(bbCtx context.Context, extraArgs []string, state *build.State) error { |
| state.SetSummaryMarkdown(buildInput.Name) |
| ctx := context.Background() |
| defer writeOutputProps(buildOutput) |
| swarming, err := r.authenticateStep(ctx, bbCtx, buildInput) |
| if err != nil { |
| err = fmt.Errorf("authenticateStep: %v", err) |
| state.SetSummaryMarkdown(fmt.Sprintf("%v: %v", buildInput.Name, err)) |
| return err |
| } |
| taskId, err := r.launchTaskStep(bbCtx, swarming, buildInput, state) |
| if err != nil { |
| err = fmt.Errorf("launchTaskStep: %v", err) |
| state.SetSummaryMarkdown(fmt.Sprintf("%v: %v", buildInput.Name, err)) |
| return err |
| } |
| if err := r.waitTaskStep(bbCtx, swarming, taskId); err != nil { |
| err = fmt.Errorf("waitTaskStep: %v", err) |
| state.SetSummaryMarkdown(fmt.Sprintf("%v: %v", buildInput.Name, err)) |
| return err |
| } |
| if err := r.resultStep(ctx, bbCtx, swarming, taskId, buildOutput); err != nil { |
| err = fmt.Errorf("resultStep: %v", err) |
| state.SetSummaryMarkdown(fmt.Sprintf("%v: %v", buildInput.Name, err)) |
| return err |
| } |
| return nil |
| }) |
| } |
| |
| func (r *runImpl) authenticateStep(ctx context.Context, bbCtx context.Context, buildInput *ftxproto.InputProperties) (*Swarming, error) { |
| step, bbCtx := build.StartStep(bbCtx, "Authenticate") |
| authenticator := auth.NewAuthenticator(ctx, auth.SilentLogin, r.parsedAuthOpts) |
| httpClient, err := authenticator.Client() |
| if err != nil { |
| fmt.Fprintf(os.Stderr, "You need to login first by running:\n") |
| fmt.Fprintf(os.Stderr, " luci-auth login -scopes %q\n", strings.Join(r.parsedAuthOpts.Scopes, " ")) |
| return nil, errors.New("Not logged in.") |
| } |
| cas, err := NewCAS(ctx, r.parsedAuthOpts, instance(buildInput)) |
| if err != nil { |
| return nil, fmt.Errorf("NewCAS: %v", err) |
| } |
| swarming, err := NewSwarming(httpClient, instance(buildInput), cas) |
| if err != nil { |
| step.End(err) |
| return nil, fmt.Errorf("NewSwarming: %v", err) |
| } |
| step.End(nil) |
| return swarming, nil |
| } |
| |
| func (r *runImpl) launchTaskStep(bbCtx context.Context, swarming *Swarming, buildInput *ftxproto.InputProperties, state *build.State) (string, error) { |
| step, bbCtx := build.StartStep(bbCtx, "Launch Swarming Task") |
| if len(buildInput.Name) == 0 { |
| err := errors.New("Name is required.") |
| step.End(err) |
| return "", err |
| } |
| parentTaskId := "" |
| if state.Build() != nil && state.Build().Infra != nil && state.Build().Infra.Swarming != nil { |
| parentTaskId = state.Build().Infra.Swarming.TaskId |
| } |
| task, err := swarming.LaunchTask(buildInput, parentTaskId) |
| if err != nil { |
| step.End(err) |
| return "", fmt.Errorf("LaunchTask: %v", err) |
| } |
| md := fmt.Sprintf("* [swarming task](https://%s.appspot.com/task?id=%s)", instance(buildInput), task.TaskId) |
| step.SetSummaryMarkdown(md) |
| step.End(nil) |
| return task.TaskId, nil |
| } |
| |
| func (r *runImpl) waitTaskStep(bbCtx context.Context, swarming *Swarming, taskId string) error { |
| step, bbCtx := build.StartStep(bbCtx, "Wait Task Completion") |
| if err := swarming.WaitTask(taskId); err != nil { |
| step.End(err) |
| return err |
| } |
| step.End(nil) |
| return nil |
| } |
| |
| func (r *runImpl) resultStep(ctx context.Context, bbCtx context.Context, swarming *Swarming, taskId string, buildOutput *ftxproto.OutputProperties) error { |
| step, bbCtx := build.StartStep(bbCtx, "Result") |
| if err := swarming.CheckTestFailure(ctx, taskId, buildOutput); err != nil { |
| step.End(err) |
| return err |
| } |
| step.End(nil) |
| return nil |
| } |
| |
| func instance(buildInput *ftxproto.InputProperties) string { |
| if buildInput.External { |
| return "chromium-swarm" |
| } else { |
| return "chrome-swarming" |
| } |
| } |