blob: 658290706d1fa74e06b2d2533e0b378170fdefe1 [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 main implements the executionservice server
package main
import (
"context"
"errors"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"github.com/golang/protobuf/jsonpb"
"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"
)
// 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)
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 {
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) {
matchedMdList, err := finder.MatchedTestsForSuites(metadataList.Values, 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
}
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...)
}
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.Open(fileName)
if err != nil {
return nil, statuserrors.NewStatusError(statuserrors.IOAccessError,
fmt.Errorf("failed to read file %v: %v", fileName, err))
}
req := api.CrosTestRequest{}
umrsh := jsonpb.Unmarshaler{}
umrsh.AllowUnknownFields = 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 {
f, err := os.Create(outputPath)
if err != nil {
return statuserrors.NewStatusError(statuserrors.IOCreateError,
fmt.Errorf("failed to create file %v: %v", outputPath, err))
}
m := jsonpb.Marshaler{}
if err := m.Marshal(f, mdList); err != nil {
return statuserrors.NewStatusError(statuserrors.MarshalError,
fmt.Errorf("failed to marshall metadata list to file %v: %v", outputPath, err))
}
return nil
}
// writeOutput writes a RunTestsResponse json.
func writeOutput(output string, resp *api.CrosTestResponse) error {
f, err := os.Create(output)
if err != nil {
return statuserrors.NewStatusError(statuserrors.IOCreateError,
fmt.Errorf("fail to create file %v: %v", output, err))
}
m := jsonpb.Marshaler{}
if err := m.Marshal(f, resp); err != nil {
return statuserrors.NewStatusError(statuserrors.MarshalError,
fmt.Errorf("failed to marshall response to file %v: %v", output, err))
}
return nil
}