| // Copyright 2025 The ChromiumOS Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // Package driver implements drivers to execute tests. |
| package driver |
| |
| import ( |
| "fmt" |
| "log" |
| "os" |
| "strconv" |
| "strings" |
| |
| "go.chromium.org/chromiumos/config/go/test/api" |
| ) |
| |
| const ( |
| invocationTimeout = "18h" |
| maxRuntime = "18h" |
| retryFailedRuns = true |
| resultsUpload = true |
| defaultGlobalLogPath = "/tmp" |
| GIT_MAIN_AL_DEV = "git_main-al-dev" |
| GIT_MAIN = "git_main" |
| tfprefix = "tradefed." |
| ) |
| |
| var skipDownloadArtifacts = []string{"TESTS_ZIP", "DEVICE_IMAGE", "BASEBAND"} |
| |
| // TODO: find better way to discover or pass in board's architecture. |
| var armBoards = []string{"corsola"} |
| |
| func getArchFromBoard(targetBoard string) string { |
| arch := "x86" |
| for _, board := range armBoards { |
| if strings.Contains(strings.ToLower(targetBoard), board) { |
| arch = "arm" |
| } |
| } |
| return arch |
| } |
| |
| func getGlobalLogPath() string { |
| logPath := os.Getenv("GLOBAL_LOG_PATH") |
| if len(logPath) <= 0 { |
| logPath = defaultGlobalLogPath |
| os.Setenv("GLOBAL_LOG_PATH", logPath) |
| } |
| return logPath |
| } |
| |
| func extractBuildInfoFromExecutionMetadata(metadata *api.ExecutionMetadata) (branch string, target string, build string) { |
| if branchFlag, err := extractMetadataFlag(metadata, "branch"); err == nil { |
| branch = branchFlag |
| } |
| if targetFlag, err := extractMetadataFlag(metadata, "build_flavor"); err == nil { |
| target = targetFlag |
| } |
| if buildIdFlag, err := extractMetadataFlag(metadata, "build_id"); err == nil { |
| build = buildIdFlag |
| } |
| |
| return |
| } |
| |
| func extractTestInfoFromExecutionMetadata(metadata *api.ExecutionMetadata) (branch string, target string, build string) { |
| if branchFlag, err := extractMetadataFlag(metadata, "extra_branch"); err == nil { |
| branch = branchFlag |
| } |
| if targetFlag, err := extractMetadataFlag(metadata, "extra_build_flavor"); err == nil { |
| target = targetFlag |
| } |
| if buildIdFlag, err := extractMetadataFlag(metadata, "extra_build"); err == nil { |
| build = buildIdFlag |
| } |
| |
| return |
| } |
| |
| func extractBuildInfoFromTest(test *api.TestCase, metadata *api.ExecutionMetadata, board string) (branch string, target string, build string) { |
| for _, t := range test.Tags { |
| if strings.HasPrefix(t.Value, "target_build=") { |
| tags := strings.Split(strings.TrimPrefix(t.Value, "target_build="), ",") |
| var targetCandidate, buildCandidate, abi string |
| for _, tag := range tags { |
| if strings.HasPrefix(tag, "target:") { |
| targetCandidate = strings.TrimPrefix(tag, "target:") |
| } |
| if strings.HasPrefix(tag, "build_id:") { |
| buildCandidate = strings.TrimPrefix(tag, "build_id:") |
| } |
| if strings.HasPrefix(tag, "abi:") { |
| abi = strings.TrimPrefix(tag, "abi:") |
| } |
| } |
| if abi != "" && strings.Contains(abi, getArchFromBoard(board)) { |
| build = buildCandidate |
| target = targetCandidate |
| } |
| } |
| if strings.HasPrefix(t.Value, "branch:") { |
| branch = strings.TrimPrefix(t.Value, "branch:") |
| } |
| if strings.HasPrefix(t.Value, "target:") && strings.Contains(t.Value, getArchFromBoard(board)) && target == "" { |
| target = strings.TrimPrefix(t.Value, "target:") |
| } |
| if strings.HasPrefix(t.Value, "build_id:") && build == "" { |
| if getArchFromBoard(board) == "arm" && branch == "git_main" { |
| build = "12681648" |
| } else { |
| build = strings.TrimPrefix(t.Value, "build_id:") |
| } |
| } |
| } |
| return |
| } |
| |
| func formatTestName(fqTestName string) string { |
| testName := fqTestName |
| testName = strings.TrimPrefix(testName, tfprefix) |
| |
| // First, trim test suite prefix for all known suites. |
| for _, suite := range knownSuites { |
| testName = strings.TrimPrefix(testName, suite+".") |
| } |
| |
| // We do not need to strip any other info as we force the naming schema via test-finder. |
| return fmt.Sprintf("\"%s\"", testName) |
| } |
| |
| func extractMetadataFlag(metadata *api.ExecutionMetadata, flagName string) (string, error) { |
| if metadata != nil && len(metadata.Args) > 0 { |
| for _, arg := range metadata.Args { |
| if arg.Flag == flagName { |
| return arg.Value, nil |
| } |
| } |
| } |
| return "", fmt.Errorf("flag %s for found in test metadata", flagName) |
| } |
| |
| func buildResultReportingArgs(logger *log.Logger, metadata *api.ExecutionMetadata, args map[string]string, board, model string) []string { |
| cmd := []string{} |
| |
| uploadToAnts := true |
| if skipAntsUploadFlag, err := extractMetadataFlag(metadata, "skip_ants_upload"); err == nil { |
| logger.Printf("Skip upload to AnTS: %s", skipAntsUploadFlag) |
| if b, err := strconv.ParseBool(skipAntsUploadFlag); err == nil && b { |
| uploadToAnts = false |
| } |
| } |
| |
| if resultsUpload && uploadToAnts { |
| cmd = append(cmd, "--ants-result-reporter:create-local-invocation", |
| "--ants-result-reporter:no-enable-sponge", |
| "--ants-result-reporter:save-metric-file") |
| |
| // Extract AnTS invocation id and WorkUnit ID from execution metadata. |
| if AnTsInvID, err := extractMetadataFlag(metadata, "ants_invocation_id"); err == nil { |
| cmd = append(cmd, "--invocation-data", fmt.Sprintf("invocation_id=%s", AnTsInvID)) |
| logger.Printf("Got invocation id: %s", AnTsInvID) |
| } |
| if AnTsWorkUnitID, err := extractMetadataFlag(metadata, "ants_work_unit_id"); err == nil { |
| cmd = append(cmd, "--invocation-data", fmt.Sprintf("work_unit_id=%s", AnTsWorkUnitID)) |
| logger.Printf("Got work unit id: %s", AnTsWorkUnitID) |
| } |
| if atpName, err := extractMetadataFlag(metadata, "atp_test_name"); err == nil { |
| cmd = append(cmd, "--invocation-data", fmt.Sprintf("atp_test_name=%s", atpName)) |
| logger.Printf("Got ATP test name: %s", atpName) |
| } |
| value, ok := args["crystalball_ingest"] |
| if ok { |
| if strings.ToLower(value) == "true" { |
| cmd = append(cmd, "--invocation-data", "invocation-property=crystalball_ingest:yes") |
| cmd = append(cmd, "--invocation-data", fmt.Sprintf("invocation-property=run_target:%s_%s", board, model)) |
| } |
| } |
| |
| // Also allow the flag to be passed in directly through the args. |
| for k, v := range args { |
| if k == "invocation-data" { |
| cmd = append(cmd, fmt.Sprintf("--%s", k), v) |
| cmd = append(cmd, "--invocation-data", fmt.Sprintf("invocation-property=run_target:%s_%s", board, model)) |
| |
| } |
| } |
| extra_branch, _ := extractMetadataFlag(metadata, "extra_branch") |
| extra_target, _ := extractMetadataFlag(metadata, "extra_target") |
| extra_build, _ := extractMetadataFlag(metadata, "extra_build") |
| if extra_branch != "" && extra_target != "" && extra_build != "" { |
| extraBuilds := fmt.Sprintf(`extra_builds=[{"build_id": "%s",`+ |
| `"build_target": "%s", "build_flavor": "%s", "build_platform": "linux",`+ |
| `"build_type": null, "branch": "%s", "build_alias": null, "attempt_id":`+ |
| `"latest", "build_os": "linux"}]`, extra_build, extra_target, |
| extra_target, extra_branch) |
| cmd = append(cmd, "--invocation-data", extraBuilds) |
| } |
| } |
| |
| if envName, err := extractMetadataFlag(metadata, "android-build-environment"); err == nil { |
| cmd = append(cmd, "--invocation-data", fmt.Sprintf("android-build-environment=%s", envName)) |
| logger.Printf("Got Android build environment: %s", envName) |
| } else { |
| // Use production build env as default, if not specified otherwise. |
| cmd = append(cmd, "--invocation-data", "android-build-environment=prod") |
| } |
| |
| return cmd |
| } |
| |
| func extractTestCaseName(testName string) string { |
| testCaseName := testName |
| if strings.HasPrefix(testCaseName, tfprefix) { |
| testCaseName = strings.TrimPrefix(testCaseName, tfprefix) |
| // Also trimming the suite name. |
| testCaseName = strings.TrimLeft(testCaseName, ".") |
| } |
| return testCaseName |
| } |