blob: 3b8de3f6e5a177a4ace8a6b2bf95465575d03761 [file] [log] [blame]
// Copyright 2018 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 autotest provides a Go API for interacting with Autotest.
//
// This package provides a very low level API with no business logic.
// This is to keep the bug surface small and keep the business logic
// clearly separate.
package autotest
import (
"fmt"
"io"
"log"
"os/exec"
"path/filepath"
"strconv"
"strings"
)
const autoservRelpath = "server/autoserv"
// AutoservArgs is the arguments for creating an autoserv command.
type AutoservArgs struct {
// Args is split with shlex.split by autoserv.
Args string
Cleanup bool
ClientTest bool
CollectCrashinfo bool
ControlName string
ExecutionTag string
HostInfoSubDir string
Hosts []string
JobLabels []string
JobName string
JobOwner string
Lab bool
LocalOnlyHostInfo bool
NoTee bool
ParentJobID int
Provision bool
Repair bool
RequireSSP bool
Reset bool
ResultsDir string
TestSourceBuild string
UseExistingResults bool
Verbose bool
Verify bool
VerifyJobRepoURL bool
WritePidfile bool
ControlFile string
}
// AutoservCommand returns the Cmd struct to execute autoserv with the
// given arguments.
func AutoservCommand(c Config, cmd *AutoservArgs) *exec.Cmd {
args := make([]string, 0, 20)
if cmd.Args != "" {
args = append(args, "--args", cmd.Args)
}
if cmd.Cleanup {
args = append(args, "--cleanup")
}
if cmd.ClientTest {
args = append(args, "-c")
} else {
// This is only used to check that it is not passed along with -c.
args = append(args, "-s")
}
if cmd.CollectCrashinfo {
args = append(args, "--collect-crashinfo")
}
if cmd.ControlName != "" {
args = append(args, "--control-name", cmd.ControlName)
}
if cmd.ExecutionTag != "" {
args = append(args, "-P", cmd.ExecutionTag)
}
if cmd.HostInfoSubDir != "" {
args = append(args, "--host-info-subdir", cmd.HostInfoSubDir)
}
if len(cmd.Hosts) != 0 {
args = append(args, "-m", strings.Join(cmd.Hosts, ","))
}
if len(cmd.JobLabels) != 0 {
args = append(args, "--job-labels", strings.Join(cmd.JobLabels, ","))
}
if cmd.JobName != "" {
args = append(args, "-l", cmd.JobName)
}
if cmd.JobOwner != "" {
args = append(args, "-u", cmd.JobOwner)
}
if cmd.Lab {
args = append(args, "--lab", "True")
}
if cmd.LocalOnlyHostInfo {
// autoserv bool args require values.
args = append(args, "--local-only-host-info", "True")
}
if cmd.NoTee {
args = append(args, "-n")
}
if cmd.ParentJobID != 0 {
args = append(args, fmt.Sprintf("--parent_job_id=%d", cmd.ParentJobID))
}
if cmd.Provision {
args = append(args, "--provision")
}
if cmd.Repair {
args = append(args, "-R")
}
if cmd.RequireSSP {
args = append(args, "--require-ssp")
}
if cmd.Reset {
args = append(args, "--reset")
}
if cmd.ResultsDir != "" {
args = append(args, "-r", cmd.ResultsDir)
}
if cmd.TestSourceBuild != "" {
args = append(args, "--test_source_build", cmd.TestSourceBuild)
}
if cmd.UseExistingResults {
args = append(args, "--use-existing-results")
}
if cmd.Verbose {
args = append(args, "--verbose")
}
if cmd.Verify {
args = append(args, "-v")
}
if cmd.VerifyJobRepoURL {
args = append(args, "--verify_job_repo_url")
}
if cmd.WritePidfile {
args = append(args, "-p")
}
// The reason for the following behavior is due to autoserv argument parsing.
// autoserv will call shlex.split on the --args value and
// append those onto the positional parameters.
// Then, autoserv takes the first positional parameter as the control file name.
// For all of the following, autoserv uses "foo" as the control file:
//
// autoserv [...] --args "foo bar"
// autoserv [...] -- foo bar
// autoserv [...] --args "spam eggs" -- foo bar
//
// Thus, if ControlFile is unset but Args is Set, we need to
// prepend a placeholder control file.
if cmd.ControlFile != "" || cmd.Args != "" {
args = append(args, cmd.ControlFile)
}
return command(c, autoservRelpath, args...)
}
const tkoRelpath = "tko/parse"
// ParseArgs contains arguments for ParseCommand.
type ParseArgs struct {
Level int
RecordDuration bool
Reparse bool
SingleDir bool
SuiteReport bool
WritePidfile bool
ResultsDir string
}
// ParseCommand returns the Cmd struct to execute tko/parse with the
// given arguments.
func ParseCommand(c Config, cmd *ParseArgs) *exec.Cmd {
args := make([]string, 0, 10)
args = append(args, "-l", strconv.Itoa(cmd.Level))
if cmd.RecordDuration {
args = append(args, "--record-duration")
}
if cmd.Reparse {
args = append(args, "-r")
}
if cmd.SingleDir {
args = append(args, "-o")
}
if cmd.SuiteReport {
args = append(args, "--suite-report")
}
if cmd.WritePidfile {
args = append(args, "--write-pidfile")
}
if cmd.ResultsDir != "" {
args = append(args, cmd.ResultsDir)
}
return command(c, tkoRelpath, args...)
}
const dutPreparationRelPath = "site_utils/deployment/prepare/main.py"
// DutPreparationArgs contains arguments for DutPreparationCommand.
type DutPreparationArgs struct {
Hostname string
ResultsDir string
HostInfoFile string
Actions []string
}
// DutPreparationCommand returns the Cmd struct to execute DUT preparation
// script with the given arguments.
func DutPreparationCommand(c Config, cmd *DutPreparationArgs) *exec.Cmd {
args := make([]string, 0, 10)
if cmd.Hostname != "" {
args = append(args, "--hostname", cmd.Hostname)
}
if cmd.ResultsDir != "" {
args = append(args, "--results-dir", cmd.ResultsDir)
}
if cmd.HostInfoFile != "" {
args = append(args, "--host-info-file", cmd.HostInfoFile)
}
args = append(args, cmd.Actions...)
return command(c, dutPreparationRelPath, args...)
}
const auditTaskRelPath = "site_utils/admin_audit/main.py"
// AuditTaskArgs contains arguments for AdminAudit task.
type AuditTaskArgs struct {
Hostname string
ResultsDir string
HostInfoFile string
Actions []string
}
// AuditTaskCommand returns the Cmd struct to execute DUT audit script with the given arguments.
func AuditTaskCommand(c Config, cmd *AuditTaskArgs) *exec.Cmd {
args := make([]string, 0, 10)
if cmd.Hostname != "" {
args = append(args, "--hostname", cmd.Hostname)
}
if cmd.ResultsDir != "" {
args = append(args, "--results-dir", cmd.ResultsDir)
}
if cmd.HostInfoFile != "" {
args = append(args, "--host-info-file", cmd.HostInfoFile)
}
args = append(args, cmd.Actions...)
return command(c, auditTaskRelPath, args...)
}
// Config describes where the Autotest directory is.
type Config struct {
AutotestDir string
LabpackDir string
}
// command creates an exec.Cmd for running an executable file from special directory.
func command(c Config, relpath string, args ...string) *exec.Cmd {
// TODO(otabek) refactor when migrated to new repository and LabpackDir will be provided always.
path := filepath.Join(c.AutotestDir, relpath)
dir := "Autotest"
if c.LabpackDir != "" {
path = filepath.Join(c.LabpackDir, relpath)
dir = "Labpack"
}
log.Printf("Running %s command %s %s", dir, path, args)
return exec.Command(path, args...)
}
// WriteKeyvals writes a map of keyvals in the format Autotest expects.
func WriteKeyvals(w io.Writer, m map[string]string) error {
for k, v := range m {
if _, err := fmt.Fprintf(w, "%s=%s\n", k, v); err != nil {
return err
}
}
return nil
}