crosvm/cros_tracing/tests/trace_marker.rs
Federico 'Morg' Pareschi 2e93874a25 cros_tracing: Add trace_marker integration tests
This CL adds a new "root" test suite for trace_marker integration tests,
since the tests require root to run in order to access the kernel's
tracefs mounted partition.

BUG=b:273379306
TEST=CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUNNER='sudo -E' cargo test --features=trace_marker

Change-Id: I4656374023d91d87959f6ce0d59eb9e51bdc77bd
Reviewed-on: https://chromium-review.googlesource.com/c/crosvm/crosvm/+/4349039
Reviewed-by: Keiichi Watanabe <keiichiw@chromium.org>
Auto-Submit: Morg <morg@chromium.org>
Commit-Queue: Morg <morg@chromium.org>
2023-04-12 01:21:39 +00:00

157 lines
5.1 KiB
Rust

// Copyright 2023 The ChromiumOS Authors
// 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::env::current_exe;
use std::fs::File;
use std::io::BufRead;
use std::io::BufReader;
use std::process::Command;
use cros_tracing::*;
const TRACE_FILE: &str = "/sys/kernel/tracing/trace";
const TRACE_CONTEXT_INFO: &str = "/sys/kernel/tracing/options/context-info";
const TRACING_ON: &str = "/sys/kernel/tracing/tracing_on";
fn setup() {
// Make sure tracing is enabled.
std::fs::write(TRACING_ON, b"1").unwrap();
// Remove extra noise from trace file for easier parsing
std::fs::write(TRACE_CONTEXT_INFO, b"0").unwrap();
// Clear the trace backlog by writing an empty string, in case we have extra
// rogue messages in the trace buffer
std::fs::write(TRACE_FILE, b"").unwrap();
init();
}
fn cleanup() {
// Stop tracing.
std::fs::write(TRACING_ON, b"0").unwrap();
// Reset trace file format back to how it was.
std::fs::write(TRACE_CONTEXT_INFO, b"1").unwrap();
}
fn trace_simple_print() {
let reader = BufReader::new(File::open(TRACE_FILE).ok().unwrap());
let message1 = "Simple print test one";
let message2 = "Simple print test two";
trace_simple_print!("{message1}");
trace_simple_print!("{message2}");
// Read contents of the file, skip the first two lines which are just a preamble.
let mut lines = reader.lines().map(|l| l.unwrap()).skip(2);
// Check the printed lines are in order.
// We need to use contains instead of matching the full string because each time we
// print to trace_marker we will get unique data like PID and timestamps that we cannot
// rely on, but the contents of the message itself should always contain our string.
assert!(lines.next().unwrap().contains(message1));
assert!(lines.next().unwrap().contains(message2));
}
fn push_descriptors() {
let mut keep_rds = Vec::new();
push_descriptors!(&mut keep_rds);
// We cannot know the fd of the trace marker file beforehand but we can check if there
// is only one fd in the vector it means we can assume it's the trace_marker one.
assert_eq!(keep_rds.len(), 1);
}
/// Executes the individual test `name` with root, in the same environment as the test suite,
/// if it does not already have root privileges. Sudo needs to be set up to run passwordless
/// or have cached credentials. The parent process spawns a child that runs with higher privileges
/// for that individual `name` test, and then waits for its completion.
///
/// Returns `true` if the test suite already has root privilege, in which case it
/// proceeds to run the test without forking, otherwise it returns `false` to let the
/// test suite know that the test was run by the child process instead.
///
/// # Arguments
///
/// * `name` - Name of the individual test to execute as root
///
/// # Examples
///
/// ```
/// libtest_mimic::Trial::test("test_with_root", move || {
/// if run_test_with_root("test_with_root") {
/// // This part only executes with root
/// function_to_test();
/// }
/// Ok(())
/// });
/// ```
fn run_test_with_root(name: &str) -> bool {
// This test needs to run as root, so if we aren't root we need to re-execute ourselves
// with sudo.
let is_root = match env::var("USER") {
Ok(val) => val == "root",
Err(_) => false,
};
if !is_root {
let can_sudo = Command::new("sudo")
.args(["--askpass", "true"])
.env("SUDO_ASKPASS", "false")
.output()
.unwrap();
if !can_sudo.status.success() {
panic!("This test needs to be run as root or with passwordless sudo.");
}
let result = Command::new("sudo")
.args([
"--preserve-env",
current_exe().unwrap().to_str().unwrap(),
"--nocapture",
"--ignored",
"--exact",
name,
])
.status()
.unwrap();
if !result.success() {
panic!("Test {name} forked with root by the trace_marker suite failed.");
}
return false;
}
true
}
fn main() {
let args = libtest_mimic::Arguments {
// Force single-threaded execution to make sure there is no race condition between
// data written to the trace_marker file. In case the tracefs environment is being
// used by other processes on the system, these tests might fail.
test_threads: Some(1),
..libtest_mimic::Arguments::from_args()
};
let tests = vec![
libtest_mimic::Trial::test("trace_simple_print", move || {
if run_test_with_root("trace_simple_print") {
setup();
trace_simple_print();
cleanup();
}
Ok(())
}),
libtest_mimic::Trial::test("push_descriptors", move || {
if run_test_with_root("push_descriptors") {
setup();
push_descriptors();
cleanup();
}
Ok(())
}),
];
libtest_mimic::run(&args, tests).exit();
}