blob: 526e8b8f751f044efcb6463c5ba6ce40a45f2839 [file] [log] [blame]
package bootserver
import (
"bufio"
"context"
"io"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
"testing"
"time"
"go.fuchsia.dev/fuchsia/src/testing/qemu"
)
// The default nodename given to an target with the default QEMU MAC address.
const DefaultNodename = "swarm-donut-petri-acre"
type LogMatch struct {
Pattern string
ShouldMatch bool
}
func zbiPath() (string, error) {
ex, err := os.Executable()
if err != nil {
return "", err
}
exPath := filepath.Dir(ex)
return filepath.Join(exPath, "../fuchsia.zbi"), nil
}
func ToolPath(t *testing.T, name string) string {
ex, err := os.Executable()
if err != nil {
t.Fatal(err)
return ""
}
exPath := filepath.Dir(ex)
return filepath.Join(exPath, "test_data", "bootserver_tools", name)
}
func FirmwarePath(t *testing.T) string {
// QEMU doesn't know how to write firmware so the contents don't matter,
// it just has to be a real file. It does get sent over the network though
// so use a small file to avoid long transfers.
return ToolPath(t, "fake_firmware")
}
func matchPattern(t *testing.T, pattern string, reader *bufio.Reader) bool {
for {
line, err := reader.ReadString('\n')
if err != nil && err == io.EOF {
break
}
t.Logf("matchPattern: %s", line)
if strings.Contains(line, pattern) {
return true
}
}
return false
}
func CmdWithOutput(t *testing.T, name string, arg ...string) []byte {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, name, arg...)
out, err := cmd.Output()
if err != nil {
t.Errorf("%s failed %s, err=%s", name, out, err)
return nil
}
return out
}
func CmdSearchLog(t *testing.T, logPatterns []LogMatch,
name string, arg ...string) {
found := false
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, name, arg...)
cmderr, err := cmd.StderrPipe()
if err != nil {
t.Errorf("Failed to stdout %s", err)
}
readerErr := bufio.NewReader(cmderr)
if err := cmd.Start(); err != nil {
t.Errorf("Failed to start %s", err)
}
for _, logPattern := range logPatterns {
match := matchPattern(t, logPattern.Pattern, readerErr)
if match != logPattern.ShouldMatch {
found = false
t.Errorf("Log pattern \"%s\" mismatch. Expected - %t, actual - %t",
logPattern.Pattern, logPattern.ShouldMatch, match)
break
}
found = true
}
if err := cmd.Wait(); err != nil {
t.Logf("Failed to wait on task %s", err)
}
if ctx.Err() == context.DeadlineExceeded {
t.Errorf("%s timed out err=%s", name, ctx.Err())
} else if !found {
t.Errorf("%s failed to match logs", name)
} else {
t.Logf("%s worked as expected", name)
}
}
func AttemptPaveNoBind(t *testing.T, shouldWork bool) {
// Get the node ipv6 address
out := CmdWithOutput(t, ToolPath(t, "netls"))
// Extract the ipv6 from the netls output
regexString := DefaultNodename + ` \((?P<ipv6>.*)\)`
match := regexp.MustCompile(regexString).FindStringSubmatch(string(out))
if len(match) != 2 {
t.Errorf("Node %s not found in netls output - %s", DefaultNodename, out)
return
}
var logPattern []LogMatch
if shouldWork {
paveWorksPattern := []LogMatch{
{"Sending request to ", true},
{"Received request from ", true},
{"Proceeding with nodename ", true},
{"Transfer starts", true},
}
logPattern = paveWorksPattern
} else {
paveFailsPattern := []LogMatch{
{"Sending request to ", true},
{"Received request from ", false},
{"Proceeding with nodename ", false},
{"Transfer starts", false},
}
logPattern = paveFailsPattern
}
CmdSearchLog(
t, logPattern,
ToolPath(t, "bootserver"), "--fvm", "\"dummy.blk\"",
"--no-bind", "-a", match[1], "-1", "--fail-fast")
}
// Starts a QEMU instance with the given kernel commandline args.
// Returns the qemu.Instance and a cleanup function to call when finished.
func StartQemu(t *testing.T, appendCmdline string, modeString string) (*qemu.Instance, func()) {
distro, err := qemu.Unpack()
if err != nil {
t.Fatalf("Failed to unpack QEMU: %s", err)
}
arch, err := distro.TargetCPU()
if err != nil {
t.Fatalf("Failed to get distro CPU: %s", err)
}
zbi, err := zbiPath()
if err != nil {
t.Fatalf("Failed to get ZBI path: %s", err)
}
instance := distro.Create(qemu.Params{
Arch: arch,
ZBI: zbi,
AppendCmdline: appendCmdline,
Networking: true,
})
instance.Start()
if err != nil {
t.Fatalf("Failed to start QEMU instance: %s", err)
}
// Make sure netsvc in expected mode.
instance.WaitForLogMessage("netsvc: running in " + modeString + " mode")
// Make sure netsvc is booted.
instance.WaitForLogMessage("netsvc: start")
return instance, func() {
instance.Kill()
distro.Delete()
}
}