blob: c7f67c0285b6413b39707f92d8db4295c273b3b2 [file] [log] [blame]
// Copyright 2023 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
import (
"context"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"regexp"
"runtime/debug"
"sync"
c "go.chromium.org/chromiumos/test/util/adb"
"go.chromium.org/chromiumos/config/go/test/api"
labapi "go.chromium.org/chromiumos/config/go/test/lab/api"
"go.chromium.org/chromiumos/test/execution/cmd/cros-test/internal/common"
"go.chromium.org/chromiumos/test/execution/cmd/cros-test/internal/device"
)
// MoblyDriver runs Mobly tests.
type MoblyDriver struct {
logger *log.Logger
}
// NewMoblyDriver creates a new Mobly driver.
func NewMoblyDriver(logger *log.Logger) *MoblyDriver {
return &MoblyDriver{logger: logger}
}
// Name returns the name of the driver.
func (md *MoblyDriver) Name() string {
return "mobly"
}
// buildMoblyCommand constructs the Mobly execution command.
func buildMoblyCommand(logger *log.Logger, test *api.TestCaseMetadata) (*exec.Cmd, error) {
parFile, ok := test.GetTestCaseInfo().GetExtraInfo()["executable_name"]
if !ok {
return nil, fmt.Errorf("missing 'executable_name' in test metadata")
}
par := filepath.Join("/usr/local/mobly", parFile)
args := []string{
"--", // Separator between global and test-specific arguments.
"-c", "/usr/local/mobly/test_config.yml",
"--tests", test.GetTestCase().GetName(),
}
// Check if the executable supports the ipv6 flag. if it does, we disable
// enforcing kernel ipv6 support as our lab does not support this generally.
// This is currently relevant for the g3 executable.
// See b/390240237 for more details.
helpCmd := exec.Command(par, "--helpfull")
out, _ := helpCmd.CombinedOutput()
flagRegex := regexp.MustCompile(`enforce_kernel_ipv6_support`)
if flagRegex.MatchString(string(out)) {
args = append([]string{"--enforce_kernel_ipv6_support=false"}, args...)
}
cmd := exec.Command(par, args...)
return cmd, nil
}
// runMoblyTest executes a Mobly test.
func runMoblyTest(ctx context.Context, logger *log.Logger, test *api.TestCaseMetadata, serials []string, metadata []*api.Arg, devices []*labapi.Dut) error {
config := NewMoblyConfig(logger, serials, metadata, devices)
if err := config.Write(logger, "/usr/local/mobly"); err != nil {
return fmt.Errorf("generating Mobly config: %w", err)
}
cmd, err := buildMoblyCommand(logger, test)
if err != nil {
return fmt.Errorf("building Mobly command: %w", err)
}
logger.Printf("Running Mobly: %s", cmd.String())
return launchAndReadMobly(cmd, logger)
}
// processMoblyResults processes the results of a Mobly test.
func (md *MoblyDriver) processMoblyResults(resultsDir, testName string, resp *api.CrosTestResponse) error {
defer func() {
if r := recover(); r != nil {
md.logger.Printf("Panic during Mobly result processing: %v\n%s", r, debug.Stack())
}
}()
rd := filepath.Join(resultsDir, "LocalTestBed")
resultsPath := filepath.Join(rd, "latest", "test_summary.yaml")
moblyResultBytes, err := os.ReadFile(resultsPath)
if err != nil {
return fmt.Errorf("reading Mobly results: %w", err)
}
testCaseResults, err := common.TranslateMoblyResults(moblyResultBytes, rd)
if err != nil {
return fmt.Errorf("translating Mobly results: %w", err)
}
resp.TestCaseResults = append(resp.TestCaseResults, testCaseResults...)
return nil
}
// RunTests executes Mobly tests.
func (md *MoblyDriver) RunTests(ctx context.Context, resultsDir string, req *api.CrosTestRequest, tlwAddr string, tests []*api.TestCaseMetadata) (*api.CrosTestResponse, error) {
resp := &api.CrosTestResponse{}
serials, err := device.DerviceSerials(req)
if err != nil {
return nil, fmt.Errorf("getting device serials: %w", err)
}
metadata, _, err := common.UnpackMetadata(req)
if err != nil {
return nil, fmt.Errorf("unpacking metadata: %w", err)
}
metadata = append(metadata, common.ExtraArgs(req)...)
if err := c.SetupAdbConnections(md.logger, serials); err != nil {
return nil, fmt.Errorf("setting up ADB connections: %w", err)
}
defer func() {
for _, s := range serials {
if err := c.TeardownAdb(md.logger, s); err != nil {
md.logger.Printf("Tearing down ADB connection to %s: %s", s, err)
}
}
}()
exit := make(chan struct{})
defer close(exit)
c.KeepAdbAlive(md.logger, serials, exit)
for _, test := range tests {
resDir := filepath.Join("/tmp", "test", "results", "mobly", test.GetTestCase().GetName())
os.Setenv("MOBLY_LOGPATH", resDir)
if err := runMoblyTest(ctx, md.logger, test, serials, metadata, device.Devices(req)); err != nil {
return nil, fmt.Errorf("running Mobly test: %w", err)
}
if err := md.processMoblyResults(resDir, test.GetTestCase().GetName(), resp); err != nil {
return nil, fmt.Errorf("processing Mobly Results: %w", err)
}
}
return resp, nil
}
// launchAndReadMobly launches a Mobly command and reads its output.
func launchAndReadMobly(cmd *exec.Cmd, logger *log.Logger) error {
stderr, err := cmd.StderrPipe()
if err != nil {
return fmt.Errorf("getting stderr pipe: %w", err)
}
stdout, err := cmd.StdoutPipe()
if err != nil {
return fmt.Errorf("getting stdout pipe: %w", err)
}
if err := cmd.Start(); err != nil {
return fmt.Errorf("starting Mobly command: %w", err)
}
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
common.TestScanner(stderr, logger, "Mobly")
}()
go func() {
defer wg.Done()
common.TestScanner(stdout, logger, "Mobly")
}()
wg.Wait()
return nil
}