blob: 6220e669599720c054d988aaa0b8d97a4f76edbe [file] [log] [blame]
// Copyright 2020 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 run
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"os"
"os/exec"
"chromiumos/tast/internal/runner"
"chromiumos/tast/ssh"
)
// runnerCmd provides common interface to execute a test_runner.
// The APIs this interface provides are based on exec.Cmd.
type runnerCmd interface {
// SetStdin sets the given stdin as the subprocess's stdin.
SetStdin(stdin io.Reader)
// SetStderr sets the given stderr as the subprocess's stderr.
SetStderr(stderr io.Writer)
// Output executes the command, and returns its stdout.
Output() ([]byte, error)
}
type localRunnerCmd struct {
// cmd holds the instance to execute a command on DUT.
cmd *ssh.Cmd
// ctx is used to run ssh.Cmd.Start() and ssh.Cmd.Wait().
ctx context.Context
}
func localRunnerCommand(ctx context.Context, cfg *Config, hst *ssh.Conn) *localRunnerCmd {
// Set proxy-related environment variables for local_test_runner so it will use them
// when accessing network.
execArgs := []string{"env"}
if cfg.proxy == proxyEnv {
// Proxy-related variables can be either uppercase or lowercase.
// See https://golang.org/pkg/net/http/#ProxyFromEnvironment.
for _, name := range []string{
"HTTP_PROXY", "HTTPS_PROXY", "NO_PROXY",
"http_proxy", "https_proxy", "no_proxy",
} {
if val := os.Getenv(name); val != "" {
execArgs = append(execArgs, fmt.Sprintf("%s=%s", name, val))
}
}
}
execArgs = append(execArgs, cfg.localRunner)
return &localRunnerCmd{hst.Command(execArgs[0], execArgs[1:]...), ctx}
}
func (r *localRunnerCmd) SetStdin(stdin io.Reader) {
r.cmd.Stdin = stdin
}
func (r *localRunnerCmd) SetStderr(stderr io.Writer) {
r.cmd.Stderr = stderr
}
func (r *localRunnerCmd) Output() ([]byte, error) {
return r.cmd.Output(r.ctx)
}
type remoteRunnerCmd struct {
*exec.Cmd
}
func remoteRunnerCommand(ctx context.Context, cfg *Config) *remoteRunnerCmd {
return &remoteRunnerCmd{exec.CommandContext(ctx, cfg.remoteRunner)}
}
func (r *remoteRunnerCmd) SetStdin(stdin io.Reader) {
r.Stdin = stdin
}
func (r *remoteRunnerCmd) SetStderr(stderr io.Writer) {
r.Stderr = stderr
}
// runTestRunnerCommand executes the given test_runner r. The test_runner reads
// serialized args from its stdin, then output json serialized value to stdout.
// This function unmarshals the output to out, so the pointer to an appropriate
// struct is expected to be passed via out.
func runTestRunnerCommand(r runnerCmd, args *runner.Args, out interface{}) error {
args.FillDeprecated()
stdin, err := json.Marshal(args)
if err != nil {
return fmt.Errorf("marshal args: %v", err)
}
r.SetStdin(bytes.NewBuffer(stdin))
var stderr bytes.Buffer
r.SetStderr(&stderr)
b, err := r.Output()
if err != nil {
// Append the first line of stderr, which often contains useful info
// for debugging to users.
if split := bytes.SplitN(stderr.Bytes(), []byte(","), 2); len(split) > 0 {
err = fmt.Errorf("%s: %s", err, string(split[0]))
}
return err
}
if err := json.Unmarshal(b, out); err != nil {
return fmt.Errorf("unmarshal output: %v", err)
}
return nil
}