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