diff --git a/crates/data_privacy_macros_impl/Cargo.toml b/crates/data_privacy_macros_impl/Cargo.toml index 9d6d4faad..993edfea6 100644 --- a/crates/data_privacy_macros_impl/Cargo.toml +++ b/crates/data_privacy_macros_impl/Cargo.toml @@ -17,6 +17,9 @@ homepage = { workspace = true } include = { workspace = true } repository = "https://github.com/microsoft/oxidizer/tree/main/crates/data_privacy_macros_impl" +[package.metadata.anvil.miri] +exclude = true + [package.metadata.cargo_check_external_types] allowed_external_types = ["proc_macro2::TokenStream", "syn::error::Error", "syn::error::Result"] diff --git a/crates/fundle_macros_impl/Cargo.toml b/crates/fundle_macros_impl/Cargo.toml index 0b4a07668..3ac0337ad 100644 --- a/crates/fundle_macros_impl/Cargo.toml +++ b/crates/fundle_macros_impl/Cargo.toml @@ -17,6 +17,9 @@ homepage = { workspace = true } include = { workspace = true } repository = "https://github.com/microsoft/oxidizer/tree/main/crates/fundle_macros_impl" +[package.metadata.anvil.miri] +exclude = true + [package.metadata.cargo_check_external_types] allowed_external_types = ["proc_macro2::*", "syn::error::*"] diff --git a/crates/internity/tests/basic.rs b/crates/internity/tests/basic.rs index b93971e4c..17542dfd6 100644 --- a/crates/internity/tests/basic.rs +++ b/crates/internity/tests/basic.rs @@ -590,6 +590,10 @@ fn foreign_sym_resolves_to_none_without_panicking() { /// missing — so asserting the prefix property on every mid-flight snapshot /// guards the cross-shard consistency of `build_reader`. #[cfg(not(all(miri, windows)))] +#[cfg_attr( + miri_strict_provenance, + ignore = "parking_lot_core uses integer-to-pointer casts on Unix, which strict-provenance Miri rejects" +)] #[test] fn freeze_races_writer_and_stays_prefix_consistent() { use std::collections::BTreeSet; diff --git a/crates/internity_macros_impl/Cargo.toml b/crates/internity_macros_impl/Cargo.toml index 01c4780bb..be30ec06f 100644 --- a/crates/internity_macros_impl/Cargo.toml +++ b/crates/internity_macros_impl/Cargo.toml @@ -17,6 +17,9 @@ homepage = { workspace = true } include = { workspace = true } repository = "https://github.com/microsoft/oxidizer/tree/main/crates/internity_macros_impl" +[package.metadata.anvil.miri] +exclude = true + [package.metadata.cargo_check_external_types] allowed_external_types = [ "proc_macro2::*", diff --git a/crates/multitude/tests/arena.rs b/crates/multitude/tests/arena.rs index 386c36052..3c25a9bec 100644 --- a/crates/multitude/tests/arena.rs +++ b/crates/multitude/tests/arena.rs @@ -3205,6 +3205,9 @@ mod drop_slice_over_u16_max_succeeds { const TOO_LONG: usize = (u16::MAX as usize) + 1; + // These tests verify a runtime length boundary rather than memory safety. + // Native CI retains the full boundary coverage without Miri's per-element cost. + #[cfg_attr(miri, ignore)] #[test] fn try_alloc_slice_clone_drop_over_u16_succeeds() { let a = Arena::new(); @@ -3212,6 +3215,7 @@ mod drop_slice_over_u16_max_succeeds { assert_eq!(a.try_alloc_slice_clone(&v[..]).unwrap().len(), TOO_LONG); } + #[cfg_attr(miri, ignore)] #[test] fn try_alloc_slice_fill_with_drop_over_u16_succeeds() { let a = Arena::new(); @@ -3221,6 +3225,7 @@ mod drop_slice_over_u16_max_succeeds { ); } + #[cfg_attr(miri, ignore)] #[test] fn try_alloc_slice_fill_iter_drop_over_u16_succeeds() { let a = Arena::new(); @@ -3597,6 +3602,7 @@ mod alloc_slice_overflow_paths { // type lowers to a single capacity allocation plus a bulk // initializing loop — much cheaper than `(0..N).map(...).collect()` // which runs the closure N times. + #[cfg_attr(miri, ignore)] #[test] fn alloc_slice_clone_drop_over_u16_succeeds() { let v: std::vec::Vec = std::vec![D(0); u16::MAX as usize + 1]; @@ -3605,6 +3611,7 @@ mod alloc_slice_overflow_paths { assert_eq!(s.len(), u16::MAX as usize + 1); } + #[cfg_attr(miri, ignore)] #[test] fn alloc_slice_fill_with_drop_over_u16_succeeds() { let arena = Arena::new(); @@ -3612,6 +3619,7 @@ mod alloc_slice_overflow_paths { assert_eq!(s.len(), u16::MAX as usize + 1); } + #[cfg_attr(miri, ignore)] #[test] fn alloc_slice_fill_iter_drop_over_u16_succeeds() { let arena = Arena::new(); @@ -3694,6 +3702,22 @@ mod oversized_paths_coverage { fn drop(&mut self) {} } + #[cfg(miri)] + const OVERSIZED_DROP_LEN: usize = 513; + #[cfg(not(miri))] + const OVERSIZED_DROP_LEN: usize = 3000; + + fn oversized_drop_arena() -> Arena { + #[cfg(miri)] + { + Arena::builder().max_normal_alloc(4 * 1024).build() + } + #[cfg(not(miri))] + { + Arena::new() + } + } + // 24 KiB single value with Drop ⇒ oversized-local value arm // (`alloc_value.rs` 433-436). #[derive(Clone)] @@ -3717,36 +3741,40 @@ mod oversized_paths_coverage { use core::sync::atomic::{AtomicUsize, Ordering}; let counter = AtomicUsize::new(0); { - let arena = Arena::new(); - let out = arena.alloc_slice_fill_with(3000, |_| CountedDrop(&counter)); - assert_eq!(out.len(), 3000); + let arena = oversized_drop_arena(); + let out = arena.alloc_slice_fill_with(OVERSIZED_DROP_LEN, |_| CountedDrop(&counter)); + assert_eq!(out.len(), OVERSIZED_DROP_LEN); assert_eq!(counter.load(Ordering::SeqCst), 0, "no drops before teardown"); } - assert_eq!(counter.load(Ordering::SeqCst), 3000, "every element dropped at arena teardown"); + assert_eq!( + counter.load(Ordering::SeqCst), + OVERSIZED_DROP_LEN, + "every element dropped at arena teardown" + ); } #[test] fn alloc_slice_clone_oversized_drop() { - let arena = Arena::new(); - let src: Vec = (0..3000).map(DropU64).collect(); + let arena = oversized_drop_arena(); + let src: Vec = (0..OVERSIZED_DROP_LEN).map(|i| DropU64(i as u64)).collect(); let out = arena.alloc_slice_clone(&src); - assert_eq!(out.len(), 3000); - assert_eq!(out[2999].0, 2999); + assert_eq!(out.len(), OVERSIZED_DROP_LEN); + assert_eq!(out[OVERSIZED_DROP_LEN - 1].0, (OVERSIZED_DROP_LEN - 1) as u64); } #[test] fn alloc_slice_fill_with_oversized_drop() { - let arena = Arena::new(); - let out = arena.alloc_slice_fill_with(3000, |i| DropU64(i as u64)); - assert_eq!(out.len(), 3000); - assert_eq!(out[2999].0, 2999); + let arena = oversized_drop_arena(); + let out = arena.alloc_slice_fill_with(OVERSIZED_DROP_LEN, |i| DropU64(i as u64)); + assert_eq!(out.len(), OVERSIZED_DROP_LEN); + assert_eq!(out[OVERSIZED_DROP_LEN - 1].0, (OVERSIZED_DROP_LEN - 1) as u64); } #[test] fn alloc_slice_fill_iter_oversized_drop() { - let arena = Arena::new(); - let out = arena.alloc_slice_fill_iter((0_u32..3000).map(|i| DropU64(u64::from(i)))); - assert_eq!(out.len(), 3000); + let arena = oversized_drop_arena(); + let out = arena.alloc_slice_fill_iter((0..OVERSIZED_DROP_LEN).map(|i| DropU64(i as u64))); + assert_eq!(out.len(), OVERSIZED_DROP_LEN); assert_eq!(out[0].0, 0); } @@ -3785,11 +3813,11 @@ mod oversized_paths_coverage { use core::mem::MaybeUninit; use multitude::Arc; - let arena = Arena::new(); + let arena = oversized_drop_arena(); // A Drop element type routes through `impl_alloc_uninit_slice_arc`; - // 3000 × 8 B = 24 KiB exceeds the fresh arena's current chunk, so - // the slice lands in a one-shot oversized chunk. - let len = 3000_usize; + // the configured element count exceeds the normal allocation threshold, + // so the slice lands in a one-shot oversized chunk. + let len = OVERSIZED_DROP_LEN; let s = arena.alloc_uninit_slice_arc::(len); // SAFETY: `s` is the unique handle, so we have exclusive write access. unsafe { @@ -4242,6 +4270,7 @@ mod alloc_drop_behavior_2 { assert_eq!(&*s, &[1, 2, 3, 4, 5]); } + #[cfg_attr(miri, ignore)] #[test] fn slice_shared_long_no_drop_succeeds() { let arena = multitude::Arena::new(); @@ -4540,6 +4569,7 @@ mod alloc_hot_path_behavior { } } + #[cfg_attr(miri, ignore)] #[test] fn arena_2266_slice_len_boundary() { let arena = Arena::new(); @@ -4892,6 +4922,7 @@ mod alloc_hot_path_behavior { assert_eq!(drops, 0, "empty Drop slices should not produce drops"); } + #[cfg_attr(miri, ignore)] #[test] fn arena_2266_large_nondrop_slice() { let arena = Arena::new(); @@ -4918,6 +4949,7 @@ mod alloc_hot_path_behavior { assert_eq!(drops, 0, "empty Drop arc slices should not produce drops"); } + #[cfg_attr(miri, ignore)] #[test] fn large_nondrop_shared_slice() { // A non-Copy, non-Drop wrapper exercises initialized shared slices. diff --git a/crates/multitude_macros_impl/Cargo.toml b/crates/multitude_macros_impl/Cargo.toml index b50d29861..67fbf4605 100644 --- a/crates/multitude_macros_impl/Cargo.toml +++ b/crates/multitude_macros_impl/Cargo.toml @@ -17,6 +17,9 @@ homepage = { workspace = true } include = { workspace = true } repository = "https://github.com/microsoft/oxidizer/tree/main/crates/multitude_macros_impl" +[package.metadata.anvil.miri] +exclude = true + [package.metadata.cargo_check_external_types] allowed_external_types = [ "proc_macro2::*", diff --git a/crates/observed_macros_impl/Cargo.toml b/crates/observed_macros_impl/Cargo.toml index 80c919fdd..b8ac2cd03 100644 --- a/crates/observed_macros_impl/Cargo.toml +++ b/crates/observed_macros_impl/Cargo.toml @@ -17,6 +17,9 @@ homepage = { workspace = true } include = { workspace = true } repository = "https://github.com/microsoft/oxidizer/tree/main/crates/observed_macros_impl" +[package.metadata.anvil.miri] +exclude = true + [package.metadata.cargo_check_external_types] allowed_external_types = ["proc_macro2::*", "syn::*"] diff --git a/crates/ohno_macros_impl/Cargo.toml b/crates/ohno_macros_impl/Cargo.toml index 68f5c4590..e0c5e6696 100644 --- a/crates/ohno_macros_impl/Cargo.toml +++ b/crates/ohno_macros_impl/Cargo.toml @@ -17,6 +17,9 @@ homepage = { workspace = true } include = { workspace = true } repository = "https://github.com/microsoft/oxidizer/tree/main/crates/ohno_macros_impl" +[package.metadata.anvil.miri] +exclude = true + [package.metadata.cargo_check_external_types] allowed_external_types = ["proc_macro2::*", "syn::*"] diff --git a/crates/routerama/src/dyn_builder.rs b/crates/routerama/src/dyn_builder.rs index 04f300db1..b8251f990 100644 --- a/crates/routerama/src/dyn_builder.rs +++ b/crates/routerama/src/dyn_builder.rs @@ -148,13 +148,18 @@ mod tests { use super::*; + #[cfg(miri)] + const DEEP_TRIE_SEGMENTS: usize = 512; + #[cfg(not(miri))] + const DEEP_TRIE_SEGMENTS: usize = 4_096; + #[test] fn failed_build_discards_deep_source_trie_iteratively() { std::thread::Builder::new() .stack_size(64 * 1024) .spawn(|| { let mut path = String::new(); - for index in 0..4_096 { + for index in 0..DEEP_TRIE_SEGMENTS { let _ = write!(path, "/segment{index}"); } diff --git a/crates/templated_uri_macros_impl/Cargo.toml b/crates/templated_uri_macros_impl/Cargo.toml index 18e6f021b..ea9530392 100644 --- a/crates/templated_uri_macros_impl/Cargo.toml +++ b/crates/templated_uri_macros_impl/Cargo.toml @@ -17,6 +17,9 @@ homepage = { workspace = true } include = { workspace = true } repository = "https://github.com/microsoft/oxidizer/tree/main/crates/templated_uri_macros_impl" +[package.metadata.anvil.miri] +exclude = true + [package.metadata.docs.rs] all-features = true diff --git a/crates/thread_aware_macros_impl/Cargo.toml b/crates/thread_aware_macros_impl/Cargo.toml index bb337101d..2f89c6385 100644 --- a/crates/thread_aware_macros_impl/Cargo.toml +++ b/crates/thread_aware_macros_impl/Cargo.toml @@ -17,6 +17,9 @@ homepage = { workspace = true } include = { workspace = true } repository = "https://github.com/microsoft/oxidizer/tree/main/crates/thread_aware_macros_impl" +[package.metadata.anvil.miri] +exclude = true + [package.metadata.cargo_check_external_types] allowed_external_types = [ "proc_macro2::*", diff --git a/justfiles/anvil/checks/miri-race-coverage.just b/justfiles/anvil/checks/miri-race-coverage.just index da3945bdb..504d78a98 100644 --- a/justfiles/anvil/checks/miri-race-coverage.just +++ b/justfiles/anvil/checks/miri-race-coverage.just @@ -18,14 +18,13 @@ anvil-miri-race-coverage: anvil-miri-race-coverage-validate-prereqs $ErrorActionPreference = 'Stop' if ($env:ANVIL_INCLUDE_AFFECTED -eq '--skip') { exit 0 } - $pkg = @(if ($env:ANVIL_INCLUDE_AFFECTED) { -split $env:ANVIL_INCLUDE_AFFECTED } else { '--workspace' }) $day = (Get-Date).Day $low = (2 * $day) - 1 $high = (2 * $day) + 1 $env:MIRIFLAGS = "-Zmiri-many-seeds=$low..$high $($env:MIRIFLAGS)".Trim() $env:RUSTFLAGS = "--cfg miri_race_coverage $($env:RUSTFLAGS)".Trim() Write-Host "anvil-miri-race-coverage: seed window $low..$high (day $day)" - & cargo '+{{ rust_nightly }}' miri test --all-features --tests @pkg + & "{{just_executable()}}" _anvil-miri-test if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } # Install prerequisites for the `anvil-miri-race-coverage` recipe. diff --git a/justfiles/anvil/checks/miri-strict-provenance.just b/justfiles/anvil/checks/miri-strict-provenance.just index 14069df8e..890866ba4 100644 --- a/justfiles/anvil/checks/miri-strict-provenance.just +++ b/justfiles/anvil/checks/miri-strict-provenance.just @@ -11,10 +11,9 @@ anvil-miri-strict-provenance: anvil-miri-strict-provenance-validate-prereqs $ErrorActionPreference = 'Stop' if ($env:ANVIL_INCLUDE_AFFECTED -eq '--skip') { exit 0 } - $pkg = @(if ($env:ANVIL_INCLUDE_AFFECTED) { -split $env:ANVIL_INCLUDE_AFFECTED } else { '--workspace' }) $env:MIRIFLAGS = "-Zmiri-strict-provenance $($env:MIRIFLAGS)".Trim() $env:RUSTFLAGS = "--cfg miri_strict_provenance $($env:RUSTFLAGS)".Trim() - & cargo '+{{ rust_nightly }}' miri test --all-features --tests @pkg + & "{{just_executable()}}" _anvil-miri-test if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } # Install prerequisites for the `anvil-miri-strict-provenance` recipe. diff --git a/justfiles/anvil/checks/miri-tree-borrows.just b/justfiles/anvil/checks/miri-tree-borrows.just index 86c2f5c60..97ae4e927 100644 --- a/justfiles/anvil/checks/miri-tree-borrows.just +++ b/justfiles/anvil/checks/miri-tree-borrows.just @@ -24,10 +24,9 @@ anvil-miri-tree-borrows: anvil-miri-tree-borrows-validate-prereqs $ErrorActionPreference = 'Stop' if ($env:ANVIL_INCLUDE_AFFECTED -eq '--skip') { exit 0 } - $pkg = @(if ($env:ANVIL_INCLUDE_AFFECTED) { -split $env:ANVIL_INCLUDE_AFFECTED } else { '--workspace' }) $env:MIRIFLAGS = "-Zmiri-tree-borrows $($env:MIRIFLAGS)".Trim() $env:RUSTFLAGS = "--cfg miri_tree_borrows $($env:RUSTFLAGS)".Trim() - & cargo '+{{ rust_nightly }}' miri test --all-features --tests @pkg + & "{{just_executable()}}" _anvil-miri-test if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } # The three nightly-miri profiles share the same toolchain prereqs diff --git a/justfiles/anvil/checks/miri.just b/justfiles/anvil/checks/miri.just index 883605f39..e8bce0c98 100644 --- a/justfiles/anvil/checks/miri.just +++ b/justfiles/anvil/checks/miri.just @@ -10,6 +10,208 @@ # by the scheduled workflow, so the affected-tier default (--workspace) # applies. Skip guards are still included for local diff-scoped runs. +# Compile all selected packages together, then run their Miri test artifacts in +# parallel. The single Cargo invocation preserves workspace feature unification, +# while parallel artifact execution uses the runner cores that a normal +# `cargo miri test` invocation leaves idle. +# +# ANVIL_MIRI_JOBS overrides the automatically selected process count. The +# default uses one process per logical processor. GC memory information is +# logged as telemetry but does not limit concurrency, allowing scheduled runs +# to reveal whether memory pressure makes the CPU-based setting unsuitable for +# a runner class. + +# Run selected Miri test artifacts with CPU-based parallelism. +[script("pwsh")] +_anvil-miri-test: + $ErrorActionPreference = 'Stop' + if ($env:ANVIL_INCLUDE_AFFECTED -eq '--skip') { exit 0 } + + $metadata = & cargo metadata --no-deps --format-version 1 | ConvertFrom-Json + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + $excludedPackages = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::Ordinal) + $packageDirectories = @{} + foreach ($package in $metadata.packages) { + $packageDirectories[[string]$package.id] = Split-Path -Parent ([string]$package.manifest_path) + # Opted-out packages still compile as dependencies of selected consumers; + # only their own test targets are omitted from Miri. + if ($package.metadata.anvil.miri.exclude -eq $true) { + [void]$excludedPackages.Add([string]$package.name) + } + } + + if ($env:ANVIL_INCLUDE_AFFECTED) { + $tokens = @(-split $env:ANVIL_INCLUDE_AFFECTED) + $filtered = [System.Collections.Generic.List[string]]::new() + for ($i = 0; $i -lt $tokens.Count; $i++) { + if ($tokens[$i] -eq '--package') { + if (($i + 1) -ge $tokens.Count) { + Write-Error 'anvil miri: --package is missing its package spec' + exit 1 + } + $packageSpec = $tokens[++$i] + $packageName = ($packageSpec -split '@', 2)[0] + if ($excludedPackages.Contains($packageName)) { continue } + $filtered.Add('--package') + $filtered.Add($packageSpec) + } else { + $filtered.Add($tokens[$i]) + } + } + $packageArgs = $filtered.ToArray() + if (-not ($packageArgs -contains '--package')) { + Write-Host 'anvil miri: all affected packages are excluded; nothing to test' + exit 0 + } + } else { + $packageArgs = @('--workspace') + foreach ($packageName in $excludedPackages) { + $packageArgs += @('--exclude', $packageName) + } + } + + $logRoot = Join-Path ([IO.Path]::GetTempPath()) "anvil-miri-$PID-$([guid]::NewGuid())" + New-Item -ItemType Directory -Path $logRoot | Out-Null + try { + $artifactManifest = Join-Path $logRoot 'artifacts.jsonl' + $toolchain = '+{{ rust_nightly }}' + & cargo $toolchain miri test --all-features --tests --no-run --message-format=json-render-diagnostics @packageArgs 1> $artifactManifest + $buildExitCode = $LASTEXITCODE + + $artifacts = [System.Collections.Generic.List[object]]::new() + $seenArtifacts = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::Ordinal) + foreach ($line in Get-Content -LiteralPath $artifactManifest) { + if (-not $line.TrimStart().StartsWith('{')) { + [Console]::Error.WriteLine($line) + continue + } + $message = $line | ConvertFrom-Json -ErrorAction Stop + if ($message.reason -eq 'compiler-message' -and $message.message.rendered) { + [Console]::Error.Write($message.message.rendered) + } elseif ($message.reason -eq 'compiler-artifact' -and $message.executable -and $message.profile.test -eq $true) { + if ($seenArtifacts.Add([string]$message.executable)) { + $packageId = [string]$message.package_id + if (-not $packageDirectories.ContainsKey($packageId)) { + Write-Error "anvil miri: package metadata not found for artifact package '$packageId'" + exit 1 + } + $name = Split-Path -Leaf ([string]$message.executable) + $priority = 100 + # Start the consistently longest code-generation suites first + # so they do not extend the tail after short artifacts finish. + switch -Regex ($name) { + '^routerama_build-' { $priority = 0; break } + '^rest_over_grpc-' { $priority = 2; break } + '^arena-' { $priority = 4; break } + '^enrich_err-' { $priority = 5; break } + '^routerama-' { $priority = 6; break } + '^basic-' { $priority = 7; break } + } + $artifacts.Add([pscustomobject]@{ + Path = [string]$message.executable + Name = $name + Priority = $priority + WorkingDirectory = $packageDirectories[$packageId] + }) + } + } + } + + if ($buildExitCode -ne 0) { exit $buildExitCode } + if ($artifacts.Count -eq 0) { + Write-Error 'anvil miri: Cargo produced no runnable test artifacts' + exit 1 + } + + $sysrootOutput = @(& cargo $toolchain miri setup --print-sysroot) + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + $env:MIRI_SYSROOT = $sysrootOutput[-1] + $env:MIRI_BE_RUSTC = 'host' + $runner = & rustup which --toolchain '{{ rust_nightly }}' cargo-miri + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + + $jobs = 0 + if ($env:ANVIL_MIRI_JOBS) { + if (-not [int]::TryParse($env:ANVIL_MIRI_JOBS, [ref]$jobs) -or $jobs -lt 1) { + Write-Error "ANVIL_MIRI_JOBS must be a positive integer, got '$($env:ANVIL_MIRI_JOBS)'" + exit 1 + } + } else { + $jobs = [Environment]::ProcessorCount + } + $jobs = [Math]::Min($jobs, $artifacts.Count) + [GC]::Collect() + [GC]::WaitForPendingFinalizers() + $memoryInfo = [GC]::GetGCMemoryInfo() + $gcAvailableGiB = [Math]::Round($memoryInfo.TotalAvailableMemoryBytes / 1GB, 1) + $memoryLoadGiB = [Math]::Round($memoryInfo.MemoryLoadBytes / 1GB, 1) + $highMemoryLoadGiB = [Math]::Round($memoryInfo.HighMemoryLoadThresholdBytes / 1GB, 1) + Write-Host "anvil miri: $([Environment]::ProcessorCount) logical processor(s); GC reports $gcAvailableGiB GiB available, $memoryLoadGiB GiB load, $highMemoryLoadGiB GiB high-load threshold" + Write-Host "anvil miri: running $($artifacts.Count) test artifacts with $jobs concurrent process(es)" + + $orderedArtifacts = @($artifacts | Sort-Object Priority, Name) + $work = for ($i = 0; $i -lt $orderedArtifacts.Count; $i++) { + [pscustomobject]@{ + Index = $i + Artifact = $orderedArtifacts[$i].Path + Name = $orderedArtifacts[$i].Name + WorkingDirectory = $orderedArtifacts[$i].WorkingDirectory + } + } + $results = @( + $work | ForEach-Object -Parallel { + function Get-MemoryTelemetry { + [GC]::Collect() + [GC]::WaitForPendingFinalizers() + $info = [GC]::GetGCMemoryInfo() + $capacityGiB = [Math]::Round($info.TotalAvailableMemoryBytes / 1GB, 1) + $loadGiB = [Math]::Round($info.MemoryLoadBytes / 1GB, 1) + $headroomGiB = [Math]::Round( + [Math]::Max([long]0, $info.TotalAvailableMemoryBytes - $info.MemoryLoadBytes) / 1GB, + 1 + ) + "GC load $loadGiB/$capacityGiB GiB, estimated headroom $headroomGiB GiB" + } + + $item = $_ + $log = Join-Path $using:logRoot "$($item.Index).log" + Write-Host "anvil miri: starting $($item.Name) ($(Get-MemoryTelemetry))" + Push-Location -LiteralPath $item.WorkingDirectory + try { + & $using:runner runner $item.Artifact *> $log + $exitCode = $LASTEXITCODE + } finally { + Pop-Location + } + Write-Host "anvil miri: completed $($item.Name) (exit $exitCode; $(Get-MemoryTelemetry))" + [pscustomobject]@{ + Index = $item.Index + Name = $item.Name + ExitCode = $exitCode + Log = $log + } + } -ThrottleLimit $jobs + ) + + foreach ($result in $results | Sort-Object Index) { + if ($env:GITHUB_ACTIONS -eq 'true') { + Write-Host "::group::Miri artifact $($result.Name)" + } else { + Write-Host "`n=== Miri artifact $($result.Name) ===" + } + Get-Content -LiteralPath $result.Log | ForEach-Object { Write-Host $_ } + if ($env:GITHUB_ACTIONS -eq 'true') { Write-Host '::endgroup::' } + } + + $failed = @($results | Where-Object ExitCode -ne 0) + if ($failed.Count -ne 0) { + [Console]::Error.WriteLine("anvil miri: failed artifacts: $($failed.Name -join ', ')") + exit 1 + } + } finally { + Remove-Item -LiteralPath $logRoot -Recurse -Force -ErrorAction SilentlyContinue + } + # Run Miri tests for affected workspace packages. [script("pwsh")] anvil-miri: anvil-miri-validate-prereqs @@ -40,7 +242,7 @@ anvil-miri: anvil-miri-validate-prereqs # whose tests are all `#[cfg_attr(miri, ignore)]` -- the canonical # opt-out for build-tooling / CLI crates), so the old nextest # `--no-tests=pass` workaround is no longer needed. - & cargo '+{{ rust_nightly }}' miri test --all-features --tests @(if ($env:ANVIL_INCLUDE_AFFECTED) { -split $env:ANVIL_INCLUDE_AFFECTED } else { '--workspace' }) + & "{{just_executable()}}" _anvil-miri-test if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } # --- pr-runtime-analysis members ---