Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -163,19 +163,15 @@ test-unit target=default-target features="":

# runs tests that requires being run separately, for example due to global state
test-isolated target=default-target features="" :
{{ cargo-cmd }} test {{ if features =="" {''} else if features=="no-default-features" {"--no-default-features" } else {"--no-default-features -F init-paging," + features } }} --profile={{ if target == "debug" { "dev" } else { target } }} {{ target-triple-flag }} -p hyperlight-host --lib -- sandbox::uninitialized::tests::test_trace_trace --exact --ignored
{{ cargo-cmd }} test {{ if features =="" {''} else if features=="no-default-features" {"--no-default-features" } else {"--no-default-features -F init-paging," + features } }} --profile={{ if target == "debug" { "dev" } else { target } }} {{ target-triple-flag }} -p hyperlight-host --lib -- sandbox::uninitialized::tests::test_log_trace --exact --ignored
{{ cargo-cmd }} test {{ if features =="" {''} else if features=="no-default-features" {"--no-default-features" } else {"--no-default-features -F init-paging," + features } }} --profile={{ if target == "debug" { "dev" } else { target } }} {{ target-triple-flag }} -p hyperlight-host --lib -- sandbox::initialized_multi_use::tests::create_1000_sandboxes --exact --ignored
{{ cargo-cmd }} test {{ if features =="" {''} else if features=="no-default-features" {"--no-default-features" } else {"--no-default-features -F init-paging," + features } }} --profile={{ if target == "debug" { "dev" } else { target } }} {{ target-triple-flag }} -p hyperlight-host --lib -- sandbox::outb::tests::test_log_outb_log --exact --ignored
{{ cargo-cmd }} test {{ if features =="" {''} else if features=="no-default-features" {"--no-default-features" } else {"--no-default-features -F init-paging," + features } }} --profile={{ if target == "debug" { "dev" } else { target } }} {{ target-triple-flag }} -p hyperlight-host --lib -- mem::shared_mem::tests::test_drop --exact --ignored
{{ cargo-cmd }} test {{ if features =="" {''} else if features=="no-default-features" {"--no-default-features" } else {"--no-default-features -F init-paging," + features } }} --profile={{ if target == "debug" { "dev" } else { target } }} {{ target-triple-flag }} -p hyperlight-host --test integration_test -- log_message --exact --ignored
@# metrics tests
{{ cargo-cmd }} test {{ if features =="" {''} else if features=="no-default-features" {"--no-default-features" } else {"--no-default-features -F function_call_metrics,init-paging," + features } }} --profile={{ if target == "debug" { "dev" } else { target } }} {{ target-triple-flag }} -p hyperlight-host --lib -- metrics::tests::test_metrics_are_emitted --exact
# runs integration tests. Guest can either be "rust" or "c"
test-integration guest target=default-target features="":
@# run execute_on_heap test with feature "executable_heap" on and off
{{if os() == "windows" { "$env:" } else { "" } }}GUEST="{{guest}}"{{if os() == "windows" { ";" } else { "" } }} {{ cargo-cmd }} test --profile={{ if target == "debug" { "dev" } else { target } }} {{ target-triple-flag }} --test integration_test execute_on_heap {{ if features =="" {" --features executable_heap"} else {"--features executable_heap," + features} }} -- --ignored
{{if os() == "windows" { "$env:" } else { "" } }}GUEST="{{guest}}"{{if os() == "windows" { ";" } else { "" } }} {{ cargo-cmd }} test --profile={{ if target == "debug" { "dev" } else { target } }} {{ target-triple-flag }} --test integration_test execute_on_heap {{ if features =="" {""} else {"--features " + features} }} -- --ignored
@# run execute_on_heap test with feature "executable_heap" on (runs with off during normal tests)
{{if os() == "windows" { "$env:" } else { "" } }}GUEST="{{guest}}"{{if os() == "windows" { ";" } else { "" } }} {{ cargo-cmd }} test --profile={{ if target == "debug" { "dev" } else { target } }} {{ target-triple-flag }} --test integration_test execute_on_heap {{ if features =="" {" --features executable_heap"} else {"--features executable_heap," + features} }}

