xref: /aosp_15_r20/external/skia/infra/bots/task_drivers/bazel_test_precompiled/bazel_test_precompiled.go (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1// Copyright 2023 Google LLC
2//
3// Use of this source code is governed by a BSD-style license that can be
4// found in the LICENSE file.
5//
6// This task driver runs an arbitary command specified via command-line arguments. Its purpose is
7// to run prebuilt Bazel tests on devices where we cannot (or don't want to) run Bazel, such as
8// Raspberry Pis.
9
10package main
11
12import (
13	"context"
14	"flag"
15	"fmt"
16	"path/filepath"
17
18	"cloud.google.com/go/storage"
19	"go.skia.org/infra/go/auth"
20	sk_exec "go.skia.org/infra/go/exec"
21	"go.skia.org/infra/go/gcs"
22	"go.skia.org/infra/go/gcs/gcsclient"
23	"go.skia.org/infra/go/skerr"
24	"go.skia.org/infra/task_driver/go/lib/auth_steps"
25	"go.skia.org/infra/task_driver/go/lib/os_steps"
26	"go.skia.org/infra/task_driver/go/td"
27	"go.skia.org/skia/bazel/device_specific_configs"
28	"go.skia.org/skia/infra/bots/task_drivers/common"
29	"google.golang.org/api/option"
30)
31
32var (
33	// Required properties for this task.
34	projectId      = flag.String("project_id", "", "ID of the Google Cloud project.")
35	taskId         = flag.String("task_id", "", "ID of this task.")
36	taskName       = flag.String("task_name", "", "Name of the task.")
37	workdir        = flag.String("workdir", ".", "Working directory, the root directory of a full Skia checkout")
38	command        = flag.String("command", "", "Path to the command to run (e.g. a shell script in a directory with CAS inputs).")
39	commandWorkDir = flag.String("command_workdir", "", "Path to the working directory of the command to run (e.g. a directory with CAS inputs).")
40	kind           = flag.String("kind", "", `Test kind ("benchmark", "gm" or "unit"). Required.`)
41
42	// Flags required by all Android tests.
43	deviceSpecificBazelConfigName = flag.String("device_specific_bazel_config", "", "Name of a Bazel config found in //bazel/devicesrc.")
44
45	// Flags used by benchmark and GM tests.
46	gitCommit        = flag.String("git_commit", "", "The git hash to which the data should be associated. This will be used when changelist_id and patchset_order are not set to report data to Gold that belongs on the primary branch.")
47	changelistID     = flag.String("changelist_id", "", "Should be non-empty only when run on the CQ.")
48	patchsetOrderStr = flag.String("patchset_order", "", "Should be non-zero only when run on the CQ.")
49
50	// Flags used by GM tests.
51	label       = flag.String("bazel_label", "", "The label of the Bazel target to test (only used for GM tests)")
52	goldctlPath = flag.String("goldctl_path", "", "The path to the golctl binary on disk.")
53	tryjobID    = flag.String("tryjob_id", "", "Should be non-zero only when run on the CQ.")
54
55	// Optional flags.
56	local  = flag.Bool("local", false, "True if running locally (as opposed to on the CI/CQ)")
57	output = flag.String("o", "", "If provided, dump a JSON blob of step data to the given file. Prints to stdout if '-' is given.")
58)
59
60func main() {
61	// StartRun calls flag.Parse()
62	ctx := td.StartRun(projectId, taskId, taskName, output, local)
63	defer td.EndRun(ctx)
64
65	if *kind == "" {
66		td.Fatal(ctx, skerr.Fmt("Flag --kind is required."))
67	}
68
69	var testKind testKind
70	if *kind == "benchmark" {
71		testKind = benchmarkTest
72	} else if *kind == "gm" {
73		testKind = gmTest
74	} else if *kind == "unit" {
75		testKind = unitTest
76	} else {
77		td.Fatal(ctx, skerr.Fmt("Unknown flag --kind value: %q", *kind))
78	}
79
80	if *deviceSpecificBazelConfigName != "" {
81		if _, ok := device_specific_configs.Configs[*deviceSpecificBazelConfigName]; !ok {
82			td.Fatal(ctx, skerr.Fmt("Unknown flag --device_specific_bazel_config value: %q", *deviceSpecificBazelConfigName))
83		}
84	}
85
86	wd, err := os_steps.Abs(ctx, *workdir)
87	if err != nil {
88		td.Fatal(ctx, err)
89	}
90
91	// Make an HTTP client with the required permissions to upload to the perf.skia.org GCS bucket.
92	httpClient, _, err := auth_steps.InitHttpClient(ctx, *local, auth.ScopeReadWrite, auth.ScopeUserinfoEmail)
93	if err != nil {
94		td.Fatal(ctx, skerr.Wrap(err))
95	}
96
97	// Make a GCS client to to upload to the perf.skia.org GCS bucket.
98	store, err := storage.NewClient(ctx, option.WithHTTPClient(httpClient))
99	if err != nil {
100		td.Fatal(ctx, skerr.Wrap(err))
101	}
102	gcsClient := gcsclient.New(store, common.PerfGCSBucketName)
103
104	tdArgs := taskDriverArgs{
105		UploadToGoldArgs: common.UploadToGoldArgs{
106			BazelLabel:                *label,
107			DeviceSpecificBazelConfig: *deviceSpecificBazelConfigName,
108			GoldctlPath:               filepath.Join(wd, *goldctlPath),
109			GitCommit:                 *gitCommit,
110			ChangelistID:              *changelistID,
111			PatchsetOrder:             *patchsetOrderStr,
112			TryjobID:                  *tryjobID,
113		},
114
115		BenchmarkInfo: common.BenchmarkInfo{
116			GitCommit:     *gitCommit,
117			TaskName:      *taskName,
118			TaskID:        *taskId,
119			ChangelistID:  *changelistID,
120			PatchsetOrder: *patchsetOrderStr,
121		},
122
123		testKind:                      testKind,
124		commandPath:                   filepath.Join(wd, *command),
125		commandWorkDir:                filepath.Join(wd, *commandWorkDir),
126		deviceSpecificBazelConfigName: *deviceSpecificBazelConfigName,
127
128		gcsClient: gcsClient,
129	}
130	if testKind == benchmarkTest || testKind == gmTest {
131		tdArgs.undeclaredOutputsDir, err = os_steps.TempDir(ctx, "", "test-undeclared-outputs-dir-*")
132		if err != nil {
133			td.Fatal(ctx, err)
134		}
135	}
136
137	if err := run(ctx, tdArgs); err != nil {
138		td.Fatal(ctx, err)
139	}
140}
141
142type testKind int
143
144const (
145	benchmarkTest testKind = iota
146	gmTest
147	unitTest
148)
149
150// taskDriverArgs gathers the inputs to this task driver, and decouples the task driver's
151// entry-point function from the command line flags, which facilitates writing unit tests.
152type taskDriverArgs struct {
153	common.UploadToGoldArgs
154	common.BenchmarkInfo
155
156	commandPath          string
157	commandWorkDir       string
158	testKind             testKind
159	undeclaredOutputsDir string
160
161	// common.UploadToGoldArgs has an identical field, but it serves a different purpose.
162	deviceSpecificBazelConfigName string
163
164	gcsClient gcs.GCSClient // Only used to upload benchmark results to Perf.
165}
166
167// run is the entrypoint of this task driver.
168func run(ctx context.Context, tdArgs taskDriverArgs) error {
169	// GM tests require an output directory in which to store any PNGs produced.
170	var env []string
171	if tdArgs.testKind == benchmarkTest || tdArgs.testKind == gmTest {
172		env = append(env, fmt.Sprintf("TEST_UNDECLARED_OUTPUTS_DIR=%s", tdArgs.undeclaredOutputsDir))
173	}
174
175	var args []string
176	if tdArgs.testKind == benchmarkTest {
177		args = common.ComputeBenchmarkTestRunnerCLIFlags(tdArgs.BenchmarkInfo)
178	}
179	if tdArgs.deviceSpecificBazelConfigName != "" {
180		args = append(args, device_specific_configs.Configs[tdArgs.deviceSpecificBazelConfigName].TestRunnerArgs()...)
181	}
182
183	runCmd := &sk_exec.Command{
184		Name:       tdArgs.commandPath,
185		Args:       args,
186		Env:        env,
187		InheritEnv: true, // Makes sure any CIPD-downloaded tools are available on $PATH.
188		Dir:        tdArgs.commandWorkDir,
189		LogStdout:  true,
190		LogStderr:  true,
191	}
192	_, err := sk_exec.RunCommand(ctx, runCmd)
193	if err != nil {
194		return skerr.Wrap(err)
195	}
196
197	if tdArgs.testKind == benchmarkTest {
198		return skerr.Wrap(common.UploadToPerf(ctx, tdArgs.gcsClient, tdArgs.BenchmarkInfo, tdArgs.undeclaredOutputsDir))
199	}
200
201	if tdArgs.testKind == gmTest {
202		return skerr.Wrap(common.UploadToGold(ctx, tdArgs.UploadToGoldArgs, tdArgs.undeclaredOutputsDir))
203	}
204
205	return nil
206}
207