Skip to content

Commit dc00c7d

Browse files
authored
vmm_tests: use libtest-mimic to define and run tests (#762)
Instead of using the built-in test harness, use libtest-mimic to define tests. This makes tests easier to define: * We can define tests programmatically at runtime instead of just statically at compile time; this will make it possible to reduce/eliminate the use of the domain-specific language in the `vmm_test` macro to define test constraints (supported VMMs, architectures, etc.). * We can define and query artifacts requirements separately from running the tests, making it easier to integrate with flowey.
1 parent 72d4de3 commit dc00c7d

File tree

21 files changed

+411
-220
lines changed

21 files changed

+411
-220
lines changed

Cargo.lock

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -90,23 +90,24 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
9090

9191
[[package]]
9292
name = "anstream"
93-
version = "0.6.11"
93+
version = "0.6.15"
9494
source = "registry+https://github.com/rust-lang/crates.io-index"
95-
checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5"
95+
checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526"
9696
dependencies = [
9797
"anstyle",
9898
"anstyle-parse",
9999
"anstyle-query",
100100
"anstyle-wincon",
101101
"colorchoice",
102+
"is_terminal_polyfill",
102103
"utf8parse",
103104
]
104105

105106
[[package]]
106107
name = "anstyle"
107-
version = "1.0.4"
108+
version = "1.0.10"
108109
source = "registry+https://github.com/rust-lang/crates.io-index"
109-
checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
110+
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
110111

111112
[[package]]
112113
name = "anstyle-parse"
@@ -1501,6 +1502,12 @@ version = "3.0.0"
15011502
source = "registry+https://github.com/rust-lang/crates.io-index"
15021503
checksum = "281e452d3bad4005426416cdba5ccfd4f5c1280e10099e21db27f7c1c28347fc"
15031504

1505+
[[package]]
1506+
name = "escape8259"
1507+
version = "0.5.3"
1508+
source = "registry+https://github.com/rust-lang/crates.io-index"
1509+
checksum = "5692dd7b5a1978a5aeb0ce83b7655c58ca8efdcb79d21036ea249da95afec2c6"
1510+
15041511
[[package]]
15051512
name = "event-listener"
15061513
version = "5.3.1"
@@ -3378,6 +3385,12 @@ dependencies = [
33783385
"windows-sys 0.52.0",
33793386
]
33803387

3388+
[[package]]
3389+
name = "is_terminal_polyfill"
3390+
version = "1.70.1"
3391+
source = "registry+https://github.com/rust-lang/crates.io-index"
3392+
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
3393+
33813394
[[package]]
33823395
name = "itertools"
33833396
version = "0.10.5"
@@ -3498,6 +3511,18 @@ dependencies = [
34983511
"vcpkg",
34993512
]
35003513

3514+
[[package]]
3515+
name = "libtest-mimic"
3516+
version = "0.8.1"
3517+
source = "registry+https://github.com/rust-lang/crates.io-index"
3518+
checksum = "5297962ef19edda4ce33aaa484386e0a5b3d7f2f4e037cbeee00503ef6b29d33"
3519+
dependencies = [
3520+
"anstream",
3521+
"anstyle",
3522+
"clap",
3523+
"escape8259",
3524+
]
3525+
35013526
[[package]]
35023527
name = "linkme"
35033528
version = "0.3.31"
@@ -5083,6 +5108,7 @@ dependencies = [
50835108
"anyhow",
50845109
"async-trait",
50855110
"chipset_resources",
5111+
"clap",
50865112
"diag_client",
50875113
"disk_backend_resources",
50885114
"disk_vhd1",
@@ -5103,6 +5129,8 @@ dependencies = [
51035129
"ide_resources",
51045130
"image",
51055131
"inspect",
5132+
"libtest-mimic",
5133+
"linkme",
51065134
"mbrman",
51075135
"mesh",
51085136
"mesh_process",
@@ -8423,8 +8451,6 @@ dependencies = [
84238451
name = "vmm_test_petri_support"
84248452
version = "0.0.0"
84258453
dependencies = [
8426-
"anyhow",
8427-
"parking_lot",
84288454
"petri",
84298455
"petri_artifacts_common",
84308456
"petri_artifacts_vmm_test",

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,7 @@ kvm-bindings = "0.7"
448448
landlock = "0.3.1"
449449
libc = "0.2"
450450
libfuzzer-sys = "0.4"
451+
libtest-mimic = "0.8"
451452
linkme = "0.3.9"
452453
log = "0.4"
453454
macaddr = "1.0"

petri/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,16 @@ sparse_mmap.workspace = true
5757

5858
anyhow.workspace = true
5959
async-trait.workspace = true
60+
clap.workspace = true
6061
fatfs = { workspace = true, features = ["std", "alloc"] }
6162
fs-err.workspace = true
6263
fscommon.workspace = true
6364
futures.workspace = true
6465
futures-concurrency.workspace = true
6566
gptman.workspace = true
6667
image = { workspace = true, features = ["png"] }
68+
libtest-mimic.workspace = true
69+
linkme.workspace = true
6770
mbrman.workspace = true
6871
prost.workspace = true
6972
tempfile.workspace = true

petri/petri_artifacts_core/src/lib.rs

Lines changed: 43 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use std::sync::Arc;
1616
// exported to support the `declare_artifacts!` macro
1717
#[doc(hidden)]
1818
pub use paste;
19+
use std::path::Path;
1920

2021
/// A trait that marks a type as being the type-safe ID for a petri artifact.
2122
///
@@ -141,38 +142,35 @@ macro_rules! declare_artifacts {
141142
};
142143
}
143144

144-
/// An object-safe trait used to abstract details of artifact resolution.
145+
/// A trait to resolve artifacts to paths.
145146
///
146-
/// Test authors are expected to use the [`TestArtifactResolver`] and
147-
/// [`TestArtifacts`] abstractions to interact with resolvers, and should not
147+
/// Test authors are expected to use the [`TestArtifactRequirements`] and
148+
/// [`TestArtifacts`] abstractions to interact with artifacts, and should not
148149
/// use this API directly.
149-
pub trait TestArtifactResolverBackend {
150+
pub trait ResolveTestArtifact {
150151
/// Given an artifact handle, return its corresponding PathBuf.
151152
///
152153
/// This method must use type-erased handles, as using typed artifact
153154
/// handles in this API would cause the trait to no longer be object-safe.
154155
fn resolve(&self, id: ErasedArtifactHandle) -> anyhow::Result<PathBuf>;
156+
}
155157

156-
/// Invoked once all calls to `resolve` has gone through.
157-
///
158-
/// Callers must ensure that this method is called after their final call to
159-
/// `resolve`. By doing this, it is possible to implement "dry-run"
160-
/// resolvers, which simply list a test's required dependencies, and then
161-
/// terminate the test run.
162-
fn finalize(self: Box<Self>) {}
158+
impl<T: ResolveTestArtifact + ?Sized> ResolveTestArtifact for &T {
159+
fn resolve(&self, id: ErasedArtifactHandle) -> anyhow::Result<PathBuf> {
160+
(**self).resolve(id)
161+
}
163162
}
164163

165164
/// A set of dependencies required to run a test.
166-
pub struct TestArtifactResolver {
167-
backend: Box<dyn TestArtifactResolverBackend>,
165+
#[derive(Clone)]
166+
pub struct TestArtifactRequirements {
168167
artifacts: Vec<(ErasedArtifactHandle, bool)>,
169168
}
170169

171-
impl TestArtifactResolver {
170+
impl TestArtifactRequirements {
172171
/// Create an empty set of dependencies.
173-
pub fn new(backend: Box<dyn TestArtifactResolverBackend>) -> Self {
174-
TestArtifactResolver {
175-
backend,
172+
pub fn new() -> Self {
173+
TestArtifactRequirements {
176174
artifacts: Vec::new(),
177175
}
178176
}
@@ -189,13 +187,27 @@ impl TestArtifactResolver {
189187
self
190188
}
191189

192-
/// Finalize the set of dependencies.
193-
pub fn finalize(self) -> TestArtifacts {
190+
/// Returns the current list of required depencencies.
191+
pub fn required_artifacts(&self) -> impl Iterator<Item = ErasedArtifactHandle> + '_ {
192+
self.artifacts
193+
.iter()
194+
.filter_map(|&(a, optional)| (!optional).then_some(a))
195+
}
196+
197+
/// Returns the current list of optional dependencies.
198+
pub fn optional_artifacts(&self) -> impl Iterator<Item = ErasedArtifactHandle> + '_ {
199+
self.artifacts
200+
.iter()
201+
.filter_map(|&(a, optional)| optional.then_some(a))
202+
}
203+
204+
/// Resolve the set of dependencies.
205+
pub fn resolve(&self, resolver: impl ResolveTestArtifact) -> anyhow::Result<TestArtifacts> {
194206
let mut failed = String::new();
195207
let mut resolved = HashMap::new();
196208

197-
for (a, optional) in self.artifacts {
198-
match self.backend.resolve(a) {
209+
for &(a, optional) in &self.artifacts {
210+
match resolver.resolve(a) {
199211
Ok(p) => {
200212
resolved.insert(a, p);
201213
}
@@ -204,36 +216,34 @@ impl TestArtifactResolver {
204216
}
205217
}
206218

207-
self.backend.finalize();
208-
209219
if !failed.is_empty() {
210-
panic!("Artifact resolution failed:\n{}", failed);
220+
anyhow::bail!("Artifact resolution failed:\n{}", failed);
211221
}
212222

213-
TestArtifacts {
223+
Ok(TestArtifacts {
214224
artifacts: Arc::new(resolved),
215-
}
225+
})
216226
}
217227
}
218228

219229
/// A resolved set of test artifacts, returned by
220-
/// [`TestArtifactResolver::finalize`].
230+
/// [`TestArtifactRequirements::resolve`].
221231
#[derive(Clone)]
222232
pub struct TestArtifacts {
223233
artifacts: Arc<HashMap<ErasedArtifactHandle, PathBuf>>,
224234
}
225235

226236
impl TestArtifacts {
227-
/// Try to resolve an artifact to a path.
237+
/// Try to get the resolved path of an artifact.
228238
#[track_caller]
229-
pub fn try_resolve(&self, artifact: impl AsArtifactHandle) -> Option<PathBuf> {
230-
self.artifacts.get(&artifact.erase()).cloned()
239+
pub fn try_get(&self, artifact: impl AsArtifactHandle) -> Option<&Path> {
240+
self.artifacts.get(&artifact.erase()).map(|p| p.as_ref())
231241
}
232242

233-
/// Resolve an artifact to a path.
243+
/// Get the resolved path of an artifact.
234244
#[track_caller]
235-
pub fn resolve(&self, artifact: impl AsArtifactHandle) -> PathBuf {
236-
self.try_resolve(artifact.erase())
245+
pub fn get(&self, artifact: impl AsArtifactHandle) -> &Path {
246+
self.try_get(artifact.erase())
237247
.unwrap_or_else(|| panic!("Artifact not initially required: {:?}", artifact.erase()))
238248
}
239249
}

petri/src/disk_image.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use std::path::Path;
2020
pub fn build_agent_image(
2121
arch: MachineArch,
2222
os_flavor: OsFlavor,
23-
resolver: &TestArtifacts,
23+
artifacts: &TestArtifacts,
2424
) -> anyhow::Result<tempfile::NamedTempFile> {
2525
match os_flavor {
2626
OsFlavor::Windows => {
@@ -30,7 +30,7 @@ pub fn build_agent_image(
3030
b"pipette ",
3131
&[(
3232
"pipette.exe",
33-
PathOrBinary::Path(&resolver.resolve(match arch {
33+
PathOrBinary::Path(artifacts.get(match arch {
3434
MachineArch::X86_64 => common_artifacts::PIPETTE_WINDOWS_X64.erase(),
3535
MachineArch::Aarch64 => common_artifacts::PIPETTE_WINDOWS_AARCH64.erase(),
3636
})),
@@ -45,7 +45,7 @@ pub fn build_agent_image(
4545
&[
4646
(
4747
"pipette",
48-
PathOrBinary::Path(&resolver.resolve(match arch {
48+
PathOrBinary::Path(artifacts.get(match arch {
4949
MachineArch::X86_64 => common_artifacts::PIPETTE_LINUX_X64.erase(),
5050
MachineArch::Aarch64 => common_artifacts::PIPETTE_LINUX_AARCH64.erase(),
5151
})),

petri/src/lib.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,22 @@
1212
mod disk_image;
1313
mod linux_direct_serial_agent;
1414
mod openhcl_diag;
15+
mod test;
1516
mod tracing;
1617
mod vm;
1718
mod worker;
1819

1920
pub use petri_artifacts_core::ArtifactHandle;
2021
pub use petri_artifacts_core::AsArtifactHandle;
2122
pub use petri_artifacts_core::ErasedArtifactHandle;
22-
pub use petri_artifacts_core::TestArtifactResolver;
23-
pub use petri_artifacts_core::TestArtifactResolverBackend;
23+
pub use petri_artifacts_core::ResolveTestArtifact;
24+
pub use petri_artifacts_core::TestArtifactRequirements;
2425
pub use petri_artifacts_core::TestArtifacts;
2526
pub use pipette_client as pipette;
27+
pub use test::test_macro_support;
28+
pub use test::test_main;
29+
pub use test::RunTest;
30+
pub use test::SimpleTest;
2631
pub use vm::*;
2732

2833
/// 1 kibibyte's worth of bytes.

0 commit comments

Comments
 (0)