blob: bcd196228028061469c7699db7232bb3d37f150e [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 (
"bytes"
"reflect"
"strings"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
)
func newTestInstance() *BaseInstance {
build, _ := newMockBuild()
return &BaseInstance{Build: build, reconnectInterval: defaultReconnectInterval}
}
func TestInstanceHandle(t *testing.T) {
// Instance loading automatically loads the build, so we need to stub it out
realNewBuild := NewBuild
NewBuild = newMockBuild
defer func() { NewBuild = realNewBuild }()
instance := newTestInstance()
instance.Launcher = &QemuLauncher{Pid: 404, TmpDir: "/some/dir"}
instance.Connector = NewSSHConnector("somehost", 123, "keyfile")
handle, err := instance.Handle()
if err != nil {
t.Fatalf("error creating handle: %s", err)
}
defer handle.Release()
handle2, err := instance.Handle()
if handle.Serialize() != handle2.Serialize() {
handle2.Release()
t.Fatalf("same instance returned different handles: %q, %q", handle, handle2)
}
// Note: we don't serialize here because that is covered by handle tests
reloadedInstance, err := loadInstanceFromHandle(handle)
if err != nil {
t.Fatalf("error loading instance from handle: %s", err)
}
got, ok := reloadedInstance.(*BaseInstance)
if !ok {
t.Fatalf("incorrect instance type")
}
if diff := cmp.Diff(instance, got, cmpopts.IgnoreUnexported(BaseInstance{},
SSHConnector{}, QemuLauncher{}, mockBuild{})); diff != "" {
t.Fatalf("incorrect data in reloaded instance (-want +got):\n%s", diff)
}
// Ensure that handle is released and invalid upon stopping
if err := instance.Stop(); err != nil {
t.Fatalf("error stopping instance: %s", err)
}
reloadedInstance, err = loadInstanceFromHandle(handle)
if err == nil {
t.Fatalf("unexpected success reloading instance from released handle: %q", handle)
}
}
func TestInstanceRepeatedStart(t *testing.T) {
i := newTestInstance()
i.Launcher = &mockLauncher{}
if err := i.Start(); err != nil {
t.Fatalf("Error starting instance: %s", err)
}
if err := i.Start(); err == nil {
t.Fatalf("Expected error starting already-running instance but succeeded")
}
}
func TestInstanceStartWithLauncherFailure(t *testing.T) {
i := newTestInstance()
i.Launcher = &mockLauncher{shouldFailToStart: true}
if err := i.Start(); err == nil {
t.Fatalf("expected launcher failure but succeeded")
}
}
func TestInstanceStartWithLauncherEarlyExit(t *testing.T) {
i := newTestInstance()
// Only the first connection will fail, but no retries should be made
// because the launcher will have died.
i.Launcher = &mockLauncher{shouldFailToConnectCount: 1, shouldExitEarly: true}
if err := i.Start(); err == nil {
t.Fatalf("expected launcher failure but succeeded")
}
}
func TestInstanceStartWithTemporaryConnectorFailure(t *testing.T) {
i := newTestInstance()
i.Launcher = &mockLauncher{shouldFailToConnectCount: maxInitialConnectAttempts - 1}
i.reconnectInterval = 1 * time.Millisecond
if err := i.Start(); err != nil {
t.Fatalf("Expected connector to succeed after reconnection attempt")
}
if err := i.Stop(); err != nil {
t.Fatalf("Error stopping instance: %s", err)
}
if running, _ := i.Launcher.IsRunning(); running {
t.Fatalf("expected launcher to have been killed, but it is running")
}
}
func TestInstanceStartWithPermanentConnectorFailure(t *testing.T) {
i := newTestInstance()
i.Launcher = &mockLauncher{shouldFailToConnectCount: maxInitialConnectAttempts}
i.reconnectInterval = 1 * time.Millisecond
if err := i.Start(); err == nil {
t.Fatalf("Expected connector failure but succeeded")
}
if running, _ := i.Launcher.IsRunning(); running {
t.Fatalf("expected launcher to have been killed, but it is running")
}
}
func TestInstanceStartWithTemporaryCommandFailure(t *testing.T) {
i := newTestInstance()
i.Launcher = &mockLauncher{shouldFailToExecuteCount: maxInitialConnectAttempts - 1}
i.reconnectInterval = 1 * time.Millisecond
if err := i.Start(); err != nil {
t.Fatalf("Expected to succeed after command re-execution attempt")
}
if err := i.Stop(); err != nil {
t.Fatalf("Error stopping instance: %s", err)
}
if running, _ := i.Launcher.IsRunning(); running {
t.Fatalf("expected launcher to have been killed, but it is running")
}
}
func TestInstanceStartWithPermanentCommandFailure(t *testing.T) {
i := newTestInstance()
i.Launcher = &mockLauncher{shouldFailToExecuteCount: maxInitialConnectAttempts + 1}
i.reconnectInterval = 1 * time.Millisecond
if err := i.Start(); err == nil {
t.Fatalf("Expected command execution failure but succeeded")
}
if running, _ := i.Launcher.IsRunning(); running {
t.Fatalf("expected launcher to have been killed, but it is running")
}
}
func TestInstance(t *testing.T) {
i := newTestInstance()
i.Launcher = &mockLauncher{}
if err := i.Start(); err != nil {
t.Fatalf("Error starting instance: %s", err)
}
fuzzers := i.ListFuzzers()
if !reflect.DeepEqual(fuzzers, []string{"foo/bar", "fail/nopid", "fail/notfound"}) {
t.Fatalf("incorrect fuzzer list: %v", fuzzers)
}
var outBuf bytes.Buffer
if err := i.RunFuzzer(&outBuf, "foo/bar", "", "arg1", "arg2"); err != nil {
t.Fatalf("Error running fuzzer: %s", err)
}
out := outBuf.String()
// Just a basic check here, more are done in fuzzer_tests
if !strings.Contains(out, "fuchsia-pkg://fuchsia.com/foo#meta/bar.cmx") {
t.Fatalf("fuzzer output missing package: %q", out)
}
if err := i.RunFuzzer(&outBuf, "invalid/fuzzer", ""); err == nil {
t.Fatalf("expected error when running invalid fuzzer")
}
remotePath := "data/path/to/remoteFile"
if err := i.Get("foo/bar", remotePath, "/path/to/localFile"); err != nil {
t.Fatalf("Error getting from instance: %s", err)
}
expected := []string{"/tmp/r/sys/fuchsia.com:foo:0#meta:bar.cmx/path/to/remoteFile"}
got := i.Connector.(*mockConnector).PathsGot
if !reflect.DeepEqual(got, expected) {
t.Fatalf("incorrect file get list: %v", got)
}
remotePath = "data/path/to/otherFile"
if err := i.Put("foo/bar", "/path/to/localFile", remotePath); err != nil {
t.Fatalf("Error putting to instance: %s", err)
}
expected = []string{"/tmp/r/sys/fuchsia.com:foo:0#meta:bar.cmx/path/to/otherFile"}
got = i.Connector.(*mockConnector).PathsPut
if !reflect.DeepEqual(got, expected) {
t.Fatalf("incorrect file put list: %v", got)
}
var logBuf bytes.Buffer
if err := i.GetLogs(&logBuf); err != nil {
t.Fatalf("error getting instance logs: %s", err)
}
log := logBuf.String()
if log != "system log\n" {
t.Fatalf("unexpected instance log: %q", log)
}
if err := i.Stop(); err != nil {
t.Fatalf("Error stopping instance: %s", err)
}
if running, _ := i.Launcher.IsRunning(); running {
t.Fatalf("expected launcher to have been killed, but it is running")
}
}
func TestInstanceRunFuzzerWithArtifactFetch(t *testing.T) {
i := newTestInstance()
i.Launcher = &mockLauncher{}
if err := i.Start(); err != nil {
t.Fatalf("Error starting instance: %s", err)
}
hostArtifactDir := "/art/dir"
var outBuf bytes.Buffer
args := []string{"-artifact_prefix=data/wow/x", "data/corpus"}
if err := i.RunFuzzer(&outBuf, "foo/bar", hostArtifactDir, args...); err != nil {
t.Fatalf("Error running fuzzer: %s", err)
}
out := outBuf.String()
if !strings.Contains(out, "/art/dir/xcrash-1312") {
t.Fatalf("fuzzer output missing host artifact path: %q", out)
}
expected := []string{"/tmp/r/sys/fuchsia.com:foo:0#meta:bar.cmx/wow/xcrash-1312"}
got := i.Connector.(*mockConnector).PathsGot
if !reflect.DeepEqual(got, expected) {
t.Fatalf("incorrect file get list: %v", got)
}
if err := i.Stop(); err != nil {
t.Fatalf("Error stopping instance: %s", err)
}
if running, _ := i.Launcher.IsRunning(); running {
t.Fatalf("expected launcher to have been killed, but it is running")
}
}