@# run the rest of the integration tests
@# skip interrupt_random_kill_stress_test and then run it explicitly so we can see the output more
Expand Down
69 changes: 40 additions & 29 deletions src/hyperlight_host/src/mem/shared_mem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1331,50 +1331,61 @@ mod tests {
assert_eq!(data, ret_vec);
}

/// A test to ensure that, if a `SharedMem` instance is cloned
/// and _all_ clones are dropped, the memory region will no longer
/// be valid.
///
/// This test is ignored because it is incompatible with other tests as
/// they may be allocating memory at the same time.
///
/// Marking this test as ignored means that running `cargo test` will not
/// run it. This feature will allow a developer who runs that command
/// from their workstation to be successful without needing to know about
/// test interdependencies. This test will, however, be run explicitly as a
/// part of the CI pipeline.
/// Test that verifies memory is properly unmapped when all SharedMemory
/// references are dropped.
#[test]
#[ignore]
#[cfg(target_os = "linux")]
#[cfg(all(target_os = "linux", not(miri)))]
fn test_drop() {
use proc_maps::maps_contain_addr;
use proc_maps::get_process_maps;

// Use a unique size that no other test uses to avoid false positives
// from concurrent tests allocating at the same address.
// The mprotect calls split the mapping into 3 regions (guard, usable, guard),
// so we check for the usable region which has this exact size.
//
// NOTE: If this test fails intermittently, there may be a race condition
// where another test allocates memory at the same address between our
// drop and the mapping check. Ensure UNIQUE_SIZE is not used by any
// other test in the codebase to avoid this.
const UNIQUE_SIZE: usize = PAGE_SIZE_USIZE * 17;

let pid = std::process::id();

let eshm = ExclusiveSharedMemory::new(PAGE_SIZE_USIZE).unwrap();
let eshm = ExclusiveSharedMemory::new(UNIQUE_SIZE).unwrap();
let (hshm1, gshm) = eshm.build();
let hshm2 = hshm1.clone();
let addr = hshm1.raw_ptr() as usize;

// ensure the address is in the process's virtual memory
let maps_before_drop = proc_maps::get_process_maps(pid.try_into().unwrap()).unwrap();
// Use the usable memory region (not raw), since mprotect splits the mapping
let base_ptr = hshm1.base_ptr() as usize;
let mem_size = hshm1.mem_size();

// Helper to check if exact mapping exists (matching both address and size)
let has_exact_mapping = |ptr: usize, size: usize| -> bool {
get_process_maps(pid.try_into().unwrap())
.unwrap()
.iter()
.any(|m| m.start() == ptr && m.size() == size)
};

// Verify mapping exists before drop
assert!(
maps_contain_addr(addr, &maps_before_drop),
"shared memory address {:#x} was not found in process map, but should be",
addr,
has_exact_mapping(base_ptr, mem_size),
"shared memory mapping not found at {:#x} with size {}",
base_ptr,
mem_size
);
// drop both shared memory instances, which should result
// in freeing the memory region

// Drop all references
drop(hshm1);
drop(hshm2);
drop(gshm);

let maps_after_drop = proc_maps::get_process_maps(pid.try_into().unwrap()).unwrap();
// now, ensure the address is not in the process's virtual memory
// Verify exact mapping is gone
assert!(
!maps_contain_addr(addr, &maps_after_drop),
"shared memory address {:#x} was found in the process map, but shouldn't be",
addr
!has_exact_mapping(base_ptr, mem_size),
"shared memory mapping still exists at {:#x} with size {} after drop",
base_ptr,
mem_size
);
}

Expand Down
41 changes: 19 additions & 22 deletions src/hyperlight_host/src/sandbox/initialized_multi_use.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1071,41 +1071,38 @@ mod tests {
}

