blob: 78e031d12be6efa82c06fad2360a7157bf237642 [file] [log] [blame]
// Copyright 2021 The Chromium OS Authors. All rights reserved.
// 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 (
"bufio"
"context"
"fmt"
"log"
"os/exec"
"sync"
"go.chromium.org/chromiumos/config/go/test/api"
"chromiumos/test/execution/cmd/cros-test/internal/device"
"chromiumos/test/execution/cmd/cros-test/internal/tastrpc"
"chromiumos/test/execution/errors"
)
// TastDriver runs tast and report its results.
type TastDriver struct {
// logger provides logging service.
logger *log.Logger
}
// NewTastDriver creates a new driver to run tast tests.
func NewTastDriver(logger *log.Logger) *TastDriver {
return &TastDriver{
logger: logger,
}
}
// Name returns the name of the driver.
func (td *TastDriver) Name() string {
return "tast"
}
// RunTests drives a test framework to execute tests.
func (td *TastDriver) RunTests(ctx context.Context, resultsDir string, req *api.CrosTestRequest, tlwAddr string, tests []*api.TestCaseMetadata) (*api.CrosTestResponse, error) {
primary := req.Primary
companions := req.Companions
testNamesToIds := getTestNamesToIds(tests)
testNames := getTestNames(tests)
reportServer, err := tastrpc.NewReportsServer(0, testNames, testNamesToIds, resultsDir)
if err != nil {
return nil, errors.NewStatusError(errors.ServerStartingError,
fmt.Errorf("failed to create tast report server: %v", err))
}
defer reportServer.Stop()
addr, err := device.Address(primary)
if err != nil {
return nil, errors.NewStatusError(errors.InvalidArgument,
fmt.Errorf("cannot get address from primary device: %v", primary))
}
var companionAddrs []string
for _, c := range companions {
address, err := device.Address(c)
if err != nil {
return nil, errors.NewStatusError(errors.InvalidArgument,
fmt.Errorf("cannot get address from companion device: %v", c))
}
companionAddrs = append(companionAddrs, address)
}
args := newTastArgs(addr, companionAddrs, testNames, resultsDir, tlwAddr, reportServer.Address())
// Run tast.
cmd := exec.Command("/usr/bin/tast", genArgList(args)...)
stderr, err := cmd.StderrPipe()
if err != nil {
return nil, fmt.Errorf("failed to capture tast stderr: %v", err)
}
stdout, err := cmd.StdoutPipe()
if err != nil {
td.logger.Println("Failed to capture tast stdout: ", err)
return nil, errors.NewStatusError(errors.IOCaptureError,
fmt.Errorf("failed to capture tast stdout: %v", err))
}
td.logger.Println("Running Tast ", cmd.String())
if err := cmd.Start(); err != nil {
td.logger.Println("Failed to run tast: ", err)
return nil, errors.NewStatusError(errors.CommandStartingError,
fmt.Errorf("failed to run tast: %v", err))
}
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
scanner := bufio.NewScanner(stderr)
for scanner.Scan() {
td.logger.Printf("[tast] %v", scanner.Text())
}
}()
go func() {
defer wg.Done()
scanner := bufio.NewScanner(stdout)
for scanner.Scan() {
td.logger.Printf("[tast] %v", scanner.Text())
}
}()
wg.Wait()
MissingTestErrMsg := ""
if err := cmd.Wait(); err != nil {
td.logger.Println("Failed to run tast: ", err)
MissingTestErrMsg = fmt.Sprintf("Test did not run due to %s", err)
return nil, errors.NewStatusError(errors.CommandExitError,
fmt.Errorf("tast exited with error: %v", err))
}
testResults := reportServer.TestsReports()
missingResults := reportServer.MissingTestsReports(MissingTestErrMsg)
results := append(testResults, missingResults...)
reportErrors := reportServer.Errors()
if len(reportErrors) > 0 {
for _, e := range reportErrors {
td.logger.Printf("%v\n", e)
}
return &api.CrosTestResponse{TestCaseResults: results}, reportErrors[len(reportErrors)-1]
}
return &api.CrosTestResponse{TestCaseResults: results}, nil
}
// Command name and flag names.
const (
runSubcommand = "run"
verboseFlag = "-verbose"
logTimeFlag = "-logtime"
sshRetriesFlag = "-sshretries"
downloadDataFlag = "-downloaddata"
buildFlag = "-build"
remoteBundlerDirFlag = "-remotebundledir"
remoteDataDirFlag = "-remotedatadir"
remoteRunnerFlag = "-remoterunner"
defaultVarsDirFlag = "-defaultvarsdir"
downloadPrivateBundlesFlag = "-downloadprivatebundles"
devServerFlag = "-devservers"
resultsDirFlag = "-resultsdir"
tlwServerFlag = "-tlwserver"
waitUntilReadyFlag = "-waituntilready"
timeOutFlag = "-timeout"
keyfileFlag = "-keyfile"
reportsServerFlag = "-reports_server"
companionDUTFlag = "-companiondut"
)
// runArgs stores arguments to invoke Tast
type runArgs struct {
target string // The url for the target machine.
patterns []string // The names of test to be run.
tastFlags map[string]string // The flags for tast.
runFlags map[string]string // The flags for tast run command.
companions []string // The companion DUTs to be used for testing.
}
// newTastArgs created an argument structure for invoking tast
func newTastArgs(dut string, companionDuts, tests []string, resultsDir, tlwAddress, rsAddress string) *runArgs {
downloadPrivateBundles := "false"
// Change downloadPrivateBundlesFlag to "true" if tlwServer is specified.
if tlwAddress != "" {
downloadPrivateBundles = "true"
}
return &runArgs{
target: dut,
tastFlags: map[string]string{
verboseFlag: "true",
logTimeFlag: "false",
},
runFlags: map[string]string{
sshRetriesFlag: "2",
downloadDataFlag: "batch",
buildFlag: "false",
downloadPrivateBundlesFlag: downloadPrivateBundles,
timeOutFlag: "3000",
resultsDirFlag: resultsDir,
reportsServerFlag: rsAddress,
tlwServerFlag: tlwAddress,
},
patterns: tests, // TO-DO Support Tags
companions: companionDuts,
}
}
// genArgList generates argument list for invoking tast
func genArgList(args *runArgs) (argList []string) {
for flag, value := range args.tastFlags {
argList = append(argList, fmt.Sprintf("%v=%v", flag, value))
}
argList = append(argList, runSubcommand)
for flag, value := range args.runFlags {
argList = append(argList, fmt.Sprintf("%v=%v", flag, value))
}
for i, c := range args.companions {
// example: -companiondut=cd1:127.0.0.1:2222
argList = append(argList, fmt.Sprintf("%v=cd%v:%v", companionDUTFlag, i+1, c))
}
argList = append(argList, args.target)
argList = append(argList, args.patterns...)
return argList
}