Skip to content

Commit 45180d0

Browse files
test(testkit): add CLI sandbox, exec success, details, wait_absent and test image helpers
Add cx.cli_sandbox (with policy files), ExecOutput::success, cx.sandbox_details, cx.wait_absent and Engine::build_test_image, use them in the ported lifecycle tests, and replace the duplicate policy file, sandbox get and test image code in the host-gateway, oci-image and podman suites. Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
1 parent c2eceee commit 45180d0

22 files changed

Lines changed: 340 additions & 343 deletions

File tree

‎tests/suites/conformance/Cargo.lock‎

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎tests/suites/conformance/src/cli.rs‎

Lines changed: 9 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,9 @@ const MAX_PAGES: usize = 1000;
2323
async fn cli_create_exec_list_delete(cx: &mut Context) -> testkit::Result {
2424
cx.cli(["status"]).await?.success()?;
2525

26-
let name = cx.name("cs");
27-
cx.track(&name);
28-
cx.cli(["sandbox", "create", "--name", &name, "--detach"])
29-
.await?
30-
.success()?;
31-
32-
let sandbox = cx.adopt(&name).await?;
33-
let got: Value = cx
34-
.cli(["sandbox", "get", &name, "--output", "json"])
35-
.await?
36-
.success()?
37-
.json()?;
26+
let sandbox = cx.cli_sandbox("cs").create().await?;
27+
let name = sandbox.name().to_string();
28+
let got = cx.sandbox_details(&name).await?;
3829
testkit::ensure!(
3930
got["name"] == name.as_str(),
4031
"sandbox get returned {:?}; expected {name:?}",
@@ -72,23 +63,12 @@ async fn cli_create_exec_list_delete(cx: &mut Context) -> testkit::Result {
7263
/// stopped, starts again, and can be stopped and deleted.
7364
#[testkit::test]
7465
async fn cli_stop_start_delete(cx: &mut Context) -> testkit::Result {
75-
let name = cx.name("cl");
76-
cx.track(&name);
77-
cx.cli([
78-
"sandbox",
79-
"create",
80-
"--name",
81-
&name,
82-
"--detach",
83-
"--no-tty",
84-
"--",
85-
"sh",
86-
"-c",
87-
"exec sleep infinity",
88-
])
89-
.await?
90-
.success()?;
91-
let sandbox = cx.adopt(&name).await?;
66+
let sandbox = cx
67+
.cli_sandbox("cl")
68+
.command(["sh", "-c", "exec sleep infinity"])
69+
.create()
70+
.await?;
71+
let name = sandbox.name().to_string();
9272
sandbox.wait_ready().await?;
9373

9474
cx.cli(["sandbox", "stop", &name]).await?.success()?;

‎tests/suites/conformance/src/filesystem_policy.rs‎

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
use std::collections::HashMap;
88
use std::time::Duration;
99

10-
use testkit::Context;
1110
use testkit::sdk::raw::proto;
11+
use testkit::{Context, ResultExt as _};
1212

1313
/// A rejected launch must close promptly instead of hanging the exec.
1414
const REJECTED_LAUNCH_TIMEOUT: Duration = Duration::from_secs(20);
@@ -66,7 +66,7 @@ async fn hard_requirement_accepts_added_device_paths(cx: &mut Context) -> testki
6666
.await?;
6767
sandbox.wait_ready().await?;
6868

69-
let output = sandbox
69+
sandbox
7070
.exec([
7171
"sh",
7272
"-c",
@@ -76,13 +76,9 @@ async fn hard_requirement_accepts_added_device_paths(cx: &mut Context) -> testki
7676
printf landlock-ok > /tmp/landlock-check; \
7777
test \"$(cat /tmp/landlock-check)\" = landlock-ok",
7878
])
79-
.await?;
80-
testkit::ensure!(
81-
output.exit_code == 0,
82-
"the hard_requirement sandbox could not read /dev/urandom or write /tmp: exit {}; stderr: {}",
83-
output.exit_code,
84-
output.stderr
85-
);
79+
.await?
80+
.success()
81+
.context("the hard_requirement sandbox must read /dev/urandom and write /tmp")?;
8682
Ok(())
8783
}
8884

@@ -128,13 +124,11 @@ async fn replaced_policy_path_rejects_next_launch(cx: &mut Context) -> testkit::
128124
format!("mv {LEAF} /sandbox/original-leaf; ln -s /var/tmp {LEAF}")
129125
},
130126
] {
131-
let output = sandbox.exec(["sh", "-c", script.as_str()]).await?;
132-
testkit::ensure!(
133-
output.exit_code == 0,
134-
"({case}) `{script}` exited {}; stderr: {}",
135-
output.exit_code,
136-
output.stderr
137-
);
127+
sandbox
128+
.exec(["sh", "-c", script.as_str()])
129+
.await?
130+
.success()
131+
.context(format!("({case}) prepare the policy path"))?;
138132
}
139133

140134
let next = tokio::time::timeout(REJECTED_LAUNCH_TIMEOUT, sandbox.exec(["true"]))

‎tests/suites/conformance/src/image.rs‎

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,7 @@ async fn explicit_image_is_used(cx: &mut Context) -> testkit::Result {
2424
/// Fail unless the sandbox's `/etc/os-release` names Ubuntu and a version,
2525
/// as OpenShell's base image does.
2626
pub(crate) async fn expect_ubuntu(sandbox: &Sandbox) -> testkit::Result {
27-
let output = sandbox.exec(["cat", "/etc/os-release"]).await?;
28-
testkit::ensure!(
29-
output.exit_code == 0,
30-
"reading /etc/os-release exited {}; stderr: {}",
31-
output.exit_code,
32-
output.stderr
33-
);
27+
let output = sandbox.exec(["cat", "/etc/os-release"]).await?.success()?;
3428
let field = |key: &str| {
3529
output.stdout.lines().find_map(|line| {
3630
line.strip_prefix(key)

‎tests/suites/conformance/src/lifecycle.rs‎

Lines changed: 42 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,8 @@
99
1010
use std::time::Duration;
1111

12-
use serde_json::Value;
13-
use testkit::sdk::{SandboxPhase, SandboxRestartPolicy, SdkError};
14-
use testkit::{Context, Poll, Sandbox, poll_until};
12+
use testkit::sdk::{SandboxPhase, SandboxRestartPolicy};
13+
use testkit::{Context, Poll, ResultExt as _, Sandbox, poll_until};
1514

1615
/// A long-running sandbox becomes ready, runs a command with exec, and is gone
1716
/// from get and list after delete.
@@ -66,22 +65,24 @@ async fn exec_outlives_startup_recovery_deadline(cx: &mut Context) -> testkit::R
6665
sandbox.exec(["sh", "-c", script.as_str()]),
6766
)
6867
.await
69-
.map_err(|_| testkit::Error::msg("exec past the recovery deadline did not finish in 60s"))??;
68+
.map_err(|_| testkit::Error::msg("exec past the recovery deadline did not finish in 60s"))??
69+
.success()
70+
.context("the recovery deadline must not end an admitted exec")?;
7071
testkit::ensure!(
71-
output.exit_code == 0 && output.stdout == "past-recovery-deadline",
72-
"the recovery deadline must not end an admitted exec: exited {} with stdout {:?}; stderr: {}",
73-
output.exit_code,
74-
output.stdout,
75-
output.stderr
72+
output.stdout == "past-recovery-deadline",
73+
"the exec past the recovery deadline printed {:?}",
74+
output.stdout
7675
);
7776

78-
let next = sandbox.exec(["printf", "fresh-exec"]).await?;
77+
let next = sandbox
78+
.exec(["printf", "fresh-exec"])
79+
.await?
80+
.success()
81+
.context("exec after the recovery deadline")?;
7982
testkit::ensure!(
80-
next.exit_code == 0 && next.stdout == "fresh-exec",
81-
"exec after the recovery deadline exited {} with stdout {:?}; stderr: {}",
82-
next.exit_code,
83-
next.stdout,
84-
next.stderr
83+
next.stdout == "fresh-exec",
84+
"exec after the recovery deadline printed {:?}",
85+
next.stdout
8586
);
8687
Ok(())
8788
}
@@ -247,14 +248,7 @@ async fn main_exit_after_ready_sets_final_phase(cx: &mut Context) -> testkit::Re
247248

248249
for (sandbox, code, phase) in &sandboxes {
249250
sandbox.wait_ready().await?;
250-
let release = sandbox.exec(["touch", RELEASE_PATH]).await?;
251-
testkit::ensure!(
252-
release.exit_code == 0,
253-
"releasing the main command of {} exited {}; stderr: {}",
254-
sandbox.name(),
255-
release.exit_code,
256-
release.stderr
257-
);
251+
sandbox.exec(["touch", RELEASE_PATH]).await?.success()?;
258252
let finished = sandbox.wait_finished().await?;
259253
testkit::ensure!(
260254
finished.phase == *phase && finished.exit_code == Some(*code),
@@ -393,13 +387,12 @@ async fn canonical_main_environment(cx: &mut Context) -> testkit::Result {
393387

394388
let exec = sandbox
395389
.exec(["sh", "-c", "printf '%s' \"${REPRO_SENTINEL:-missing}\""])
396-
.await?;
390+
.await?
391+
.success()?;
397392
testkit::ensure!(
398-
exec.exit_code == 0 && exec.stdout == declared,
399-
"exec (tty {tty}) saw REPRO_SENTINEL={:?}, exit {}; expected {declared:?}; stderr: {}",
400-
exec.stdout,
401-
exec.exit_code,
402-
exec.stderr
393+
exec.stdout == declared,
394+
"exec (tty {tty}) saw REPRO_SENTINEL={:?}; expected {declared:?}",
395+
exec.stdout
403396
);
404397

405398
if *tty {
@@ -499,7 +492,7 @@ fi
499492
)
500493
.await?;
501494

502-
let initial = sandbox_details(cx, &sandbox).await?;
495+
let initial = cx.sandbox_details(sandbox.name()).await?;
503496
let initial_instance = initial["main_process_instance_id"]
504497
.as_str()
505498
.unwrap_or_default()
@@ -508,22 +501,17 @@ fi
508501
!initial_instance.is_empty(),
509502
"sandbox get reports no main process instance: {initial}"
510503
);
511-
let release = sandbox
504+
sandbox
512505
.exec(["touch", "/sandbox/.openshell-restart-release"])
513-
.await?;
514-
testkit::ensure!(
515-
release.exit_code == 0,
516-
"releasing the initial main command exited {}; stderr: {}",
517-
release.exit_code,
518-
release.stderr
519-
);
506+
.await?
507+
.success()?;
520508

521509
let restarted = poll_until(
522510
"the on-failure restart",
523511
RESTART_TIMEOUT,
524512
Duration::from_millis(250),
525513
async || {
526-
let details = sandbox_details(cx, &sandbox).await?;
514+
let details = cx.sandbox_details(sandbox.name()).await?;
527515
let instance = details["main_process_instance_id"]
528516
.as_str()
529517
.unwrap_or_default();
@@ -558,15 +546,6 @@ fi
558546
Ok(())
559547
}
560548

561-
/// `sandbox get --output json`, which reports the main process instance and
562-
/// restart policy that the SDK does not expose.
563-
async fn sandbox_details(cx: &Context, sandbox: &Sandbox) -> testkit::Result<Value> {
564-
cx.cli(["sandbox", "get", sandbox.name(), "--output", "json"])
565-
.await?
566-
.success()?
567-
.json()
568-
}
569-
570549
/// How long the gateway may take to remove an ephemeral sandbox after its
571550
/// main command exits. Both sandboxes are released before the test waits, so
572551
/// their removals overlap.
@@ -578,55 +557,29 @@ const EPHEMERAL_CLEANUP_TIMEOUT: Duration = Duration::from_secs(60);
578557
async fn detached_no_keep_sandbox_is_removed_after_main_exits(cx: &mut Context) -> testkit::Result {
579558
let mut sandboxes = Vec::new();
580559
for (suffix, code) in [("ez", 0), ("ef", 17)] {
581-
let name = cx.name(suffix);
582-
let release_path = format!("/sandbox/.ephemeral-release-{name}");
560+
let builder = cx.cli_sandbox(suffix);
561+
let release_path = format!("/sandbox/.ephemeral-release-{}", builder.name());
583562
let script =
584563
format!("while [ ! -e '{release_path}' ]; do sleep 0.1; done; sleep 1; exit {code}");
585-
cx.track(&name);
586-
cx.cli([
587-
"sandbox",
588-
"create",
589-
"--name",
590-
&name,
591-
"--no-keep",
592-
"--detach",
593-
"--",
594-
"sh",
595-
"-c",
596-
&script,
597-
])
598-
.await?
599-
.success()?;
600-
sandboxes.push((cx.adopt(&name).await?, release_path, code));
564+
let sandbox = builder
565+
.args(["--no-keep"])
566+
.command(["sh", "-c", &script])
567+
.create()
568+
.await?;
569+
sandboxes.push((sandbox, release_path, code));
601570
}
602571

603572
for (sandbox, release_path, _) in &sandboxes {
604573
sandbox.wait_ready().await?;
605-
let release = sandbox.exec(["touch", release_path.as_str()]).await?;
606-
testkit::ensure!(
607-
release.exit_code == 0,
608-
"releasing the main command of {} exited {}; stderr: {}",
609-
sandbox.name(),
610-
release.exit_code,
611-
release.stderr
612-
);
574+
sandbox
575+
.exec(["touch", release_path.as_str()])
576+
.await?
577+
.success()?;
613578
}
614579
for (sandbox, _, code) in &sandboxes {
615-
poll_until(
616-
&format!(
617-
"ephemeral sandbox {} to be removed after exiting {code}",
618-
sandbox.name()
619-
),
620-
EPHEMERAL_CLEANUP_TIMEOUT,
621-
Duration::from_millis(500),
622-
async || match cx.client().get_sandbox(sandbox.name()).await {
623-
Err(SdkError::NotFound { .. }) => Ok(Poll::Ready(())),
624-
Err(error) => Err(error.into()),
625-
Ok(found) => Ok(Poll::Pending(format!("phase {:?}", found.phase))),
626-
},
627-
)
628-
.await?;
629-
cx.expect_absent(sandbox).await?;
580+
cx.wait_absent(sandbox, EPHEMERAL_CLEANUP_TIMEOUT)
581+
.await
582+
.context(format!("ephemeral sandbox exited {code}"))?;
630583
}
631584
Ok(())
632585
}

‎tests/suites/conformance/src/process.rs‎

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,12 @@ async fn core_dumps_are_disabled(cx: &mut Context) -> testkit::Result {
2222
"-c",
2323
"printf '%s %s' \"$(ulimit -c)\" \"$(ulimit -Hc)\"",
2424
])
25-
.await?;
25+
.await?
26+
.success()?;
2627
testkit::ensure!(
27-
output.exit_code == 0 && output.stdout == "0 0",
28-
"core file size limits (soft hard) are {:?}, exit {}; expected \"0 0\"; stderr: {}",
29-
output.stdout,
30-
output.exit_code,
31-
output.stderr
28+
output.stdout == "0 0",
29+
"core file size limits (soft hard) are {:?}; expected \"0 0\"",
30+
output.stdout
3231
);
3332
Ok(())
3433
}

‎tests/suites/conformance/src/templates.rs‎

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -127,13 +127,12 @@ async fn sandbox_from_template_uses_its_workload(cx: &mut Context) -> testkit::R
127127

128128
let output = sandbox
129129
.exec(["sh", "-c", "printf '%s' \"${FEATURE_FLAG:-missing}\""])
130-
.await?;
130+
.await?
131+
.success()?;
131132
testkit::ensure!(
132-
output.exit_code == 0 && output.stdout == "on",
133-
"FEATURE_FLAG in a sandbox from the template is {:?}, exit {}; expected \"on\"; stderr: {}",
134-
output.stdout,
135-
output.exit_code,
136-
output.stderr
133+
output.stdout == "on",
134+
"FEATURE_FLAG in a sandbox from the template is {:?}; expected \"on\"",
135+
output.stdout
137136
);
138137
Ok(())
139138
}

‎tests/suites/drivers/podman/Cargo.lock‎

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎tests/suites/drivers/podman/Cargo.toml‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,5 @@ publish = false
1616
container-engine-support = { path = "../../../support/container-engine" }
1717
openshell-testkit = { path = "../../../testkit/testkit" }
1818
podman-support = { path = "../../../support/podman" }
19-
tempfile = "3"
2019

2120
[workspace]

0 commit comments

Comments
 (0)