blob: a41bc166572bb0a870d6aacd7b660e5fa29ebf3c [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 paver
import (
"bytes"
"context"
"crypto/rand"
"crypto/rsa"
"os"
"path/filepath"
"strings"
"testing"
"golang.org/x/crypto/ssh"
)
func stringSliceEquals(lhs []string, rhs []string) bool {
if len(lhs) != len(rhs) {
return false
}
for i, v := range lhs {
if v != rhs[i] {
return false
}
}
return true
}
// The easiest way to make a fake key is to just generate a real one.
func generatePublicKey(t *testing.T) ssh.PublicKey {
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
t.Fatal(err)
}
pub, err := ssh.NewPublicKey(&privateKey.PublicKey)
if err != nil {
t.Fatal(err)
}
return pub
}
// createScript returns the path to a bash script that outputs its name and
// all its arguments.
func createScript(t *testing.T) string {
contents := "#!/bin/sh\necho \"$0 $@\"\n"
name := filepath.Join(t.TempDir(), "bootserver.sh")
if err := os.WriteFile(name, []byte(contents), 0o700); err != nil {
t.Fatal(err)
}
return name
}
func createAndRunPaver(t *testing.T, options ...BuildPaverOption) (zedbootPaverArgs []string, paverArgs []string) {
bootserverPath := createScript(t)
var output bytes.Buffer
options = append(options, Stdout(&output))
paver, err := NewBuildPaver(bootserverPath, filepath.Dir(bootserverPath), options...)
if err != nil {
t.Fatal(err)
}
if err := paver.Pave(context.Background(), "a-fake-device-name"); err != nil {
t.Fatal(err)
}
outputs := strings.Split(output.String(), "\n")
zedbootPaverArgs = strings.Split(outputs[0], " ")
paverArgs = strings.Split(outputs[1], " ")
if zedbootPaverArgs[0] != bootserverPath || zedbootPaverArgs[4] != "pave-zedboot" {
t.Fatalf("Paver called the wrong bootserver or mode. Expected %s with mode pave-zedboot, actual %s with mode %s",
bootserverPath, zedbootPaverArgs[0], zedbootPaverArgs[4])
}
if paverArgs[0] != bootserverPath || paverArgs[4] != "pave" {
t.Fatalf("Paver called the wrong bootserver or mode. Expected %s with mode pave, actual %s with mode %s",
bootserverPath, paverArgs[0], paverArgs[4])
}
return
}
func TestDefault(t *testing.T) {
zedbootPaverArgs, paverArgs := createAndRunPaver(t)
{
var deviceName string
hasAllowVersionMismatch := false
for i, arg := range zedbootPaverArgs {
if arg == "-n" {
if i+1 < len(zedbootPaverArgs) {
deviceName = zedbootPaverArgs[i+1]
}
} else if arg == "--allow-zedboot-version-mismatch" {
hasAllowVersionMismatch = true
}
}
if deviceName != "a-fake-device-name" {
t.Fatalf("Missing device name in zedboot paver arguments.")
}
if !hasAllowVersionMismatch {
t.Fatalf("Missing allow version mismatch flag in zedboot paver arguments.")
}
}
{
var deviceName string
hasFailFastIfVersionMismatch := false
for i, arg := range paverArgs {
if arg == "-n" {
if i+1 < len(paverArgs) {
deviceName = paverArgs[i+1]
}
} else if arg == "--fail-fast-if-version-mismatch" {
hasFailFastIfVersionMismatch = true
}
}
if deviceName != "a-fake-device-name" {
t.Fatalf("Missing device name in paver arguments.")
}
if !hasFailFastIfVersionMismatch {
t.Fatalf("Missing allow version mismatch flag in paver arguments.")
}
}
}
func TestSSHKeys(t *testing.T) {
sshKey := generatePublicKey(t)
_, paverArgs := createAndRunPaver(t, SSHPublicKey(sshKey))
hasAuthorizedKeys := false
for i, arg := range paverArgs {
if arg == "--authorized-keys" {
// Check that there's at least one more argument.
if i+1 < len(paverArgs) {
hasAuthorizedKeys = true
break
}
}
}
if !hasAuthorizedKeys {
t.Fatalf("Missing authorized keys file in paver arguments.")
}
}
func TestOverrideSlotA(t *testing.T) {
name := t.TempDir()
_, paverArgs := createAndRunPaver(t, OverrideSlotA(name))
var zirconAPath string
for i, arg := range paverArgs {
if arg == "--zircona" {
if i+1 < len(paverArgs) {
zirconAPath = paverArgs[i+1]
}
}
}
if zirconAPath != name {
t.Fatalf("Missing zircon A image in paver arguments.")
}
}
func TestOverrideVBMetaA(t *testing.T) {
name := t.TempDir()
_, paverArgs := createAndRunPaver(t, OverrideVBMetaA(name))
var vbmetaAPath string
for i, arg := range paverArgs {
if arg == "--vbmetaa" {
if i+1 < len(paverArgs) {
vbmetaAPath = paverArgs[i+1]
}
}
}
if vbmetaAPath != name {
t.Fatalf("Missing vbmeta A image in paver arguments.")
}
}
func TestPaveMode(t *testing.T) {
bootserverPath := createScript(t)
var output bytes.Buffer
paver, err := NewBuildPaver(bootserverPath, filepath.Dir(bootserverPath), Stdout(&output))
if err != nil {
t.Fatal(err)
}
{
if err := paver.PaveWithOptions(context.Background(), "a-fake-device-name", Options{Mode: ZedbootOnly}); err != nil {
t.Fatal(err)
}
outputs := strings.Split(output.String(), "\n")
args := strings.Split(outputs[0], " ")
if args[4] != "pave-zedboot" {
t.Errorf("Paver called with wrong mode")
}
if len(outputs[1]) != 0 {
t.Errorf("Unexpected extra command")
}
}
{
output.Reset()
if err := paver.PaveWithOptions(context.Background(), "a-fake-device-name", Options{Mode: SkipZedboot}); err != nil {
t.Fatal(err)
}
outputs := strings.Split(output.String(), "\n")
args := strings.Split(outputs[0], " ")
if args[4] != "pave" {
t.Fatalf("Paver called with wrong mode")
}
if len(outputs[1]) != 0 {
t.Errorf("Unexpected extra command")
}
}
}
func checkDoesNotSupportLogLevel(t *testing.T, contents string) {
dir := t.TempDir()
bootserverPath := filepath.Join(dir, "bootserver.sh")
if err := os.WriteFile(bootserverPath, []byte(contents), 0o700); err != nil {
t.Fatal(err)
}
var output bytes.Buffer
options := []BuildPaverOption{Stdout(&output)}
paver, err := NewBuildPaver(bootserverPath, dir, options...)
if err != nil {
t.Fatal(err)
}
if err := paver.Pave(context.Background(), "a-fake-device-name"); err != nil {
t.Fatal(err)
}
outputs := strings.Split(output.String(), "\n")
actualZedbootPaverArgs := strings.Split(outputs[0], " ")
actualPaverArgs := strings.Split(outputs[1], " ")
expectedZedbootPaverArgs := []string{
bootserverPath,
"--images", filepath.Join(dir, "images.json"),
"--mode", "pave-zedboot",
"--allow-zedboot-version-mismatch",
"-1",
"-n", "a-fake-device-name",
}
if !stringSliceEquals(actualZedbootPaverArgs, expectedZedbootPaverArgs) {
t.Fatalf("expected args %v, got %v", expectedZedbootPaverArgs, actualZedbootPaverArgs)
}
expectedPaverArgs := []string{
bootserverPath,
"--images", filepath.Join(dir, "images.json"),
"--mode", "pave",
"--fail-fast-if-version-mismatch",
"-1",
"-n", "a-fake-device-name",
}
if !stringSliceEquals(actualPaverArgs, expectedPaverArgs) {
t.Fatalf("expected args %v, got %v", expectedPaverArgs, actualPaverArgs)
}
}
// Check old bootserver with the expected error message does not pass log-level.
func TestDoesNotSupportsLogLevel(t *testing.T) {
checkDoesNotSupportLogLevel(t,
`#!/bin/sh
lhs="$@"
rhs="-log-level debug"
if [ "$lhs" = "$rhs" ]; then
echo "flag provided but not defined: -log-level" 1>&2
echo "Usage of /tmp/ota-tests/8803577180649075905/images/bootserver:" 1>&2
exit 2
fi
echo "$0 $@"`)
}
// Check we don't pass -log-level if we get an unexpected exit code.
func TestDoesNotSupportLogLevelWithUnexpectedExitCode(t *testing.T) {
checkDoesNotSupportLogLevel(t,
`#!/bin/sh
echo "$0 $@"`)
checkDoesNotSupportLogLevel(t,
`#!/bin/sh
lhs="$@"
rhs="-log-level debug"
if [ "$lhs" = "$rhs" ]; then
echo "cannot specify a bootserver mode without an image manifest [--images]" 1>&2
exit 0
fi
echo "$0 $@"`)
checkDoesNotSupportLogLevel(t,
`#!/bin/sh
lhs="$@"
rhs="-log-level debug"
if [ "$lhs" = "$rhs" ]; then
echo "cannot specify a bootserver mode without an image manifest [--images]" 1>&2
exit 3
fi
echo "$0 $@"`)
}
func TestDoesNotSupportLogLevelWithUnexpectedStderr(t *testing.T) {
checkDoesNotSupportLogLevel(t,
`#!/bin/sh
lhs="$@"
rhs="-log-level debug"
if [ "$lhs" = "$rhs" ]; then
exit 2
fi
echo "$0 $@"`)
checkDoesNotSupportLogLevel(t,
`#!/bin/sh
lhs="$@"
rhs="-log-level debug"
if [ "$lhs" = "$rhs" ]; then
echo "unknown flag -log-level" 1>&2
exit 2
fi
echo "$0 $@"`)
}
func TestSupportsLogLevel(t *testing.T) {
contents := `#!/bin/sh
lhs="$@"
rhs="-log-level debug"
if [ "$lhs" = "$rhs" ]; then
echo "cannot specify a bootserver mode without an image manifest [--images]" 1>&2
exit 1
fi
echo "$0 $@"`
dir := t.TempDir()
bootserverPath := filepath.Join(dir, "bootserver.sh")
if err := os.WriteFile(bootserverPath, []byte(contents), 0o700); err != nil {
t.Fatal(err)
}
var output bytes.Buffer
options := []BuildPaverOption{Stdout(&output)}
paver, err := NewBuildPaver(bootserverPath, dir, options...)
if err != nil {
t.Fatal(err)
}
if err := paver.Pave(context.Background(), "a-fake-device-name"); err != nil {
t.Fatal(err)
}
outputs := strings.Split(output.String(), "\n")
actualZedbootPaverArgs := strings.Split(outputs[0], " ")
actualPaverArgs := strings.Split(outputs[1], " ")
expectedZedbootPaverArgs := []string{
bootserverPath,
"--images", filepath.Join(dir, "images.json"),
"--mode", "pave-zedboot",
"--allow-zedboot-version-mismatch",
"-1",
"-n", "a-fake-device-name",
"-log-level", "debug",
}
if !stringSliceEquals(actualZedbootPaverArgs, expectedZedbootPaverArgs) {
t.Fatalf("expected args %v, got %v", expectedZedbootPaverArgs, actualZedbootPaverArgs)
}
expectedPaverArgs := []string{
bootserverPath,
"--images", filepath.Join(dir, "images.json"),
"--mode", "pave",
"--fail-fast-if-version-mismatch",
"-1",
"-n", "a-fake-device-name",
"-log-level", "debug",
}
if !stringSliceEquals(actualPaverArgs, expectedPaverArgs) {
t.Fatalf("expected args %v, got %v", expectedPaverArgs, actualPaverArgs)
}
}