blob: 93715fb79033a8e6d22193d062392c916577857a [file] [log] [blame] [edit]
// 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 (
"io/ioutil"
"os"
"path"
"path/filepath"
"strings"
"testing"
"github.com/golang/glog"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
)
// These SSHConnector tests connect to an in-memory SSH server (see ssh_mock.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 := &SSHConnector{Host: "somehost", Port: 123, Key: "keyfile"}
handle, err := NewHandleFromObjects(c)
if err != nil {
t.Fatalf("error creating handle: %s", err)
}
// Note: we don't serialize here because that is covered by handle tests
reloadedConn, err := loadConnectorFromHandle(handle)
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 TestSSHCommand(t *testing.T) {
c, _ := getFakeSSHConnector(t)
defer c.Close()
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 TestSSHInvalidCommand(t *testing.T) {
c, _ := getFakeSSHConnector(t)
defer c.Close()
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 TestSSHGet(t *testing.T) {
c, fs := getFakeSSHConnector(t)
defer c.Close()
tmpDir := getTempdir(t)
defer os.RemoveAll(tmpDir)
testFile := &fakeFile{name: "/testfile", content: "test file contents"}
fs.files = append(fs.files, testFile)
if err := c.Get("/testfile", tmpDir); err != nil {
t.Fatalf("error getting file: %s", err)
}
got, err := ioutil.ReadFile(path.Join(tmpDir, 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 TestSSHGetNonexistentSourceFile(t *testing.T) {
c, _ := getFakeSSHConnector(t)
defer c.Close()
tmpDir := getTempdir(t)
defer os.RemoveAll(tmpDir)
if err := c.Get("/testfile", tmpDir); err == nil {
t.Fatal("expected error but succeeded")
}
}
func TestSSHGetToNonexistentDestDir(t *testing.T) {
c, _ := getFakeSSHConnector(t)
defer c.Close()
tmpDir := getTempdir(t)
defer os.RemoveAll(tmpDir)
if err := c.Get("/testfile", filepath.Join(tmpDir, "nope")); err == nil {
t.Fatal("expected error but succeeded")
}
}
func TestSSHPut(t *testing.T) {
c, fs := getFakeSSHConnector(t)
defer c.Close()
tmpDir := getTempdir(t)
defer os.RemoveAll(tmpDir)
tmpFile := path.Join(tmpDir, "testfile")
fileContents := "test file contents"
if err := ioutil.WriteFile(tmpFile, []byte(fileContents), 0o600); err != nil {
t.Fatalf("error writing local file: %s", err)
}
remotePath := "/some/dir"
fs.files = []*fakeFile{{name: remotePath, isDir: true}}
if err := c.Put(tmpFile, remotePath); err != nil {
t.Fatalf("error putting file: %s", err)
}
expectRemoteFileWithContent(t, fs, path.Join(remotePath, filepath.Base(tmpFile)), fileContents)
}
func TestSSHGetGlob(t *testing.T) {
c, fs := getFakeSSHConnector(t)
defer c.Close()
tmpDir := getTempdir(t)
defer os.RemoveAll(tmpDir)
testFiles := []*fakeFile{
{name: "/subdir/a", content: "apple"},
{name: "/subdir/b", content: "banana"},
{name: "/subdir/j", content: "jabuticaba"},
}
// Add a fake directory entry so globbing works correctly
fs.files = append(testFiles, &fakeFile{name: "/subdir", isDir: true})
if err := c.Get("/subdir/*", tmpDir); err != nil {
t.Fatalf("error running remote command: %s", err)
}
for _, testFile := range testFiles {
got, err := ioutil.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) {
c, fs := getFakeSSHConnector(t)
defer c.Close()
tmpDir := getTempdir(t)
defer os.RemoveAll(tmpDir)
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 := ioutil.WriteFile(tmpFile, []byte(testFile.content), 0o600); err != nil {
t.Fatalf("error writing local file: %s", err)
}
}
remotePath := "/some/dir"
fs.files = []*fakeFile{{name: remotePath, isDir: true}}
if err := c.Put(path.Join(tmpDir, "*"), remotePath); err != nil {
t.Fatalf("error putting file: %s", err)
}
for _, testFile := range testFiles {
expectRemoteFileWithContent(t, fs, path.Join(remotePath, testFile.name), testFile.content)
}
}
func TestSSHGetDir(t *testing.T) {
c, fs := getFakeSSHConnector(t)
defer c.Close()
// Set up remote file structure
testFiles := []*fakeFile{
{name: "/x/outer/a", content: "apple"},
{name: "/x/outer/inner/b", content: "banana"},
{name: "/x/outer/inner/j", content: "jabuticaba"},
}
testDirs := []*fakeFile{
{name: "/x", isDir: true},
{name: "/x/outer", isDir: true},
{name: "/x/outer/inner", isDir: true},
}
fs.files = append(testFiles, testDirs...)
// Get /outer (contains file and subdirectory)
tmpDir := getTempdir(t)
defer os.RemoveAll(tmpDir)
srcDir := "/x/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 := ioutil.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 = getTempdir(t)
defer os.RemoveAll(tmpDir)
srcDir = "/x/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 := ioutil.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) {
tmpDir := getTempdir(t)
defer os.RemoveAll(tmpDir)
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 := ioutil.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)
defer c.Close()
remotePath := "/some/dir"
fs.files = []*fakeFile{{name: remotePath, isDir: true}}
if err := c.Put(path.Join(tmpDir, "outer"), remotePath); err != nil {
t.Fatalf("error putting dir: %s", err)
}
for _, testFile := range testFiles {
expectRemoteFileWithContent(t, fs, path.Join(remotePath, testFile.name), testFile.content)
}
// Put /outer/inner (contains files)
c, fs = getFakeSSHConnector(t)
defer c.Close()
fs.files = []*fakeFile{{name: remotePath, isDir: true}}
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, fs, remotePath, testFile.content)
} else {
if _, err := fs.getFile(remotePath); err == nil {
t.Fatalf("unexpected remote file created: %q", remotePath)
}
}
}
}
// Helper functions:
func getFakeSSHConnector(t *testing.T) (*SSHConnector, *fakeSftp) {
glog.Info("Starting local SSH server...")
conn, errCh, fakeFs, err := startLocalSSHServer()
if err != nil {
t.Fatalf("error starting local server: %s", err)
}
// 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, fs *fakeSftp, name string, content string) {
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")
}