blob: 250aed45a4d54fe1782463ea249ad03ed055042f [file] [log] [blame]
// Copyright 2021 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Package cli implements the executionservice server
package cli
import (
"context"
"errors"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"go.chromium.org/chromiumos/config/go/test/api"
"go.chromium.org/chromiumos/test/execution/cmd/cros-test/internal/driver"
statuserrors "go.chromium.org/chromiumos/test/execution/errors"
"go.chromium.org/chromiumos/test/util/finder"
"google.golang.org/protobuf/encoding/protojson"
)
// driverToTestsMapping builds a map between test and its driver.
func driverToTestsMapping(logger *log.Logger, mdList []*api.TestCaseMetadata) (map[driver.Driver][]*api.TestCaseMetadata, error) {
tastDriver := driver.NewTastDriver(logger)
tautoDriver := driver.NewTautoDriver(logger)
gtestDriver := driver.NewGtestDriver(logger)
crosierDriver := driver.NewCrosierDriver(logger)
moblyDriver := driver.NewMoblyDriver(logger)
tradefedDriver := driver.NewTradefedDriver(logger)
driverToTests := make(map[driver.Driver][]*api.TestCaseMetadata)
for _, md := range mdList {
if md.TestCase == nil {
return nil, statuserrors.NewStatusError(statuserrors.InvalidArgument,
fmt.Errorf("missing test case information %v", md))
}
if md.TestCaseExec == nil || md.TestCaseExec.TestHarness == nil {
return nil, statuserrors.NewStatusError(statuserrors.InvalidArgument,
fmt.Errorf("test case %v does not have test harness information", md.TestCase.Name))
}
if md.TestCaseExec.TestHarness.GetTast() != nil {
driverToTests[tastDriver] = append(driverToTests[tastDriver], md)
} else if md.TestCaseExec.TestHarness.GetTauto() != nil {
driverToTests[tautoDriver] = append(driverToTests[tautoDriver], md)
} else if md.TestCaseExec.TestHarness.GetGtest() != nil {
driverToTests[gtestDriver] = append(driverToTests[gtestDriver], md)
} else if md.TestCaseExec.TestHarness.GetCrosier() != nil {
driverToTests[crosierDriver] = append(driverToTests[crosierDriver], md)
} else if md.TestCaseExec.TestHarness.GetMobly() != nil {
driverToTests[moblyDriver] = append(driverToTests[moblyDriver], md)
} else if md.TestCaseExec.TestHarness.GetTradefed() != nil {
driverToTests[tradefedDriver] = append(driverToTests[tradefedDriver], md)
} else {
return nil, statuserrors.NewStatusError(statuserrors.InvalidArgument,
errors.New("manual harness has not been supported"))
}
}
return driverToTests, nil
}
// runTests runs the requested tests.
func runTests(ctx context.Context, logger *log.Logger, resultRootDir, tlwAddr string, metadataList *api.TestCaseMetadataList, req *api.CrosTestRequest) (*api.CrosTestResponse, error) {
md := metadataList.GetValues()
shouldPullGcsMD := isMoblySuite(req)
if shouldPullGcsMD {
src, err := finder.GetSourceData(context.Background(), "mobly_priv_artifacts/out")
if err != nil {
fmt.Printf("err %s", err)
}
metadata := finder.TranslateMoblySrcToMetadata(src)
md = append(md, metadata...)
}
matchedMdList, err := finder.MatchedTestsForSuites(md, req.TestSuites)
if err != nil {
return nil, statuserrors.NewStatusError(statuserrors.InvalidArgument,
fmt.Errorf("failed to match test metadata: %v", err))
}
// Write all matching test metadata to the results directory.
const mdFilename = "test_metadata.json"
if err := os.MkdirAll(resultRootDir, 0755); err != nil {
return nil, statuserrors.NewStatusError(statuserrors.IOCreateError,
fmt.Errorf("failed to create result root directory %v", resultRootDir))
}
defer exec.Command("chmod", "-R", "777", resultRootDir).Run()
mdPath := filepath.Join(resultRootDir, mdFilename)
if err := writeMetadata(mdPath, &api.TestCaseMetadataList{Values: matchedMdList}); err != nil {
return nil, err
}
if out, err := exec.Command("gcloud", "auth", "list", "--format", "value(account)").CombinedOutput(); err != nil {
logger.Println("Unable to run gcloud auth list: ", string(out))
} else {
logger.Println("The output of gcloud auth list: ", string(out))
}
driversToTests, err := driverToTestsMapping(logger, matchedMdList)
if err != nil {
return nil, err
}
allRspn := api.CrosTestResponse{}
for driver, tests := range driversToTests {
resultsDir := filepath.Join(resultRootDir, driver.Name())
// Make sure the result directory exists.
if err := os.MkdirAll(resultsDir, 0755); err != nil {
return nil, statuserrors.NewStatusError(statuserrors.IOCreateError,
fmt.Errorf("failed to create result directory %v", resultsDir))
}
rspn, err := driver.RunTests(ctx, resultsDir, req, tlwAddr, tests)
if err != nil {
return nil, err
}
allRspn.TestCaseResults = append(allRspn.TestCaseResults, rspn.TestCaseResults...)
allRspn.GivenTestResults = append(allRspn.GivenTestResults, rspn.GivenTestResults...)
}
return &allRspn, nil
}
// readInput reads an execution_service json file and returns a pointer to RunTestsRequest.
func readInput(fileName string) (*api.CrosTestRequest, error) {
f, err := os.ReadFile(fileName)
if err != nil {
return nil, statuserrors.NewStatusError(statuserrors.IOAccessError,
fmt.Errorf("failed to read file %v: %v", fileName, err))
}
req := api.CrosTestRequest{}
umrsh := protojson.UnmarshalOptions{DiscardUnknown: true}
if err := umrsh.Unmarshal(f, &req); err != nil {
return nil, statuserrors.NewStatusError(statuserrors.UnmarshalError,
fmt.Errorf("failed to unmarshal file %v: %v", fileName, err))
}
return &req, nil
}
// writeMetadata writes a TestCaseMetadataList json.
func writeMetadata(outputPath string, mdList *api.TestCaseMetadataList) error {
json, err := protojson.Marshal(mdList)
if err != nil {
return statuserrors.NewStatusError(statuserrors.MarshalError,
fmt.Errorf("failed to marshall metadata list: %v", err))
}
if err := os.WriteFile(outputPath, json, 0666); err != nil {
return statuserrors.NewStatusError(statuserrors.IOCreateError,
fmt.Errorf("failed to write file %v: %v", outputPath, err))
}
return nil
}
// writeOutput writes a RunTestsResponse json.
func writeOutput(output string, resp *api.CrosTestResponse) error {
json, err := protojson.Marshal(resp)
if err != nil {
return statuserrors.NewStatusError(statuserrors.MarshalError,
fmt.Errorf("failed to marshall response: %v", err))
}
if err := os.WriteFile(output, json, 0666); err != nil {
return statuserrors.NewStatusError(statuserrors.IOCreateError,
fmt.Errorf("failed to write file %v: %v", output, err))
}
return nil
}
func isMoblySuite(req *api.CrosTestRequest) bool {
for _, suite := range req.GetTestSuites() {
md := suite.GetExecutionMetadata()
if mdFlagPresent(md, "moblySuite") {
return true
}
}
return false
}
func mdFlagPresent(metadata *api.ExecutionMetadata, flagName string) bool {
if metadata != nil && len(metadata.Args) > 0 {
for _, arg := range metadata.Args {
if arg.Flag == flagName {
return true
}
}
}
return false
}