summary refs log tree commit diff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/boot.rs241
1 files changed, 241 insertions, 0 deletions
diff --git a/tests/boot.rs b/tests/boot.rs
new file mode 100644
index 0000000..b4e38e1
--- /dev/null
+++ b/tests/boot.rs
@@ -0,0 +1,241 @@
+// Copyright 2019 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use std::env;
+use std::fs;
+use std::io::{stdout, Write};
+use std::mem;
+use std::os::unix::fs::symlink;
+use std::path::{Path, PathBuf};
+use std::process::Command;
+use std::sync::Once;
+
+use libc::{cpu_set_t, sched_getaffinity};
+
+use crosvm::{linux, Config, Executable};
+use devices::{SerialParameters, SerialType};
+
+const CHROOT_KERNEL_PATH: &str = "/mnt/host/source/src/third_party/kernel/v4.19/";
+const CONTAINER_VM_DEFCONFIG: &str = "arch/x86/configs/chromiumos-container-vm-x86_64_defconfig";
+const KERNEL_REPO: &str = "https://chromium.googlesource.com/chromiumos/third_party/kernel";
+const KERNEL_REPO_BRANCH: &str = "chromeos-4.19";
+// TODO(zachr): this URL is a placeholder until the automated builder is running and we've settled
+// on a location.
+const KERNEL_PREBUILT: &str = "http://storage.googleapis.com/crosvm-testing";
+
+/// Returns the number of CPUs that this process and its children can use by querying our process's
+/// CPU affinity.
+fn get_cpu_count() -> usize {
+    unsafe {
+        let mut set: cpu_set_t = mem::zeroed();
+        let ret = sched_getaffinity(0, mem::size_of::<cpu_set_t>(), &mut set);
+        if ret != 0 {
+            // A good guess.
+            4
+        } else {
+            // The cpu_set_t is normally counted using the CPU_COUNT macro, but we don't have that
+            // in Rust. Because it's always a bitset, we will treat it like one here.
+            let set: [u8; mem::size_of::<cpu_set_t>()] = mem::transmute(set);
+            set.iter().map(|b| b.count_ones() as usize).sum()
+        }
+    }
+}
+
+/// Clones a chrome os kernel into the given path.
+fn clone_kernel_source(dir: &Path) {
+    let status = Command::new("git")
+        .args(&[
+            "clone",
+            "--depth",
+            "1",
+            "--branch",
+            KERNEL_REPO_BRANCH,
+            KERNEL_REPO,
+        ])
+        .arg(dir)
+        .status()
+        .expect("failed to execute git");
+    if !status.success() {
+        panic!("failed to clone kernel source: {}", status);
+    }
+}
+
+// Kernel binary algorithm.
+// 1: If CROSVM_CARGO_TEST_KERNEL_BINARY is in the env:
+//        If CROSVM_CARGO_TEST_KERNEL_BINARY is empty, skip step 3.
+//        If CROSVM_CARGO_TEST_KERNEL_BINARY does not exist, panic.
+// 2: If "bzImage" exists in the target directory use that.
+// 3: Download "bzImage" from the KERNEL_PREBUILT url and use that.
+//    If the download does not work, go to the kernel source algorithm.
+//
+// Kernel source algorithm
+// 1: If CROSVM_CARGO_TEST_KERNEL_SOURCE is in the env, use that.
+//    If CROSVM_CARGO_TEST_KERNEL_SOURCE does not exist, panic
+// 2: If CHROOT_KERNEL_PATH exists, use that.
+// 3: Checkout and use the chromeos kernel.
+//
+// Kernel config algorithm
+// 1: If the .config already exists in the kernel source, use that.
+// 2: If the CONTAINER_VM_DEFCONFIG exists in the kernel source, use that.
+// 3: Use `make defconfig`.
+fn prepare_kernel_once(dir: &Path) {
+    let kernel_binary = dir.join("bzImage");
+
+    let mut download_prebuilt = true;
+    if let Ok(env_kernel_binary) = env::var("CROSVM_CARGO_TEST_KERNEL_BINARY") {
+        if env_kernel_binary.is_empty() {
+            download_prebuilt = false;
+        } else {
+            println!(
+                "using kernel binary from enviroment `{}`",
+                env_kernel_binary
+            );
+            let env_kernel_binary = PathBuf::from(env_kernel_binary);
+            if env_kernel_binary.exists() {
+                symlink(env_kernel_binary, &kernel_binary)
+                    .expect("failed to create symlink for kernel binary");
+                return;
+            } else {
+                panic!(
+                    "expected kernel binary at `{}`",
+                    env_kernel_binary.display()
+                )
+            }
+        }
+    }
+
+    println!("looking for kernel binary at `{}`", kernel_binary.display());
+    if kernel_binary.exists() {
+        println!("using kernel binary at `{}`", kernel_binary.display());
+        return;
+    }
+
+    if download_prebuilt {
+        // Resolve the base URL into a specific path for this architecture.
+        let kernel_prebuilt = format!(
+            "{}/{}/{}",
+            KERNEL_PREBUILT,
+            env::consts::ARCH,
+            "latest-bzImage"
+        );
+        println!(
+            "downloading prebuilt kernel binary from `{}`",
+            kernel_prebuilt
+        );
+        let status = Command::new("curl")
+            .args(&["--fail", "--location"])
+            .arg("--output")
+            .arg(&kernel_binary)
+            .arg(kernel_prebuilt)
+            .status();
+        if let Ok(status) = status {
+            if status.success() {
+                println!("using prebuilt kernel binary");
+                return;
+            }
+        }
+
+        println!("failed to download prebuilt kernel binary");
+    }
+
+    let kernel_source = if let Ok(env_kernel_source) = env::var("CROSVM_CARGO_TEST_KERNEL_SOURCE") {
+        if Path::new(&env_kernel_source).is_dir() {
+            PathBuf::from(env_kernel_source)
+        } else {
+            panic!("expected kernel source at `{}`", env_kernel_source);
+        }
+    } else if Path::new(CHROOT_KERNEL_PATH).is_dir() {
+        PathBuf::from(CHROOT_KERNEL_PATH)
+    } else {
+        let kernel_source = dir.join("kernel-source");
+        // Check for kernel source
+        if !kernel_source.is_dir() {
+            clone_kernel_source(&kernel_source);
+        }
+        kernel_source
+    };
+
+    println!("building kernel from source `{}`", kernel_source.display());
+
+    // Special provisions for using the ChromeOS kernel source and its config used in crostini.
+    let current_config = kernel_source.join(".config");
+    let container_vm_defconfig = kernel_source.join(CONTAINER_VM_DEFCONFIG);
+    if current_config.exists() {
+        fs::copy(current_config, dir.join(".config"))
+            .expect("failed to copy existing kernel config");
+    } else if container_vm_defconfig.exists() {
+        fs::copy(container_vm_defconfig, dir.join(".config"))
+            .expect("failed to copy  chromiumos container vm kernel config");
+    } else {
+        // TODO(zachr): the defconfig for vanilla kernels is probably inadequate. There should
+        // probably be a step where additional options are added to the resulting .config.
+        let status = Command::new("make")
+            .current_dir(&kernel_source)
+            .arg(format!("O={}", dir.display()))
+            .arg("defconfig")
+            .status()
+            .expect("failed to execute make");
+        if !status.success() {
+            panic!("failed to default config kernel: {}", status);
+        }
+    }
+
+    let output = Command::new("make")
+        .current_dir(&kernel_source)
+        .arg(format!("O={}", dir.display()))
+        .args(&["bzImage", "-j"])
+        .arg(format!("{}", get_cpu_count()))
+        .output()
+        .expect("failed to execute make");
+    if !output.status.success() {
+        let _ = stdout().lock().write(&output.stderr);
+        panic!("failed to build kernel: {}", output.status);
+    }
+
+    fs::copy(dir.join("arch/x86/boot/bzImage"), &kernel_binary)
+        .expect("failed to copy kernel binary");
+}
+
+/// Gets the target directory path for artifacts.
+fn get_target_path() -> PathBuf {
+    env::current_exe()
+        .ok()
+        .map(|mut path| {
+            path.pop();
+            path
+        })
+        .expect("failed to get target dir")
+}
+
+/// Thread-safe method for preparing a kernel and returning the path to its binary.
+fn prepare_kernel() -> PathBuf {
+    // Lots of unit tests need the kernel, but it should only get prepared once by any arbitrary
+    // test. The rest of the tests should wait until the arbitrary one finishes.
+    let default_linux_dir = get_target_path();
+    static PREP_ONCE: Once = Once::new();
+    PREP_ONCE.call_once(|| prepare_kernel_once(&default_linux_dir));
+    default_linux_dir.join("bzImage")
+}
+
+#[test]
+fn boot() {
+    let kernel_path = prepare_kernel();
+
+    let mut c = Config::default();
+    c.sandbox = false;
+    c.serial_parameters.insert(
+        1,
+        SerialParameters {
+            type_: SerialType::Sink,
+            path: None,
+            num: 1,
+            console: false,
+            stdin: false,
+        },
+    );
+    c.executable_path = Some(Executable::Kernel(kernel_path));
+
+    let r = linux::run_config(c);
+    r.expect("failed to run linux");
+}