| // Copyright 2023 Google LLC |
| // |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| // |
| // This task driver runs an arbitary command specified via command-line arguments. Its purpose is |
| // to run prebuilt Bazel tests on devices where we cannot (or don't want to) run Bazel, such as |
| // Raspberry Pis. |
| |
| package main |
| |
| import ( |
| "context" |
| "flag" |
| "fmt" |
| "path/filepath" |
| |
| "cloud.google.com/go/storage" |
| "go.skia.org/infra/go/auth" |
| sk_exec "go.skia.org/infra/go/exec" |
| "go.skia.org/infra/go/gcs" |
| "go.skia.org/infra/go/gcs/gcsclient" |
| "go.skia.org/infra/go/skerr" |
| "go.skia.org/infra/task_driver/go/lib/auth_steps" |
| "go.skia.org/infra/task_driver/go/lib/os_steps" |
| "go.skia.org/infra/task_driver/go/td" |
| "go.skia.org/skia/bazel/device_specific_configs" |
| "go.skia.org/skia/infra/bots/task_drivers/common" |
| "google.golang.org/api/option" |
| ) |
| |
| var ( |
| // Required properties for this task. |
| projectId = flag.String("project_id", "", "ID of the Google Cloud project.") |
| taskId = flag.String("task_id", "", "ID of this task.") |
| taskName = flag.String("task_name", "", "Name of the task.") |
| workdir = flag.String("workdir", ".", "Working directory, the root directory of a full Skia checkout") |
| command = flag.String("command", "", "Path to the command to run (e.g. a shell script in a directory with CAS inputs).") |
| commandWorkDir = flag.String("command_workdir", "", "Path to the working directory of the command to run (e.g. a directory with CAS inputs).") |
| kind = flag.String("kind", "", `Test kind ("benchmark", "gm" or "unit"). Required.`) |
| |
| // Flags required by all Android tests. |
| deviceSpecificBazelConfigName = flag.String("device_specific_bazel_config", "", "Name of a Bazel config found in //bazel/devicesrc.") |
| |
| // Flags used by benchmark and GM tests. |
| 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.") |
| changelistID = flag.String("changelist_id", "", "Should be non-empty only when run on the CQ.") |
| patchsetOrderStr = flag.String("patchset_order", "", "Should be non-zero only when run on the CQ.") |
| |
| // Flags used by GM tests. |
| label = flag.String("bazel_label", "", "The label of the Bazel target to test (only used for GM tests)") |
| goldctlPath = flag.String("goldctl_path", "", "The path to the golctl binary on disk.") |
| tryjobID = flag.String("tryjob_id", "", "Should be non-zero only when run on the CQ.") |
| |
| // Optional flags. |
| local = flag.Bool("local", false, "True if running locally (as opposed to on the CI/CQ)") |
| output = flag.String("o", "", "If provided, dump a JSON blob of step data to the given file. Prints to stdout if '-' is given.") |
| ) |
| |
| func main() { |
| // StartRun calls flag.Parse() |
| ctx := td.StartRun(projectId, taskId, taskName, output, local) |
| defer td.EndRun(ctx) |
| |
| if *kind == "" { |
| td.Fatal(ctx, skerr.Fmt("Flag --kind is required.")) |
| } |
| |
| var testKind testKind |
| if *kind == "benchmark" { |
| testKind = benchmarkTest |
| } else if *kind == "gm" { |
| testKind = gmTest |
| } else if *kind == "unit" { |
| testKind = unitTest |
| } else { |
| td.Fatal(ctx, skerr.Fmt("Unknown flag --kind value: %q", *kind)) |
| } |
| |
| if *deviceSpecificBazelConfigName != "" { |
| if _, ok := device_specific_configs.Configs[*deviceSpecificBazelConfigName]; !ok { |
| td.Fatal(ctx, skerr.Fmt("Unknown flag --device_specific_bazel_config value: %q", *deviceSpecificBazelConfigName)) |
| } |
| } |
| |
| wd, err := os_steps.Abs(ctx, *workdir) |
| if err != nil { |
| td.Fatal(ctx, err) |
| } |
| |
| // Make an HTTP client with the required permissions to upload to the perf.skia.org GCS bucket. |
| httpClient, _, err := auth_steps.InitHttpClient(ctx, *local, auth.ScopeReadWrite, auth.ScopeUserinfoEmail) |
| if err != nil { |
| td.Fatal(ctx, skerr.Wrap(err)) |
| } |
| |
| // Make a GCS client to to upload to the perf.skia.org GCS bucket. |
| store, err := storage.NewClient(ctx, option.WithHTTPClient(httpClient)) |
| if err != nil { |
| td.Fatal(ctx, skerr.Wrap(err)) |
| } |
| gcsClient := gcsclient.New(store, common.PerfGCSBucketName) |
| |
| tdArgs := taskDriverArgs{ |
| UploadToGoldArgs: common.UploadToGoldArgs{ |
| BazelLabel: *label, |
| DeviceSpecificBazelConfig: *deviceSpecificBazelConfigName, |
| GoldctlPath: filepath.Join(wd, *goldctlPath), |
| GitCommit: *gitCommit, |
| ChangelistID: *changelistID, |
| PatchsetOrder: *patchsetOrderStr, |
| TryjobID: *tryjobID, |
| }, |
| |
| BenchmarkInfo: common.BenchmarkInfo{ |
| GitCommit: *gitCommit, |
| TaskName: *taskName, |
| TaskID: *taskId, |
| ChangelistID: *changelistID, |
| PatchsetOrder: *patchsetOrderStr, |
| }, |
| |
| testKind: testKind, |
| commandPath: filepath.Join(wd, *command), |
| commandWorkDir: filepath.Join(wd, *commandWorkDir), |
| deviceSpecificBazelConfigName: *deviceSpecificBazelConfigName, |
| |
| gcsClient: gcsClient, |
| } |
| if testKind == benchmarkTest || testKind == gmTest { |
| tdArgs.undeclaredOutputsDir, err = os_steps.TempDir(ctx, "", "test-undeclared-outputs-dir-*") |
| if err != nil { |
| td.Fatal(ctx, err) |
| } |
| } |
| |
| if err := run(ctx, tdArgs); err != nil { |
| td.Fatal(ctx, err) |
| } |
| } |
| |
| type testKind int |
| |
| const ( |
| benchmarkTest testKind = iota |
| gmTest |
| unitTest |
| ) |
| |
| // taskDriverArgs gathers the inputs to this task driver, and decouples the task driver's |
| // entry-point function from the command line flags, which facilitates writing unit tests. |
| type taskDriverArgs struct { |
| common.UploadToGoldArgs |
| common.BenchmarkInfo |
| |
| commandPath string |
| commandWorkDir string |
| testKind testKind |
| undeclaredOutputsDir string |
| |
| // common.UploadToGoldArgs has an identical field, but it serves a different purpose. |
| deviceSpecificBazelConfigName string |
| |
| gcsClient gcs.GCSClient // Only used to upload benchmark results to Perf. |
| } |
| |
| // run is the entrypoint of this task driver. |
| func run(ctx context.Context, tdArgs taskDriverArgs) error { |
| // GM tests require an output directory in which to store any PNGs produced. |
| var env []string |
| if tdArgs.testKind == benchmarkTest || tdArgs.testKind == gmTest { |
| env = append(env, fmt.Sprintf("TEST_UNDECLARED_OUTPUTS_DIR=%s", tdArgs.undeclaredOutputsDir)) |
| } |
| |
| var args []string |
| if tdArgs.testKind == benchmarkTest { |
| args = common.ComputeBenchmarkTestRunnerCLIFlags(tdArgs.BenchmarkInfo) |
| } |
| if tdArgs.deviceSpecificBazelConfigName != "" { |
| args = append(args, device_specific_configs.Configs[tdArgs.deviceSpecificBazelConfigName].TestRunnerArgs()...) |
| } |
| |
| runCmd := &sk_exec.Command{ |
| Name: tdArgs.commandPath, |
| Args: args, |
| Env: env, |
| InheritEnv: true, // Makes sure any CIPD-downloaded tools are available on $PATH. |
| Dir: tdArgs.commandWorkDir, |
| LogStdout: true, |
| LogStderr: true, |
| } |
| _, err := sk_exec.RunCommand(ctx, runCmd) |
| if err != nil { |
| return skerr.Wrap(err) |
| } |
| |
| if tdArgs.testKind == benchmarkTest { |
| return skerr.Wrap(common.UploadToPerf(ctx, tdArgs.gcsClient, tdArgs.BenchmarkInfo, tdArgs.undeclaredOutputsDir)) |
| } |
| |
| if tdArgs.testKind == gmTest { |
| return skerr.Wrap(common.UploadToGold(ctx, tdArgs.UploadToGoldArgs, tdArgs.undeclaredOutputsDir)) |
| } |
| |
| return nil |
| } |