blob: b00bf774c572e0fddd30a31b08a7212ea5b15cbb [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 reboot
import (
"context"
"flag"
"fmt"
"log"
"os"
"testing"
"time"
"go.fuchsia.dev/fuchsia/src/sys/pkg/tests/system-tests/check"
"go.fuchsia.dev/fuchsia/src/sys/pkg/tests/system-tests/flash"
"go.fuchsia.dev/fuchsia/src/sys/pkg/tests/system-tests/pave"
"go.fuchsia.dev/fuchsia/src/testing/host-target-testing/artifacts"
"go.fuchsia.dev/fuchsia/src/testing/host-target-testing/device"
"go.fuchsia.dev/fuchsia/src/testing/host-target-testing/errutil"
"go.fuchsia.dev/fuchsia/src/testing/host-target-testing/ffx"
"go.fuchsia.dev/fuchsia/src/testing/host-target-testing/sl4f"
"go.fuchsia.dev/fuchsia/src/testing/host-target-testing/util"
"go.fuchsia.dev/fuchsia/tools/lib/color"
"go.fuchsia.dev/fuchsia/tools/lib/logger"
)
var c *config
func TestMain(m *testing.M) {
log.SetPrefix("reboot-test: ")
log.SetFlags(log.Ldate | log.Ltime | log.LUTC | log.Lshortfile)
var err error
c, err = newConfig(flag.CommandLine)
if err != nil {
log.Fatalf("failed to create config: %s", err)
}
flag.Parse()
if err = c.validate(); err != nil {
log.Fatalf("config is invalid: %s", err)
}
os.Exit(m.Run())
}
func TestReboot(t *testing.T) {
ctx := context.Background()
l := logger.NewLogger(
logger.TraceLevel,
color.NewColor(color.ColorAuto),
os.Stdout,
os.Stderr,
"reboot-test: ")
l.SetFlags(logger.Ldate | logger.Ltime | logger.LUTC | logger.Lshortfile)
ctx = logger.WithLogger(ctx, l)
if err := doTest(ctx); err != nil {
logger.Errorf(ctx, "test failed: %v", err)
errutil.HandleError(ctx, c.deviceConfig.SerialSocketPath, err)
t.Fatal(err)
}
}
func doTest(ctx context.Context) error {
outputDir, archiveCleanup, err := c.archiveConfig.OutputDir()
if err != nil {
return fmt.Errorf("failed to get archive output directory: %w", err)
}
defer archiveCleanup()
ffx, ffxCleanup, err := c.ffxConfig.NewFfxTool(ctx)
if err != nil {
return fmt.Errorf("failed to create ffx: %w", err)
}
defer ffxCleanup()
deviceClient, err := c.deviceConfig.NewDeviceClient(ctx, ffx)
if err != nil {
return fmt.Errorf("failed to create ota test client: %w", err)
}
defer deviceClient.Close()
l := logger.NewLogger(
logger.TraceLevel,
color.NewColor(color.ColorAuto),
os.Stdout,
os.Stderr,
device.NewEstimatedMonotonicTime(deviceClient, "reboot-test: "),
)
l.SetFlags(logger.Ldate | logger.Ltime | logger.LUTC | logger.Lshortfile)
ctx = logger.WithLogger(ctx, l)
build, err := c.buildConfig.GetBuild(ctx, deviceClient, outputDir)
if err != nil {
return fmt.Errorf("failed to get downgrade build: %w", err)
}
if build == nil {
return fmt.Errorf("no build configured")
}
if err := util.RunWithTimeout(ctx, c.paveTimeout, func() error {
err := initializeDevice(ctx, deviceClient, ffx, build)
return err
}); err != nil {
return fmt.Errorf("initialization failed: %w", err)
}
return testReboot(ctx, deviceClient, ffx.IsolateDir(), build)
}
func testReboot(
ctx context.Context,
device *device.Client,
ffxIsolateDir ffx.IsolateDir,
build artifacts.Build,
) error {
if err := sleepAfterReboot(ctx, device); err != nil {
return err
}
for i := 1; i <= c.cycleCount; i++ {
logger.Infof(ctx, "Reboot Attempt %d", i)
// Protect against the test stalling out by wrapping it in a closure,
// setting a timeout on the context, and running the actual test in a
// closure.
if err := util.RunWithTimeout(ctx, c.cycleTimeout, func() error {
return doTestReboot(ctx, device, ffxIsolateDir, build)
}); err != nil {
return fmt.Errorf("Reboot Cycle %d failed: %w", i, err)
}
}
return nil
}
func doTestReboot(
ctx context.Context,
device *device.Client,
ffxIsolateDir ffx.IsolateDir,
build artifacts.Build,
) error {
// We don't install an OTA, so we don't need to prefetch the blobs.
repo, err := build.GetPackageRepository(
ctx,
artifacts.LazilyFetchBlobs,
ffxIsolateDir,
)
if err != nil {
return fmt.Errorf("unable to get repository: %w", err)
}
updatePackage, err := repo.OpenUpdatePackage(ctx, "update/0")
if err != nil {
return fmt.Errorf("error opening update/0: %w", err)
}
// Install version N on the device if it is not already on that version.
expectedSystemImage, err := updatePackage.OpenSystemImagePackage(ctx)
if err != nil {
return fmt.Errorf("error extracting expected system image merkle: %w", err)
}
var expectedConfig *sl4f.Configuration
if c.checkABR {
config, err := check.DetermineCurrentABRConfig(ctx, device, repo)
if err != nil {
return fmt.Errorf("error determining target config: %w", err)
}
expectedConfig = config
}
if err := check.ValidateDevice(
ctx,
device,
expectedSystemImage,
expectedConfig,
c.checkABR,
); err != nil {
return err
}
if err := device.Reboot(ctx); err != nil {
return fmt.Errorf("error rebooting: %w", err)
}
if err := check.ValidateDevice(
ctx,
device,
expectedSystemImage,
expectedConfig,
c.checkABR,
); err != nil {
return fmt.Errorf("failed to validate device: %w", err)
}
if err := sleepAfterReboot(ctx, device); err != nil {
return err
}
return nil
}
func sleepAfterReboot(ctx context.Context, device *device.Client) error {
if c.sleepAfterReboot != 0 {
logger.Infof(ctx, "Sleeping %s seconds after reboot", c.sleepAfterReboot)
disconnectionCh := device.DisconnectionListener()
select {
case <-time.After(c.sleepAfterReboot * time.Second):
case <-disconnectionCh:
return fmt.Errorf("Device unexpectedly disconnected")
}
}
return nil
}
func initializeDevice(
ctx context.Context,
device *device.Client,
ffx *ffx.FFXTool,
build artifacts.Build,
) error {
logger.Infof(ctx, "Initializing device")
repo, err := build.GetPackageRepository(ctx, artifacts.LazilyFetchBlobs, ffx.IsolateDir())
if err != nil {
return err
}
updatePackage, err := repo.OpenUpdatePackage(ctx, "update/0")
if err != nil {
return fmt.Errorf("error opening update/0: %w", err)
}
// Install version N on the device if it is not already on that version.
expectedSystemImage, err := updatePackage.OpenSystemImagePackage(ctx)
if err != nil {
return fmt.Errorf("error extracting expected system image: %w", err)
}
// Only provision if the device is not running the expected version.
upToDate, err := check.IsDeviceUpToDate(ctx, device, expectedSystemImage)
if err != nil {
return fmt.Errorf("failed to check if up to date during initialization: %w", err)
}
if upToDate {
logger.Infof(ctx, "device already up to date")
} else {
sshPrivateKey, err := c.deviceConfig.SSHPrivateKey()
if err != nil {
return fmt.Errorf("failed to get ssh key: %w", err)
}
if c.useFlash {
if err := flash.FlashDevice(ctx, device, ffx, build, sshPrivateKey.PublicKey()); err != nil {
return fmt.Errorf("failed to flash device during initialization: %w", err)
}
} else {
if err := pave.PaveDevice(ctx, device, ffx, build, sshPrivateKey.PublicKey()); err != nil {
return fmt.Errorf("failed to pave device during initialization: %w", err)
}
}
}
var expectedConfig *sl4f.Configuration
if c.checkABR {
// Check if we support ABR. If so, we always boot into A after a pave.
expectedConfig, err = check.DetermineCurrentABRConfig(ctx, device, repo)
if err != nil {
return err
}
if !upToDate && expectedConfig != nil {
config := sl4f.ConfigurationA
expectedConfig = &config
}
}
if err := check.ValidateDevice(
ctx,
device,
expectedSystemImage,
expectedConfig,
c.checkABR,
); err != nil {
return err
}
return nil
}