voa-core 0.4.1

File Hierarchy for the Verification of OS Artifacts (VOA)
Documentation
//! User mode tests
//!
//! Also see <https://uapi-group.org/specifications/specs/file_hierarchy_for_the_verification_of_os_artifacts/#user-mode>
//!
//! Load path list in these containerized user mode tests:
//!
//! ```
//! LoadPathList([
//!     LoadPath {
//!         path: "/home/user/.config/voa",
//!         ephemeral: false,
//!         writable: true,
//!     },
//!     LoadPath {
//!         path: "/etc/xdg/voa",
//!         ephemeral: false,
//!         writable: false,
//!     },
//!     LoadPath {
//!         path: "/home/user/.local/share/voa",
//!         ephemeral: false,
//!         writable: false,
//!     },
//!     LoadPath {
//!         path: "/usr/local/share/voa",
//!         ephemeral: false,
//!         writable: false,
//!     },
//!     LoadPath {
//!         path: "/usr/share/voa",
//!         ephemeral: false,
//!         writable: false,
//!     },
//! ])
//! ```

use std::{collections::HashMap, env::vars, fs::copy, os::unix::fs::chown, path::PathBuf};

use log::debug;
use serde::Deserialize;
use testresult::TestResult;
use voa_core::Error;

use crate::{TestObject, init_logger, setup};

#[derive(Debug, Deserialize, PartialEq)]
struct VerifierOutput {
    load_path: String,
    verifier: String,
}

/// Binary built from `examples/voa-list.rs`
const LIST_VOA_CMD: &str = "/usr/local/bin/examples/voa-list";
const USER: &str = "user";

#[test]
fn user_mode_test() -> TestResult {
    init_logger();

    const SETUP: &[TestObject] = &[
        TestObject::Path("/usr/local/share/voa/arch/packages/default/openpgp/"),
        TestObject::File("/usr/local/share/voa/arch/packages/default/openpgp/foo.pgp"),
        TestObject::Path("/home/user/.local/share/voa/arch/packages/default/openpgp/"),
        TestObject::File("/home/user/.local/share/voa/arch/packages/default/openpgp/bar.pgp"),
    ];

    setup(SETUP)?;

    // Create all system users and their homes
    change_user_run::create_users(&[USER], None, None)?;

    let env_list = [
        "LLVM_PROFILE_FILE",
        "CARGO_LLVM_COV",
        "CARGO_LLVM_COV_SHOW_ENV",
        "CARGO_LLVM_COV_TARGET_DIR",
        "RUSTFLAGS",
        "RUSTDOCFLAGS",
    ];
    let mut envs: HashMap<String, String> = HashMap::new();
    // Note: This instructs relevant .profraw data to be written to /tmp.
    envs.insert(
        "LLVM_PROFILE_FILE".to_string(),
        "/tmp/voa-%p-%16m.profraw".to_string(),
    );

    let cmdout = change_user_run::run_command_as_user(
        LIST_VOA_CMD,
        &["--os-id", "arch", "--role", "packages"],
        None,
        &env_list,
        Some(envs),
        USER,
    )
    .map_err(|source| Error::InternalError {
        context: format!("{source}"),
    })?;

    let res: Vec<VerifierOutput> = serde_json::from_str(&cmdout.stdout).unwrap();

    assert_eq!(res.len(), 2);

    assert_eq!(
        res[0],
        VerifierOutput {
            load_path: "/home/user/.local/share/voa".into(),
            verifier: "/home/user/.local/share/voa/arch/packages/default/openpgp/bar.pgp".into()
        }
    );

    assert_eq!(
        res[1],
        VerifierOutput {
            load_path: "/usr/local/share/voa".into(),
            verifier: "/usr/local/share/voa/arch/packages/default/openpgp/foo.pgp".into()
        }
    );

    // -- coverage data

    if let Some(cov_target_dir) = vars().find_map(|(key, value)| {
        if key == "CARGO_LLVM_COV_TARGET_DIR" {
            Some(PathBuf::from(value))
        } else {
            None
        }
    }) {
        debug!("Found CARGO_LLVM_COV_TARGET_DIR={cov_target_dir:?}");
        for dir_entry in PathBuf::from("/tmp").read_dir()? {
            let dir_entry = dir_entry?;
            let from = dir_entry.path();
            let Some(file_name) = &from.file_name() else {
                continue;
            };
            if let Some(extension) = from.extension()
                && extension == "profraw"
            {
                let target_file = cov_target_dir.join(file_name);
                debug!("Copying {from:?} to {target_file:?}");
                copy(&from, &target_file)?;
                chown(&target_file, Some(0), Some(0))?;
            }
        }
    }

    Ok(())
}