blob: d71a1d1830a9ef63938dbd3b682f3cec84d35b31 [file] [log] [blame]
// Copyright 2020 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 fuzz
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
)
const mockFuzzerPid = 414141
// Used for keeping history of Put/Get calls
type transferCmd struct {
src, dst string
}
// mockConnector is used in Fuzzer and Instance tests, and returned by mockLauncher.Start()
type mockConnector struct {
connected bool
shouldFailToConnectCount uint
shouldFailToExecuteCount uint
shouldFailToGetSysLog bool
ffxIsolateDir string
// Store history of Get/Puts to enable basic checks
PathsGot []transferCmd
PathsPut []transferCmd
LastPutFileContent string
// List of paths for which IsDir() should return true
Dirs []string
// Store history of commands run on this connection
CmdHistory []string
FuzzCtlHistory []string
FfxHistory []string
CleanedUp bool
}
func NewMockConnector(t *testing.T) *mockConnector {
return &mockConnector{ffxIsolateDir: t.TempDir()}
}
func (c *mockConnector) Connect() error {
if c.connected {
return fmt.Errorf("Connect called when already connected")
}
if c.shouldFailToConnectCount > 0 {
c.shouldFailToConnectCount -= 1
return fmt.Errorf("Intentionally broken Connector")
}
c.connected = true
return nil
}
func (c *mockConnector) Close() {
c.connected = false
}
func (c *mockConnector) RmDir(path string) error {
return c.Command("rm", "-rf", path).Run()
}
func (c *mockConnector) IsDir(path string) (bool, error) {
if strings.HasSuffix(path, invalidPath) {
return false, os.ErrNotExist
}
for _, dir := range c.Dirs {
if path == dir {
return true, nil
}
}
return false, nil
}
func (c *mockConnector) Cleanup() {
c.CleanedUp = true
}
func (c *mockConnector) Command(name string, args ...string) InstanceCmd {
shouldFail := c.shouldFailToExecuteCount > 0
if shouldFail {
c.shouldFailToExecuteCount -= 1
}
return &mockInstanceCmd{connector: c, name: name, args: args, shouldFail: shouldFail}
}
func (c *mockConnector) Get(targetSrc string, hostDst string) error {
fileInfo, err := os.Stat(hostDst)
if err != nil {
return fmt.Errorf("error stat-ing dest %q: %s", hostDst, err)
}
if !fileInfo.IsDir() {
return fmt.Errorf("host dest is not a dir")
}
c.PathsGot = append(c.PathsGot, transferCmd{targetSrc, hostDst})
return nil
}
func (c *mockConnector) Put(hostSrc string, targetDst string) error {
srcList, err := filepath.Glob(hostSrc)
if err != nil {
return fmt.Errorf("error globbing source %q: %s", hostSrc, err)
}
if len(srcList) == 0 {
return fmt.Errorf("no matches for glob %q", hostSrc)
}
for _, src := range srcList {
fileInfo, err := os.Stat(src)
if err != nil {
return fmt.Errorf("error stat-ing source %q: %s", src, err)
}
// If a file was Put, save its contents
if !fileInfo.IsDir() {
data, err := os.ReadFile(src)
if err != nil {
return fmt.Errorf("error reading file: %s", err)
}
c.LastPutFileContent = string(data)
}
c.PathsPut = append(c.PathsPut, transferCmd{src, targetDst})
}
return nil
}
func (c *mockConnector) GetSysLog(pid int) (string, error) {
if c.shouldFailToGetSysLog {
return "syslog failure", fmt.Errorf("Intentionally broken Connector")
}
// Only return logs for the PID we expect
if pid != mockFuzzerPid {
return "", nil
}
// TODO(https://fxbug.dev/42121948): more realistic test data
lines := []string{
fmt.Sprintf("syslog for %d", pid),
"[1234.5][klog] INFO: {{{0x41}}}",
}
return strings.Join(lines, "\n"), nil
}
func (c *mockConnector) FfxRun(outputDir string, args ...string) (string, error) {
c.FfxHistory = append(c.FfxHistory, strings.Join(args, " "))
return strings.Join(args, " "), nil
}
// Whether, based on PathsPut, the target path should exist (assumes only files are put)
func (c *mockConnector) TargetPathExists(path string) bool {
for _, put := range c.PathsPut {
targetPath := filepath.Join(put.dst, filepath.Base(put.src))
if targetPath == path {
return true
}
}
return false
}
func (c *mockConnector) FfxCommand(outputDir string, args ...string) (*exec.Cmd, error) {
// Used only by Fuzzer.Run, which needs a full subprocess to work with
args = append([]string{"--target", "some-target",
"--isolate-dir", c.ffxIsolateDir}, args...)
if outputDir != "" {
args = append(args, "-o", outputDir)
}
// Instead of fully rewriting cache paths, which is impossible since we
// don't implement an actual cache here, just set any unknown ones to an
// invalidPath.
for j, arg := range args {
if !strings.HasPrefix(arg, cachePrefix) {
continue
}
if !c.TargetPathExists(arg) {
args[j] = invalidPath
}
}
cmd := mockCommand("ffx", args...)
return cmd, nil
}