#[test]
#[ignore] // this test runs by itself because it uses a lot of system resources
fn create_1000_sandboxes() {
let barrier = Arc::new(Barrier::new(21));
fn create_200_sandboxes() {
const NUM_THREADS: usize = 10;
const SANDBOXES_PER_THREAD: usize = 20;

let mut handles = vec![];
// barrier to make sure all threads start their work simultaneously
let start_barrier = Arc::new(Barrier::new(NUM_THREADS + 1));
let mut thread_handles = vec![];

for _ in 0..20 {
let c = barrier.clone();
for _ in 0..NUM_THREADS {
let barrier = start_barrier.clone();

let handle = thread::spawn(move || {
c.wait();

for _ in 0..50 {
let usbox = UninitializedSandbox::new(
GuestBinary::FilePath(
simple_guest_as_string().expect("Guest Binary Missing"),
),
None,
)
.unwrap();
barrier.wait();

let mut multi_use_sandbox: MultiUseSandbox = usbox.evolve().unwrap();
for _ in 0..SANDBOXES_PER_THREAD {
let guest_path = simple_guest_as_string().expect("Guest Binary Missing");
let uninit =
UninitializedSandbox::new(GuestBinary::FilePath(guest_path), None).unwrap();

let res: i32 = multi_use_sandbox.call("GetStatic", ()).unwrap();
let mut sandbox: MultiUseSandbox = uninit.evolve().unwrap();

assert_eq!(res, 0);
let result: i32 = sandbox.call("GetStatic", ()).unwrap();
assert_eq!(result, 0);
}
});

handles.push(handle);
thread_handles.push(handle);
}

barrier.wait();
start_barrier.wait();

for handle in handles {
for handle in thread_handles {
handle.join().unwrap();
}
}
Expand Down
165 changes: 70 additions & 95 deletions src/hyperlight_host/src/sandbox/uninitialized.rs
Original file line number Diff line number Diff line change
Expand Up @@ -758,115 +758,90 @@ mod tests {
}
}

/// Tests that tracing spans and events are properly emitted when a tracing subscriber is set.
///
/// This test verifies:
/// 1. Spans are created with correct attributes (correlation_id)
/// 2. Nested spans from UninitializedSandbox::new are properly parented
/// 3. Error events are emitted when sandbox creation fails
#[test]
// Tests that trace data are emitted when a trace subscriber is set
// this test is ignored because it is incompatible with other tests , specifically those which require a logger for tracing
// marking this test as ignored means that running `cargo test` will not run this test but will allow a developer who runs that command
// from their workstation to be successful without needed to know about test interdependencies
// this test will be run explicitly as a part of the CI pipeline
#[ignore]
#[cfg(feature = "build-metadata")]
fn test_trace_trace() {
use hyperlight_testing::logger::Logger as TestLogger;
use hyperlight_testing::tracing_subscriber::TracingSubscriber as TestSubscriber;
use serde_json::{Map, Value};
use tracing::Level as tracing_level;
use hyperlight_testing::tracing_subscriber::TracingSubscriber;
use tracing::Level;
use tracing_core::Subscriber;
use tracing_core::callsite::rebuild_interest_cache;
use uuid::Uuid;

use crate::testing::log_values::build_metadata_testing::try_to_strings;
use crate::testing::log_values::test_value_as_str;

TestLogger::initialize_log_tracer();
rebuild_interest_cache();
let subscriber = TestSubscriber::new(tracing_level::TRACE);
tracing::subscriber::with_default(subscriber.clone(), || {
let correlation_id = Uuid::new_v4().as_hyphenated().to_string();
let span = tracing::error_span!("test_trace_logs", correlation_id).entered();

// We should be in span 1

let current_span = subscriber.current_span();
assert!(current_span.is_known(), "Current span is unknown");
let current_span_metadata = current_span.into_inner().unwrap();
assert_eq!(
current_span_metadata.0.into_u64(),
1,
"Current span is not span 1"
);
assert_eq!(current_span_metadata.1.name(), "test_trace_logs");

// Get the span data and check the correlation id

let span_data = subscriber.get_span(1);
let span_attributes: &Map<String, Value> = span_data
.get("span")
.unwrap()
.get("attributes")
.unwrap()
.as_object()
.unwrap();

test_value_as_str(span_attributes, "correlation_id", correlation_id.as_str());

let mut binary_path = simple_guest_as_string().unwrap();
binary_path.push_str("does_not_exist");
/// Helper to extract a string value from nested JSON: obj["span"]["attributes"][key]
fn get_span_attr<'a>(span: &'a serde_json::Value, key: &str) -> Option<&'a str> {
span.get("span")?.get("attributes")?.get(key)?.as_str()
}

let sbox = UninitializedSandbox::new(GuestBinary::FilePath(binary_path), None);
assert!(sbox.is_err());
/// Helper to extract event field: obj["event"][field]
fn get_event_field<'a>(event: &'a serde_json::Value, field: &str) -> Option<&'a str> {
event.get("event")?.get(field)?.as_str()
}

// Now we should still be in span 1 but span 2 should be created (we created entered and exited span 2 when we called UninitializedSandbox::new)
/// Helper to extract event metadata field: obj["event"]["metadata"][field]
fn get_event_metadata<'a>(event: &'a serde_json::Value, field: &str) -> Option<&'a str> {
event.get("event")?.get("metadata")?.get(field)?.as_str()
}

