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  }