| // Copyright 2021 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 e2e |
| |
| import ( |
| "context" |
| "errors" |
| "flag" |
| "log" |
| "os" |
| "os/exec" |
| "path/filepath" |
| "sync" |
| "testing" |
| |
| "go.fuchsia.dev/fuchsia/tools/botanist/constants" |
| "go.fuchsia.dev/fuchsia/tools/fvdl/e2e/e2etest" |
| "go.fuchsia.dev/fuchsia/tools/lib/ffxutil" |
| ) |
| |
| var ( |
| aemuDir = flag.String("aemu_dir", "", "Path to AEMU directory") |
| grpcWebProxyDir = flag.String("grpcwebproxy_dir", "", "Path to grpcwebproxy directory") |
| deviceLauncherDir = flag.String("device_launcher_dir", "", "Path to device launcher directory") |
| targetCPU = flag.String("target_cpu", "", "Toolchain used to build Fuchsia image.") |
| emulatorPath string |
| fvdl string |
| deviceLauncher string |
| grpcwebproxyPath string |
| fuchsiaBuildDir string |
| hostToolsDir string |
| ffx string |
| ffxInstance *ffxutil.FFXInstance |
| fvm string |
| zbi string |
| kernel string |
| runtimeDir string |
| amberFiles string |
| setupOnce sync.Once |
| ) |
| |
| func setUp(t *testing.T, intree bool) { |
| setupOnce.Do(func() { |
| var err error |
| |
| deviceLauncher = *deviceLauncherDir |
| if _, err := os.Stat(deviceLauncher); os.IsNotExist(err) { |
| t.Fatalf("Invalid vdl path %q err: %s", deviceLauncher, err) |
| } |
| if emulatorPath = e2etest.FindFileFromDir(*aemuDir, "emulator"); emulatorPath == "" { |
| t.Fatalf("Cannot find emulator binary from %q", runtimeDir) |
| } |
| if grpcwebproxyPath = e2etest.FindFileFromDir(*grpcWebProxyDir, "grpcwebproxy"); grpcwebproxyPath == "" { |
| t.Fatalf("Cannot find grpcwebproxy binary from %q", runtimeDir) |
| } |
| |
| ex, err := os.Executable() |
| if err != nil { |
| t.Fatal(err) |
| } |
| exDir := filepath.Dir(ex) |
| |
| runtimeDir = e2etest.FindDirFromDir(exDir, "fvdl_test_runtime_deps") |
| if len(runtimeDir) == 0 { |
| t.Fatalf("Cannot find fvdl_test_runtime_deps binary from %s", exDir) |
| } |
| t.Logf("[test info] fuchsia_build_dir %s", exDir) |
| t.Logf("[test info] fvdl_test_runtime_deps %s", runtimeDir) |
| hostToolsDir = filepath.Join(runtimeDir, "host_tools") |
| if _, err := os.Stat(hostToolsDir); os.IsNotExist(err) { |
| t.Fatalf("Invalid host tools dir %q err: %s", hostToolsDir, err) |
| } |
| fvdl = filepath.Join(hostToolsDir, "fvdl") |
| if _, err := os.Stat(fvdl); os.IsNotExist(err) { |
| t.Fatal(err) |
| } |
| ffx = filepath.Join(hostToolsDir, "ffx") |
| if _, err := os.Stat(ffx); os.IsNotExist(err) { |
| t.Fatal(err) |
| } |
| fuchsiaBuildDir = filepath.Join(runtimeDir, "images") |
| if _, err := os.Stat(fuchsiaBuildDir); os.IsNotExist(err) { |
| t.Fatalf("Invalid fuchsia build dir %q err: %s", fuchsiaBuildDir, err) |
| } |
| zbi = filepath.Join(fuchsiaBuildDir, "fuchsia.zbi") |
| if _, err := os.Stat(zbi); os.IsNotExist(err) { |
| t.Fatal(err) |
| } |
| fvm = filepath.Join(fuchsiaBuildDir, "fvm.blk") |
| if _, err := os.Stat(fvm); os.IsNotExist(err) { |
| t.Fatal(err) |
| } |
| kernel = filepath.Join(fuchsiaBuildDir, "multiboot.bin") |
| if _, err := os.Stat(kernel); os.IsNotExist(err) { |
| t.Fatal(err) |
| } |
| packages := filepath.Join(fuchsiaBuildDir, "packages.tar.gz") |
| if _, err := os.Stat(packages); os.IsNotExist(err) { |
| t.Fatal(err) |
| } |
| if err := os.Mkdir(filepath.Join(runtimeDir, ".jiri_root"), 0o755); err != nil && !os.IsExist(err) { |
| t.Fatal(err) |
| } |
| if err := e2etest.ExtractPackage(packages, fuchsiaBuildDir); err != nil { |
| t.Fatal(err) |
| } |
| amberFiles = filepath.Join(fuchsiaBuildDir, "amber-files") |
| if intree { |
| if err := e2etest.GenerateFakeArgsFile(filepath.Join(fuchsiaBuildDir, "args.gn")); err != nil { |
| t.Fatal(err) |
| } |
| if err := e2etest.GenerateFakeImagesJson(filepath.Join(fuchsiaBuildDir, "images.json")); err != nil { |
| t.Fatal(err) |
| } |
| } |
| if err := e2etest.CreateSSHKeyPairFiles(runtimeDir); err != nil { |
| t.Fatal(err) |
| } |
| }) |
| } |
| |
| // runVDLWithArgs runs fvdl, if intree, use environment variables to set tools and image path. |
| // if not intree, images will be downloaded from GCS. |
| func runVDLWithArgs(ctx context.Context, t *testing.T, args []string, intree bool) string { |
| testOut, ok := os.LookupEnv("FUCHSIA_TEST_OUTDIR") |
| if !ok { |
| testOut = t.TempDir() |
| } |
| if err := os.MkdirAll(filepath.Join(testOut, t.Name()), 0o755); err != nil { |
| t.Fatal(err) |
| } |
| // Create a new isolated ffx instance. |
| ffxInstance, err := ffxutil.NewFFXInstance(ffx, "", os.Environ(), os.Getenv(constants.NodenameEnvKey), os.Getenv(constants.SSHKeyEnvKey), testOut) |
| if err != nil { |
| t.Fatal(err) |
| } |
| ffxConfigPath := filepath.Join(testOut, "ffx_config.json") |
| vdlOut := filepath.Join(testOut, t.Name(), "vdl_out") |
| t.Logf("[test info] writing vdl output to %s", vdlOut) |
| td := t.TempDir() |
| |
| t.Cleanup(func() { |
| killEmu(ctx, t, intree, vdlOut) |
| if ffxInstance != nil { |
| if err := ffxInstance.Stop(); err != nil { |
| t.Logf("FFX didn't stop the running daemon %s", err) |
| } |
| } |
| }) |
| |
| maxTries := 2 |
| |
| for tries := 0; tries < maxTries; tries++ { |
| cmd := exec.CommandContext( |
| ctx, |
| fvdl, |
| append( |
| args, |
| "--vdl-output", vdlOut, |
| "--emulator-log", filepath.Join(testOut, t.Name(), "emu_log"), |
| "--amber-unpack-root", filepath.Join(td, "packages"), |
| "--isolated-ffx-config-path", ffxConfigPath, |
| )..., |
| ) |
| if intree { |
| cmd.Env = append( |
| os.Environ(), |
| "FUCHSIA_BUILD_DIR="+fuchsiaBuildDir, |
| "FUCHSIA_ZBI_COMPRESSION=zstd", |
| "HOST_OUT_DIR="+hostToolsDir, |
| "PREBUILT_AEMU_DIR="+mustAbs(t, emulatorPath), |
| "PREBUILT_GRPCWEBPROXY_DIR="+mustAbs(t, grpcwebproxyPath), |
| "PREBUILT_VDL_DIR="+mustAbs(t, deviceLauncher), |
| "FVDL_INVOKER=fvdl_e2e_test", |
| ) |
| } else { |
| t.Logf("[test info] setting HOME to: %s", runtimeDir) |
| // Set $HOME to runtimeDir so that fvdl can find the ssh key files, which are |
| // expected in $HOME/.ssh/... |
| cmd.Env = append(os.Environ(), "HOME="+runtimeDir, "FVDL_INVOKER=fvdl_e2e_test") |
| } |
| cmd.Dir = td |
| cmd.Stdout = os.Stdout |
| cmd.Stderr = os.Stderr |
| if err := cmd.Run(); err != nil { |
| var e *exec.ExitError |
| if errors.As(err, &e) { |
| // ExitCode == 1 means something went wrong with the launcher. Don't retry. |
| if e.ExitCode() == 1 { |
| t.Fatal(err) |
| } |
| } |
| if tries == maxTries-1 { |
| t.Fatal(err) |
| } |
| log.Printf("Retry launching emulator... got err %s", err) |
| } else { |
| break |
| } |
| } |
| return vdlOut |
| } |
| |
| func mustAbs(t *testing.T, p string) string { |
| t.Helper() |
| abs, err := filepath.Abs(p) |
| if err != nil { |
| t.Fatalf("Failed to convert %q to absolute path: %v", p, err) |
| } |
| return abs |
| } |
| |
| // killEmu shuts down fvdl with action kill command |
| func killEmu(ctx context.Context, t *testing.T, intree bool, vdlOut string) { |
| t.Logf("[test info] killing fvdl using proto: %s", vdlOut) |
| var cmd *exec.Cmd |
| if intree { |
| cmd = exec.CommandContext(ctx, fvdl, "kill", "--launched-proto", vdlOut) |
| cmd.Env = append( |
| os.Environ(), |
| "FUCHSIA_BUILD_DIR="+fuchsiaBuildDir, |
| "FUCHSIA_ZBI_COMPRESSION=zstd", |
| "HOST_OUT_DIR="+hostToolsDir, |
| "PREBUILT_AEMU_DIR="+mustAbs(t, emulatorPath), |
| "PREBUILT_GRPCWEBPROXY_DIR="+mustAbs(t, grpcwebproxyPath), |
| "PREBUILT_VDL_DIR="+mustAbs(t, deviceLauncher), |
| "FVDL_INVOKER=fvdl_e2e_test", |
| ) |
| } else { |
| cmd = exec.CommandContext( |
| ctx, |
| fvdl, |
| "--sdk", |
| "kill", |
| "--launched-proto", |
| vdlOut, |
| "-d", |
| mustAbs(t, filepath.Join(deviceLauncher, "device_launcher")), |
| ) |
| cmd.Env = append(os.Environ(), "FVDL_INVOKER=fvdl_e2e_test") |
| } |
| cmd.Stdout = os.Stdout |
| cmd.Stderr = os.Stderr |
| if err := cmd.Run(); err != nil { |
| t.Errorf("shutting down fvdl errored: %w", err) |
| } |
| } |