blob: ced1b348c3e17db42d1c8a3fa01e7fe18d77d25d [file] [log] [blame]
// Copyright 2019 The Fuchsia 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 main
import (
"context"
"fmt"
"io"
"io/ioutil"
"path"
"strings"
"go.fuchsia.dev/fuchsia/tools/build/lib"
"go.fuchsia.dev/fuchsia/tools/lib/logger"
"go.fuchsia.dev/fuchsia/tools/lib/runner"
"go.fuchsia.dev/fuchsia/tools/net/sshutil"
"go.fuchsia.dev/fuchsia/tools/testing/runtests"
)
const (
// A test output directory within persistent storage.
dataOutputDir = "/data/infra/testrunner"
// Various tools for running tests.
runtestsName = "runtests"
runTestComponentName = "run-test-component"
runTestSuiteName = "run-test-suite"
componentV2Suffix = ".cm"
)
// subprocessTester executes tests in local subprocesses.
type subprocessTester struct {
r *runner.SubprocessRunner
}
// NewSubprocessTester returns a SubprocessTester that can execute tests
// locally with a given working directory and environment.
func newSubprocessTester(dir string, env []string) *subprocessTester {
return &subprocessTester{
r: &runner.SubprocessRunner{
Dir: dir,
Env: env,
},
}
}
func (t *subprocessTester) Test(ctx context.Context, test build.Test, stdout io.Writer, stderr io.Writer) (runtests.DataSinkMap, error) {
command := test.Command
if len(test.Command) == 0 {
if test.Path == "" {
return nil, fmt.Errorf("test %q has no `command` or `path` set", test.Name)
}
command = []string{test.Path}
}
return nil, t.r.Run(ctx, command, stdout, stderr)
}
func (t *subprocessTester) Close() error {
return nil
}
// fuchsiaSSHTester executes fuchsia tests over an SSH connection.
type fuchsiaSSHTester struct {
r *runner.SSHRunner
copier *runtests.DataSinkCopier
useRuntests bool
localOutputDir string
}
// newFuchsiaSSHTester returns a fuchsiaSSHTester associated to a fuchsia
// instance of given nodename, the private key paired with an authorized one
// and the directive of whether `runtests` should be used to execute the test.
func newFuchsiaSSHTester(nodename, sshKeyFile, localOutputDir string, useRuntests bool) (*fuchsiaSSHTester, error) {
key, err := ioutil.ReadFile(sshKeyFile)
if err != nil {
return nil, fmt.Errorf("failed to read SSH key file: %v", err)
}
config, err := sshutil.DefaultSSHConfig(key)
if err != nil {
return nil, fmt.Errorf("failed to create an SSH client config: %v", err)
}
client, err := sshutil.ConnectToNode(context.Background(), nodename, config)
if err != nil {
return nil, fmt.Errorf("failed to establish an SSH connection: %v", err)
}
r := runner.NewSSHRunner(client, config)
copier, err := runtests.NewDataSinkCopier(client)
if err != nil {
return nil, err
}
return &fuchsiaSSHTester{
r: r,
copier: copier,
useRuntests: useRuntests,
localOutputDir: localOutputDir,
}, nil
}
// Test runs a test over SSH.
func (t *fuchsiaSSHTester) Test(ctx context.Context, test build.Test, stdout io.Writer, stderr io.Writer) (runtests.DataSinkMap, error) {
if err := t.r.ReconnectIfNecessary(ctx); err != nil {
return nil, fmt.Errorf("failed to restablish SSH connection: %v", err)
}
setCommand(&test, t.useRuntests, dataOutputDir)
testErr := t.r.Run(ctx, test.Command, stdout, stderr)
var copyErr error
var sinks runtests.DataSinkMap
if t.useRuntests {
if sinks, copyErr = t.copier.Copy(dataOutputDir, t.localOutputDir); copyErr != nil {
logger.Errorf(ctx, "failed to copy data sinks off target for test %q: %v", test.Name, copyErr)
}
}
if testErr == nil {
return sinks, copyErr
}
return sinks, testErr
}
// Close terminates the underlying SSH connection. The object is no longer
// usable after calling this method.
func (t *fuchsiaSSHTester) Close() error {
if err := t.copier.Close(); err != nil {
t.r.Close()
return err
}
return t.r.Close()
}
func setCommand(test *build.Test, useRuntests bool, remoteOutputDir string) {
if len(test.Command) > 0 {
return
}
if useRuntests {
name := path.Base(test.Path)
dir := path.Dir(test.Path)
test.Command = []string{runtestsName, "-t", name, dir, "-o", remoteOutputDir}
return
} else if test.PackageURL != "" {
if strings.HasSuffix(test.PackageURL, componentV2Suffix) {
test.Command = []string{runTestSuiteName, test.PackageURL}
} else {
test.Command = []string{runTestComponentName, test.PackageURL}
}
} else {
test.Command = []string{test.Path}
}
}