blob: cdadd5f31d036ac9b2a30625266094e04d31cd10 [file] [log] [blame]
// 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.linksStep(bbCtx, buildInput, taskId); err != nil {
err = fmt.Errorf("linksStep: %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)
}
step.End(nil)
return task.TaskId, nil
}
func (r *runImpl) linksStep(bbCtx context.Context, buildInput *ftxproto.InputProperties, taskId string) error {
step, bbCtx := build.StartStep(bbCtx, "Links")
md := fmt.Sprintf("* [swarming task](https://%s.appspot.com/task?id=%s)", instance(buildInput), taskId)
if spongeId, ok := buildInput.Metadata["GUITAR_SPONGE_ID"]; ok {
md = fmt.Sprintf("%s\n* [sponge](http://sponge/%s)", md, spongeId)
}
step.SetSummaryMarkdown(md)
step.End(nil)
return 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"
}
}