let current_span = subscriber.current_span();
assert!(current_span.is_known(), "Current span is unknown");
let current_span_metadata = current_span.into_inner().unwrap();
assert_eq!(
current_span_metadata.0.into_u64(),
1,
"Current span is not span 1"
);
let subscriber = TracingSubscriber::new(Level::TRACE);

let span_metadata = subscriber.get_span_metadata(2);
assert_eq!(span_metadata.name(), "new");
tracing::subscriber::with_default(subscriber.clone(), || {
let correlation_id = Uuid::new_v4().to_string();
let _span = tracing::error_span!("test_trace_logs", %correlation_id).entered();

// Verify we're in span 1 with correct name
let (span_id, span_meta) = subscriber
.current_span()
.into_inner()
.expect("Should be inside a span");
assert_eq!(span_id.into_u64(), 1, "Should be in span 1");
assert_eq!(span_meta.name(), "test_trace_logs");

// Verify correlation_id was recorded
let span_data = subscriber.get_span(1);
let recorded_id =
get_span_attr(&span_data, "correlation_id").expect("correlation_id not found");
assert_eq!(recorded_id, correlation_id);

// Try to create a sandbox with a non-existent binary - this should fail
// and emit an error event
let bad_path = simple_guest_as_string().unwrap() + "does_not_exist";
let result = UninitializedSandbox::new(GuestBinary::FilePath(bad_path), None);
assert!(result.is_err(), "Sandbox creation should fail");

// Verify we're still in span 1 (our test span)
let (span_id, _) = subscriber
.current_span()
.into_inner()
.expect("Should still be inside a span");
assert_eq!(span_id.into_u64(), 1, "Should still be in span 1");

// Verify span 2 was created by UninitializedSandbox::new
let inner_span_meta = subscriber.get_span_metadata(2);
assert_eq!(inner_span_meta.name(), "new");

// Verify the error event was emitted
let events = subscriber.get_events();
assert_eq!(events.len(), 1, "Expected exactly one error event");

// There should be one event for the error that the binary path does not exist plus 14 info events for the logging of the crate info
let event = &events[0];
let level = get_event_metadata(event, "level").expect("event should have level");
let error = get_event_field(event, "error").expect("event should have error field");
let target = get_event_metadata(event, "target").expect("event should have target");
let module_path =
get_event_metadata(event, "module_path").expect("event should have module_path");

let events = subscriber.get_events();
assert_eq!(events.len(), 1);

let mut count_matching_events = 0;

for json_value in events {
let event_values = json_value.as_object().unwrap().get("event").unwrap();
let metadata_values_map =
event_values.get("metadata").unwrap().as_object().unwrap();
let event_values_map = event_values.as_object().unwrap();

let expected_error_start = "Error(\"GuestBinary not found:";

let err_vals_res = try_to_strings([
(metadata_values_map, "level"),
(event_values_map, "error"),
(metadata_values_map, "module_path"),
(metadata_values_map, "target"),
]);
if let Ok(err_vals) = err_vals_res
&& err_vals[0] == "ERROR"
&& err_vals[1].starts_with(expected_error_start)
&& err_vals[2] == "hyperlight_host::sandbox::uninitialized"
&& err_vals[3] == "hyperlight_host::sandbox::uninitialized"
{
count_matching_events += 1;
}
}
assert_eq!(level, "ERROR");
assert!(
count_matching_events == 1,
"Unexpected number of matching events {}",
count_matching_events
error.contains("GuestBinary not found"),
"Error should mention 'GuestBinary not found', got: {error}"
);
span.exit();
subscriber.clear();
assert_eq!(target, "hyperlight_host::sandbox::uninitialized");
assert_eq!(module_path, "hyperlight_host::sandbox::uninitialized");
});
}

Expand Down
Loading