diff --git a/.github/workflows/DailyArm64.yml b/.github/workflows/DailyArm64.yml index 14e4d80f4..53f3f256f 100644 --- a/.github/workflows/DailyArm64.yml +++ b/.github/workflows/DailyArm64.yml @@ -4,8 +4,8 @@ name: Daily aarch64 # Full aarch64 coverage runs here on a schedule rather than on every PR, because # there are only limited aarch64 runners. PRs run a reduced aarch64 -# build-test (build + default Rust tests only) and skip aarch64 examples; this -# workflow restores Miri, the single-driver tests, and the examples once a day. +# build-test (build, Miri and default Rust tests) and skip aarch64 examples; +# this workflow restores the single-driver tests and the examples once a day. on: schedule: @@ -37,7 +37,7 @@ jobs: arch: arm64 config: ${{ matrix.config }} - # Full aarch64 build-and-test. full_aarch64: "true" re-enables the Miri and + # Full aarch64 build-and-test. full_aarch64: "true" re-enables the # single-driver steps that are skipped on PRs. build-test: needs: build-guests diff --git a/.github/workflows/RustNightly.yml b/.github/workflows/RustNightly.yml index a3ae9c55f..19a1933d9 100644 --- a/.github/workflows/RustNightly.yml +++ b/.github/workflows/RustNightly.yml @@ -92,6 +92,11 @@ jobs: - name: Build run: just build ${{ matrix.config }} + - name: Install cargo-nextest + uses: taiki-e/install-action@4cef1412cce204788f482e778a0b9187f9626a29 # v2.87.21 + with: + tool: cargo-nextest + - name: Run Miri tests run: env -u RUSTC_WRAPPER just miri-tests diff --git a/.github/workflows/dep_build_test.yml b/.github/workflows/dep_build_test.yml index 95d4c3928..6271e7993 100644 --- a/.github/workflows/dep_build_test.yml +++ b/.github/workflows/dep_build_test.yml @@ -28,11 +28,11 @@ on: type: string full_aarch64: description: >- - When "true", also run the Miri and single-driver tests on aarch64. - PRs leave this "false" so the aarch64 runner only builds and runs the - default Rust tests; the daily schedule (DailyArm64.yml) sets it "true". - This input only affects aarch64: it does not change X64, where those - tests already run on Linux and are skipped on Windows regardless. + When "true", also run the single-driver tests on aarch64. PRs leave + this "false" so the aarch64 runner builds and runs Miri and the default + Rust tests; the daily schedule (DailyArm64.yml) sets it "true". This + input only affects aarch64: it does not change X64, where those tests + already run on Linux and are skipped on Windows regardless. required: false type: string default: "false" @@ -113,11 +113,16 @@ jobs: - name: Build run: just build ${{ inputs.config }} + - name: Install cargo-nextest + if: runner.os == 'Linux' + uses: taiki-e/install-action@4cef1412cce204788f482e778a0b9187f9626a29 # v2.87.21 + with: + tool: cargo-nextest + - name: Run Miri tests - # Linux-only. Skipped on aarch64 PRs to conserve the limited runners; - # the daily schedule (full_aarch64 == 'true') restores it. On X64 Linux - # it always runs. - if: runner.os == 'Linux' && (inputs.arch == 'X64' || inputs.full_aarch64 == 'true') + # Linux on both architectures. arch/aarch64 carries no tests of its own + # yet, so aarch64 covers a subset until some land. + if: runner.os == 'Linux' run: env -u RUSTC_WRAPPER just miri-tests - name: Run Rust tests diff --git a/Justfile b/Justfile index 577e24b8b..55fb3a75b 100644 --- a/Justfile +++ b/Justfile @@ -313,11 +313,21 @@ test-rust-tracing target=default-target features="": test-doc target=default-target features="": {{ cargo-cmd }} test --profile={{ if target == "debug" { "dev" } else { target } }} {{ target-triple-flag }} {{ if features =="" {''} else { "--features " + features } }} --doc -miri-tests: +# Nextest runs one Miri process per job, each with its own interpreter, so +# memory grows with the job count at roughly 0.4GB per job. Lower MIRI_JOBS on +# a machine with many cores but little RAM. +miri-jobs := env('MIRI_JOBS', num_cpus()) + +ensure-cargo-nextest: + {{ if os() == "windows" { "if (-not (cargo nextest --version 2>$null)) { cargo install --locked cargo-nextest }" } else { "cargo nextest --version >/dev/null 2>&1 || cargo install --locked cargo-nextest" } }} + +miri-tests: (ensure-cargo-nextest) + @# A Miri interpreter is single-threaded, so libtest's --test-threads cannot + @# use more than one core. Nextest spawns one Miri process per test. rustup +nightly component list | grep -q "miri.*installed" || rustup component add miri --toolchain nightly # We can add more as needed - cargo +nightly miri test -p hyperlight-common -F trace_guest - cargo +nightly miri test -p hyperlight-host --lib -- mem::shared_mem::tests + cargo +nightly miri nextest run -p hyperlight-common -F trace_guest -j {{miri-jobs}} + cargo +nightly miri nextest run -p hyperlight-host --lib -E 'test(/mem::shared_mem::tests/)' -j {{miri-jobs}} ################ ### LINTING #### diff --git a/src/hyperlight_common/src/transport.rs b/src/hyperlight_common/src/transport.rs index c0a6aed29..8e8cd46f1 100644 --- a/src/hyperlight_common/src/transport.rs +++ b/src/hyperlight_common/src/transport.rs @@ -565,11 +565,16 @@ mod tests { #[test] fn segments_source_reads_fragmented_values_in_order() { + #[cfg(miri)] + let count = 64; + #[cfg(not(miri))] + let count = 1024; + let mut source = Segments::new( - (0..1024).flat_map(|_| [Bytes::from_static(b"ab"), Bytes::from_static(b"cd")]), + (0..count).flat_map(|_| [Bytes::from_static(b"ab"), Bytes::from_static(b"cd")]), ); - for _ in 0..1024 { + for _ in 0..count { assert_eq!(source.take_bytes(1).unwrap(), b"a"); assert_eq!( source.take_chunks(2).unwrap(), diff --git a/src/hyperlight_common/src/virtq/buffer.rs b/src/hyperlight_common/src/virtq/buffer.rs index 7fc82b473..c66fe80d0 100644 --- a/src/hyperlight_common/src/virtq/buffer.rs +++ b/src/hyperlight_common/src/virtq/buffer.rs @@ -459,8 +459,13 @@ mod tests { #[test] fn segments_split_to_keeps_small_suffix_inline() { - let mut segments = Segments::new((0..8192).map(|_| Bytes::from_static(b"x"))); - drop(segments.split_to(8191).unwrap()); + #[cfg(miri)] + let count = 64; + #[cfg(not(miri))] + let count = 8192; + + let mut segments = Segments::new((0..count).map(|_| Bytes::from_static(b"x"))); + drop(segments.split_to(count - 1).unwrap()); let suffix = segments.split_to(1).unwrap(); @@ -473,23 +478,34 @@ mod tests { #[test] fn segments_split_to_keeps_large_remainder_in_place() { - let mut segments = Segments::new((0..8192).map(|_| Bytes::from_static(b"x"))); + #[cfg(miri)] + let count = 64; + #[cfg(not(miri))] + let count = 8192; + let half = count / 2; + + let mut segments = Segments::new((0..count).map(|_| Bytes::from_static(b"x"))); let storage = segments.chunks.as_ptr(); - let prefix = segments.split_to(4096).unwrap(); + let prefix = segments.split_to(half).unwrap(); - assert_eq!(prefix.segment_count(), 4096); + assert_eq!(prefix.segment_count(), half); assert!(prefix.iter().all(|segment| segment.as_ref() == b"x")); - assert_eq!(segments.segment_count(), 4096); - assert_eq!(segments.as_slice().as_ptr(), storage.wrapping_add(4096)); + assert_eq!(segments.segment_count(), half); + assert_eq!(segments.as_slice().as_ptr(), storage.wrapping_add(half)); assert!(segments.chunks[..segments.head].iter().all(Bytes::is_empty)); } #[test] fn segments_split_to_repeatedly_consumes_fragments() { - let mut segments = Segments::new((0..4096).map(|_| Bytes::from_static(b"ab"))); + #[cfg(miri)] + let count = 64; + #[cfg(not(miri))] + let count = 4096; + + let mut segments = Segments::new((0..count).map(|_| Bytes::from_static(b"ab"))); - for _ in 0..4096 { + for _ in 0..count { assert_eq!(segments.split_to(1).unwrap().into_bytes().as_ref(), b"a"); assert_eq!(segments.split_to(1).unwrap().into_bytes().as_ref(), b"b"); }