blob: e4e7c47906d02a71666ccc1e10c6171345c39fc9 [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 (
"os"
"os/exec"
"path"
"path/filepath"
"strings"
"testing"
"time"
"github.com/golang/glog"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/kr/fs"
)
// These SSHConnector tests connect to an in-memory SSH server (see ssh_fake.go
// for details), so we have good coverage of the SSH/SFTP mechanics. However,
// on the remote side, they rely on mocked commands and a mocked filesystem so
// do not test interaction with an actual instance. For that, we rely on the
// end-to-end tests in e2e_test.go.
func TestSSHConnectorHandle(t *testing.T) {
c := NewSSHConnector(nil, "somehost", 123, "keyfile", "/tmp/path")
handle, err := NewHandleWithData(HandleData{connector: c})
if err != nil {
t.Fatalf("error creating handle: %s", err)
}
defer handle.Release()
build, _ := newMockBuild()
// Note: we don't serialize here because that is covered by handle tests
reloadedConn, err := loadConnectorFromHandle(build, handle, true)
if err != nil {
t.Fatalf("error loading connector from handle: %s", err)
}
c2, ok := reloadedConn.(*SSHConnector)
if !ok {
t.Fatalf("incorrect connector type")
}
if diff := cmp.Diff(c, c2, cmpopts.IgnoreUnexported(SSHConnector{})); diff != "" {
t.Fatalf("incorrect data in reloaded connector (-want +got):\n%s", diff)
}
}
func TestIncompleteSSHConnectorHandle(t *testing.T) {
// Construct an object that isn't fully initialized
c := NewSSHConnector(nil, "", 123, "", "")
handle, err := NewHandleWithData(HandleData{connector: c})
if err != nil {
t.Fatalf("error creating handle: %s", err)
}
defer handle.Release()
build, _ := newMockBuild()
if _, err := loadConnectorFromHandle(build, handle, true); err == nil {
t.Fatalf("expected error, but succeeded")
}
}
func TestSSHConnectorTmpDir(t *testing.T) {
c, _ := getFakeSSHConnector(t, 0)
defer c.Cleanup()
if err := c.Connect(); err != nil {
t.Fatalf("error calling Connect: %s", err)
}
if c.TmpDir == "" {
t.Fatalf("TmpDir was not set on Connect")
}
originalTmpDir := c.TmpDir
c.Close()
// The testing SSH server implementation doesn't support reconnection, so
// we cheat by spinning up a second one and copying over the connection
// info to the first connector.
c2, _ := getFakeSSHConnector(t, 0)
defer c2.Cleanup()
c.Host = c2.Host
c.Port = c2.Port
c.Key = c2.Key
if err := c.Connect(); err != nil {
t.Fatalf("error reconnecting: %s", err)
}
if c.TmpDir != originalTmpDir {
t.Fatalf("TmpDir changed after reconnection: %s", c.TmpDir)
}
c.Cleanup()
expectPathAbsent(t, c.TmpDir)
}
func TestSSHCommand(t *testing.T) {
c, _ := getFakeSSHConnector(t, 0)
defer c.Cleanup()
arg := "some cool args"
cmd := c.Command("echo", arg)
out, err := cmd.Output()
if err != nil {
t.Fatalf("error running remote command: %s", err)
}
if string(out) != arg+"\n" {
t.Fatalf("unexpected output: %q", string(out))
}
}
func TestFfxIsolateDir(t *testing.T) {
// Enable subprocess mocking (for ffx)
ExecCommand = mockCommand
defer func() { ExecCommand = exec.Command }()
c, _ := getFakeSSHConnector(t, 0)
defer c.Cleanup()
cmd, err := c.FfxCommand("", "fuzz", "list")
if err != nil {
t.Fatalf("error constructing ffx command: %s", err)
}
isolateDir := getFlagFromArgs(t, "--isolate-dir", cmd.Args)
cmd2, err := c.FfxCommand("", "fuzz", "list")
if err != nil {
t.Fatalf("error constructing ffx command: %s", err)
}
isolateDir2 := getFlagFromArgs(t, "--isolate-dir", cmd2.Args)
if isolateDir != isolateDir2 {
t.Fatalf("isolate dir changed between commands")
}
c.Cleanup()
expectPathAbsent(t, isolateDir)
}
func TestFfxRun(t *testing.T) {
// Enable subprocess mocking (for ffx)
ExecCommand = mockCommand
defer func() { ExecCommand = exec.Command }()
c, _ := getFakeSSHConnector(t, 0)
defer c.Cleanup()
out, err := c.FfxRun("", "fuzz", "list")
if err != nil {
t.Fatalf("error running ffx command: %s", err)
}
if !strings.Contains(out, "fuchsia-pkg://fuchsia.com/ffx-fuzzers#meta/one.cm") {
t.Fatalf("unexpected output: %q", out)
}
}
func TestFfxRunPathRewriting(t *testing.T) {
// Enable subprocess mocking (for ffx)
ExecCommand = mockCommand
defer func() { ExecCommand = exec.Command }()
c, _ := getFakeSSHConnector(t, 0)
defer c.Cleanup()
targetPath := cachePrefix + "/target/path"
tempDir := t.TempDir()
testcase := filepath.Join(tempDir, "testcase")
touchRandomFile(t, testcase)
if err := c.Put(testcase, targetPath); err != nil {
t.Fatalf("failed to put artifact: %s", err)
}
// Should fail if the input doesn't exist
if _, err := c.FfxRun("", "fuzz", "try", "url", invalidPath); err == nil {
t.Fatalf("unexpectedly succeeded trying an invalid file")
}
// Therefore it should succeed iff the translation was done
if _, err := c.FfxRun("", "fuzz", "try", "url",
path.Join(targetPath, "testcase")); err != nil {
t.Fatalf("error running ffx command: %s", err)
}
}
func TestFfxRunWithOutputDirectory(t *testing.T) {
// Enable subprocess mocking (for ffx)
ExecCommand = mockCommand
defer func() { ExecCommand = exec.Command }()
c, _ := getFakeSSHConnector(t, 0)
defer c.Cleanup()
dir := t.TempDir()
if _, err := c.FfxRun(dir, "fuzz", "run", "url"); err != nil {
t.Fatalf("error running ffx command: %s", err)
}
if !fileExists(filepath.Join(dir, "artifacts", "crash-1312")) {
t.Fatalf("artifact not found in output dir %s", dir)
}
}
func TestFfxRunWithoutFfx(t *testing.T) {
// Enable subprocess mocking (for ffx)
ExecCommand = mockCommand
defer func() { ExecCommand = exec.Command }()
c, _ := getFakeSSHConnector(t, 0)
defer c.Cleanup()
brokenBuild := c.build.(*mockBuild)
brokenBuild.paths["ffx"] = invalidPath
if _, err := c.FfxRun("", "fuzz", "list"); err == nil {
t.Fatalf("expected failure calling missing ffx but succeeded")
}
}
func TestFfxRunInvalidCommand(t *testing.T) {
// Enable subprocess mocking (for ffx)
ExecCommand = mockCommand
defer func() { ExecCommand = exec.Command }()
c, _ := getFakeSSHConnector(t, 0)
defer c.Cleanup()
if _, err := c.FfxRun("", "config", "pet"); err == nil {
t.Fatalf("unexpectedly succeeded running invalid ffx command")
}
}
func TestSSHInvalidCommand(t *testing.T) {
c, _ := getFakeSSHConnector(t, 0)
defer c.Cleanup()
cmd := c.Command("LOAD", `"*",8`)
if err := cmd.Run(); err == nil || err.(*InstanceCmdError).ReturnCode != 127 {
t.Fatalf("expected command not found but got: %s", err)
}
}
func TestSSHCommandWithConnectionFailures(t *testing.T) {
// Should recover with a few failures
c, _ := getFakeSSHConnector(t, sshReconnectCount-1)
defer c.Cleanup()
cmd := c.Command("echo", "a few")
if err := cmd.Run(); err != nil {
t.Fatalf("error running remote command: %s", err)
}
// Should abort after repeated failures
c2, _ := getFakeSSHConnector(t, sshReconnectCount)
defer c2.Close()
cmd = c2.Command("echo", "too many")
if err := cmd.Run(); err == nil {
t.Fatalf("missing expected error running remote command")
}
}
func TestSSHIsDir(t *testing.T) {
// Only supported for cache paths
remotePath := cachePrefix + "/root"
c, fs := getFakeSSHConnector(t, 0)
defer c.Cleanup()
elements := []*fakeFile{
{name: remotePath + "/subdir", isDir: true},
{name: remotePath + "/subdir/file", content: ":D"}}
setUpRemoteFilesystem(t, c, fs, elements)
for _, file := range elements {
isDir, err := c.IsDir(file.name)
if err != nil {
t.Fatalf("error stating file: %s", err)
}
if isDir != file.isDir {
t.Fatalf("unexpected IsDir return for %s: %t", file.name, isDir)
}
}
}
func TestSSHRmDir(t *testing.T) {
for _, remotePath := range []string{"/some/dir", cachePrefix + "/some/dir"} {
t.Run(remotePath, func(t *testing.T) {
c, fs := getFakeSSHConnector(t, 0)
defer c.Cleanup()
filename := remotePath + "/subdir/file"
elements := []*fakeFile{
{name: remotePath, isDir: true},
{name: remotePath + "/subdir", isDir: true},
{name: filename, content: ":D"}}
setUpRemoteFilesystem(t, c, fs, elements)
if err := c.RmDir(remotePath + "/subdir"); err != nil {
t.Fatalf("error calling rmdir: %s", err)
}
expectRemoteFileAbsent(t, c, fs, filename)
expectRemoteFileAbsent(t, c, fs, remotePath+"/subdir")
expectRemoteFilePresent(t, c, fs, remotePath)
})
}
}
func TestSSHGet(t *testing.T) {
for _, filename := range []string{"/testfile", cachePrefix + "/testfile"} {
t.Run(filename, func(t *testing.T) {
c, fs := getFakeSSHConnector(t, 0)
defer c.Cleanup()
tmpDir := t.TempDir()
testFile := &fakeFile{name: filename, content: "test file contents"}
setUpRemoteFilesystem(t, c, fs, []*fakeFile{testFile})
if err := c.Get(filename, tmpDir); err != nil {
t.Fatalf("error getting file: %s", err)
}
got, err := os.ReadFile(path.Join(tmpDir, path.Base(testFile.name)))
if err != nil {
t.Fatalf("error reading fetched file: %s", err)
}
if diff := cmp.Diff(testFile.content, string(got)); diff != "" {
t.Fatalf("fetched file has unexpected content (-want +got):\n%s", diff)
}
})
}
}
func TestSSHGetLiveCorpus(t *testing.T) {
// Enable subprocess mocking (for ffx)
ExecCommand = mockCommand
defer func() { ExecCommand = exec.Command }()
c, fs := getFakeSSHConnector(t, 0)
defer c.Cleanup()
tmpDir := t.TempDir()
remotePath := cachePrefix + "/some/dir"
files := []*fakeFile{
{name: remotePath + "/" + liveCorpusMarkerName, content: "fuzzer_url"},
{name: remotePath + "/extra", content: "extra"}}
setUpRemoteFilesystem(t, c, fs, files)
if err := c.Get(remotePath+"/*", tmpDir); err != nil {
t.Fatalf("error getting file: %s", err)
}
got := readDirRecursive(t, tmpDir)
if diff := cmp.Diff([]string{"a", "b", "extra"}, got); diff != "" {
t.Fatalf("fetched dir has unexpected content (-want +got):\n%s", diff)
}
}
func TestSSHGetLiveCorpusInSubdir(t *testing.T) {
// Enable subprocess mocking (for ffx)
ExecCommand = mockCommand
defer func() { ExecCommand = exec.Command }()
c, fakeFs := getFakeSSHConnector(t, 0)
defer c.Cleanup()
tmpDir := t.TempDir()
remotePath := cachePrefix + "/some/dir"
files := []*fakeFile{
{name: remotePath + "/inner/innerfile", content: "bonus"},
{name: remotePath + "/inner/" + liveCorpusMarkerName, content: "fuzzer_url"},
{name: remotePath + "/outerfile", content: "extra"}}
setUpRemoteFilesystem(t, c, fakeFs, files)
if err := c.Get(remotePath+"/*", tmpDir); err != nil {
t.Fatalf("error getting file: %s", err)
}
got := readDirRecursive(t, tmpDir)
want := []string{"inner/a", "inner/b", "inner/innerfile", "outerfile"}
if diff := cmp.Diff(want, got); diff != "" {
t.Fatalf("fetched dir has unexpected content (-want +got):\n%s", diff)
}
}
func TestSSHGetNonexistentSourceFile(t *testing.T) {
for _, filename := range []string{"/testfile", cachePrefix + "/testfile"} {
t.Run(filename, func(t *testing.T) {
c, _ := getFakeSSHConnector(t, 0)
defer c.Cleanup()
tmpDir := t.TempDir()
if err := c.Get(filename, tmpDir); err == nil {
t.Fatal("expected error but succeeded")
}
})
}
}
func TestSSHGetToNonexistentDestDir(t *testing.T) {
for _, filename := range []string{"/testfile", cachePrefix + "/testfile"} {
t.Run(filename, func(t *testing.T) {
c, _ := getFakeSSHConnector(t, 0)
defer c.Cleanup()
tmpDir := t.TempDir()
if err := c.Get(filename, filepath.Join(tmpDir, "nope")); err == nil {
t.Fatal("expected error but succeeded")
}
})
}
}
func TestSSHPut(t *testing.T) {
for _, remotePath := range []string{"/some/dir", cachePrefix + "/some/dir"} {
t.Run(remotePath, func(t *testing.T) {
c, fs := getFakeSSHConnector(t, 0)
defer c.Cleanup()
tmpDir := t.TempDir()
tmpFile := path.Join(tmpDir, "testfile")
fileContents := "test file contents"
if err := os.WriteFile(tmpFile, []byte(fileContents), 0o600); err != nil {
t.Fatalf("error writing local file: %s", err)
}
files := []*fakeFile{{name: remotePath, isDir: true}}
setUpRemoteFilesystem(t, c, fs, files)
if err := c.Put(tmpFile, remotePath); err != nil {
t.Fatalf("error putting file: %s", err)
}
expectRemoteFileWithContent(t, c, fs,
path.Join(remotePath, filepath.Base(tmpFile)), fileContents)
})
}
}
func TestSSHGetGlob(t *testing.T) {
for _, remotePath := range []string{"/subdir", cachePrefix + "/subdir"} {
t.Run(remotePath, func(t *testing.T) {
c, fs := getFakeSSHConnector(t, 0)
defer c.Cleanup()
tmpDir := t.TempDir()
testFiles := []*fakeFile{
{name: remotePath + "/a", content: "apple"},
{name: remotePath + "/b", content: "banana"},
{name: remotePath + "/j", content: "jabuticaba"},
}
// Add a fake directory entry so globbing works correctly
entries := append(testFiles, &fakeFile{name: "/subdir", isDir: true})
setUpRemoteFilesystem(t, c, fs, entries)
if err := c.Get(remotePath+"/*", tmpDir); err != nil {
t.Fatalf("error running remote command: %s", err)
}
for _, testFile := range testFiles {
got, err := os.ReadFile(path.Join(tmpDir, filepath.Base(testFile.name)))
if err != nil {
t.Fatalf("error reading fetched file: %s", err)
}
if diff := cmp.Diff(testFile.content, string(got)); diff != "" {
t.Fatalf("fetched file has unexpected content (-want +got):\n%s", diff)
}
}
})
}
}
func TestSSHPutGlob(t *testing.T) {
for _, remotePath := range []string{"/some/dir", cachePrefix + "/some/dir"} {
t.Run(remotePath, func(t *testing.T) {
c, fs := getFakeSSHConnector(t, 0)
defer c.Cleanup()
tmpDir := t.TempDir()
testFiles := []*fakeFile{
{name: "a", content: "apple"},
{name: "b", content: "banana"},
{name: "j", content: "jabuticaba"},
}
for _, testFile := range testFiles {
tmpFile := path.Join(tmpDir, testFile.name)
if err := os.WriteFile(tmpFile, []byte(testFile.content), 0o600); err != nil {
t.Fatalf("error writing local file: %s", err)
}
}
files := []*fakeFile{{name: remotePath, isDir: true}}
setUpRemoteFilesystem(t, c, fs, files)
if err := c.Put(path.Join(tmpDir, "*"), remotePath); err != nil {
t.Fatalf("error putting file: %s", err)
}
for _, testFile := range testFiles {
expectRemoteFileWithContent(t, c, fs, path.Join(remotePath, testFile.name),
testFile.content)
}
})
}
}
func TestSSHGetDir(t *testing.T) {
for _, remotePath := range []string{"/x", cachePrefix + "/x"} {
t.Run(remotePath, func(t *testing.T) {
c, fs := getFakeSSHConnector(t, 0)
defer c.Cleanup()
// Set up remote file structure
testFiles := []*fakeFile{
{name: remotePath + "/outer/a", content: "apple"},
{name: remotePath + "/outer/inner/b", content: "banana"},
{name: remotePath + "/outer/inner/j", content: "jabuticaba"},
}
testDirs := []*fakeFile{
{name: remotePath, isDir: true},
{name: remotePath + "/outer", isDir: true},
{name: remotePath + "/outer/inner", isDir: true},
}
entries := append(testFiles, testDirs...)
setUpRemoteFilesystem(t, c, fs, entries)
// Get /outer (contains file and subdirectory)
tmpDir := t.TempDir()
srcDir := remotePath + "/outer"
if err := c.Get(srcDir, tmpDir); err != nil {
t.Fatalf("error getting dir: %s", err)
}
for _, testFile := range testFiles {
relPath := strings.TrimPrefix(testFile.name, path.Dir(srcDir))
got, err := os.ReadFile(path.Join(tmpDir, relPath))
if err != nil {
t.Fatalf("error reading fetched file: %s", err)
}
if diff := cmp.Diff(testFile.content, string(got)); diff != "" {
t.Fatalf("fetched file has unexpected content (-want +got):\n%s", diff)
}
}
// Get /outer/inner (contains files)
tmpDir = t.TempDir()
srcDir = remotePath + "/outer/inner"
if err := c.Get(srcDir, tmpDir); err != nil {
t.Fatalf("error getting dir: %s", err)
}
for _, testFile := range testFiles {
relName := strings.TrimPrefix(testFile.name, path.Dir(srcDir))
got, err := os.ReadFile(path.Join(tmpDir, relName))
if !strings.HasPrefix(testFile.name, srcDir) {
if err == nil {
t.Fatalf("unexpected file retrieved: %q", testFile.name)
}
} else {
if err != nil {
t.Fatalf("error reading fetched file: %s", err)
}
if diff := cmp.Diff(testFile.content, string(got)); diff != "" {
t.Fatalf("fetched file has unexpected content (-want +got):\n%s", diff)
}
}
}
})
}
}
func TestSSHPutDir(t *testing.T) {
for _, remotePath := range []string{"/some/dir", cachePrefix + "/some/dir"} {
t.Run(remotePath, func(t *testing.T) {
tmpDir := t.TempDir()
testFiles := []*fakeFile{
{name: "/outer/a", content: "apple"},
{name: "/outer/inner/b", content: "banana"},
{name: "/outer/inner/j", content: "jabuticaba"},
}
testDirs := []*fakeFile{
{name: "/outer", isDir: true},
{name: "/outer/inner", isDir: true},
}
for _, testDir := range testDirs {
newDir := path.Join(tmpDir, testDir.name)
if err := os.Mkdir(newDir, 0o700); err != nil {
t.Fatalf("error creating local dir: %s", err)
}
}
for _, testFile := range testFiles {
tmpFile := path.Join(tmpDir, testFile.name)
if err := os.WriteFile(tmpFile, []byte(testFile.content), 0o600); err != nil {
t.Fatalf("error writing local file: %s", err)
}
}
// Put /outer (contains file and subdirectory)
c, fs := getFakeSSHConnector(t, 0)
defer c.Cleanup()
files := []*fakeFile{{name: remotePath, isDir: true}}
setUpRemoteFilesystem(t, c, fs, files)
if err := c.Put(path.Join(tmpDir, "outer"), remotePath); err != nil {
t.Fatalf("error putting dir: %s", err)
}
for _, testFile := range testFiles {
expectRemoteFileWithContent(t, c, fs, path.Join(remotePath, testFile.name),
testFile.content)
}
// Put /outer/inner (contains files)
c, fs = getFakeSSHConnector(t, 0)
defer c.Cleanup()
setUpRemoteFilesystem(t, c, fs, files)
srcDir := "/outer/inner"
if err := c.Put(path.Join(tmpDir, srcDir), remotePath); err != nil {
t.Fatalf("error putting dir: %s", err)
}
for _, testFile := range testFiles {
relName := strings.TrimPrefix(testFile.name, path.Dir(srcDir))
remotePath := path.Join(remotePath, relName)
if strings.HasPrefix(testFile.name, srcDir) {
expectRemoteFileWithContent(t, c, fs, remotePath, testFile.content)
} else {
expectRemoteFileAbsent(t, c, fs, remotePath)
}
}
})
}
}
// Helper functions:
func getFakeSSHConnector(t *testing.T, failureCount uint) (*SSHConnector, *fakeSftp) {
glog.Info("Starting local SSH server...")
conn, errCh, fakeFs, err := startLocalSSHServer(failureCount)
if err != nil {
t.Fatalf("error starting local server: %s", err)
}
conn.reconnectInterval = 1 * time.Millisecond
build, _ := newMockBuild()
conn.build = build
// Monitor for server errors
go func() {
for err := range errCh {
t.Errorf("error from local SSH server: %s", err)
}
}()
return conn, fakeFs
}
func expectRemoteFileWithContent(t *testing.T, conn *SSHConnector,
fs *fakeSftp, name string, content string) {
if isCachePath(name) {
got := readFileFromCache(t, conn, name)
if diff := cmp.Diff(content, got); diff != "" {
t.Fatalf("uploaded file has unexpected content (-want +got):\n%s", diff)
}
return
}
for _, f := range fs.files {
if f.name == name {
if diff := cmp.Diff(content, f.content); diff != "" {
t.Fatalf("uploaded file has unexpected content (-want +got):\n%s", diff)
}
return
}
}
t.Fatalf("uploaded file not found in expected location")
}
func expectRemoteFileAbsent(t *testing.T, conn *SSHConnector,
fs *fakeSftp, path string) {
if isCachePath(path) {
hostPath, err := conn.cacheToHostPath(path)
if err != nil {
t.Fatalf("error looking up cache path %s: %s", path, err)
}
if fileExists(hostPath) {
t.Fatalf("unexpected remote file exists: %q", path)
}
return
}
if _, err := fs.getFile(path); err == nil {
t.Fatalf("unexpected remote file exists: %q", path)
}
}
func expectRemoteFilePresent(t *testing.T, conn *SSHConnector,
fs *fakeSftp, path string) {
if isCachePath(path) {
hostPath, err := conn.cacheToHostPath(path)
if err != nil {
t.Fatalf("error looking up cache path %s: %s", path, err)
}
if !fileExists(hostPath) {
t.Fatalf("expected remote file doesn't exist: %q", path)
}
return
}
if _, err := fs.getFile(path); err != nil {
t.Fatalf("expected remote file doesn't exist: %q", path)
}
}
// List all files in a directory, recursively. Returns paths relative to the root path.
func readDirRecursive(t *testing.T, root string) []string {
var files []string
walker := fs.Walk(root)
for walker.Step() {
if err := walker.Err(); err != nil {
t.Fatalf("error while walking: %s", err)
}
if walker.Stat().IsDir() {
continue
}
relPath, err := filepath.Rel(root, walker.Path())
if err != nil {
t.Fatalf("error taking relpath: %s", err)
}
files = append(files, relPath)
}
return files
}
// Set an initial state of the (fake) remote filesystem
func setUpRemoteFilesystem(t *testing.T, conn *SSHConnector,
fs *fakeSftp, files []*fakeFile) {
// Need to ensure TmpDir is initialized
if err := conn.Connect(); err != nil {
t.Fatalf("error connecting: %s", err)
}
fs.files = []*fakeFile{}
for _, file := range files {
if !isCachePath(file.name) {
fs.files = append(fs.files, file)
} else if !file.isDir {
writeFileToCache(t, conn, file.name, file.content)
}
}
}
// Methods for directly manipulating files inside the cache, to set up/check
// its state when testing
func readFileFromCache(t *testing.T, conn *SSHConnector, path string) string {
hostPath, err := conn.cacheToHostPath(path)
if err != nil {
t.Fatalf("error looking up cache path %s: %s", path, err)
}
data, err := os.ReadFile(hostPath)
if err != nil {
t.Fatalf("error reading file %s from cache: %s", hostPath, err)
}
return string(data)
}
func writeFileToCache(t *testing.T, conn *SSHConnector, path, content string) {
hostPath, err := conn.cacheToHostPath(path)
if err != nil {
t.Fatalf("error looking up cache path %s: %s", path, err)
}
// Make any enclosing dirs automatically
if err := os.MkdirAll(filepath.Dir(hostPath), os.ModeDir|0o700); err != nil {
t.Fatalf("error making dir(s) for cache path %s: %s", hostPath, err)
}
if err := os.WriteFile(hostPath, []byte(content), 0o600); err != nil {
t.Fatalf("error writing file %s to cache: %s", hostPath, err)
}
}