Skip to content

Commit 4e45045

Browse files
politerealismelezar
andcommitted
test(conformance): migrate create-upload tests to testkit
Move the three `sandbox create --upload` tests from the legacy e2e crate into the testkit conformance suite. The behavior is not driver-specific, so it runs against any prepared gateway. Test names are unchanged so filters keep working: - create_with_upload_directory_preserves_source_basename - create_with_multiple_uploads - create_with_upload_single_file Changes from the originals: - Verify uploads by reading the files back with `sandbox exec` and comparing exact content, instead of matching the printed output of `sandbox create`. - Check the nested file in the directory test, and that the directory is not flattened into the destination. - Check each destination in the multiple-uploads test separately. - Use the default upload path. The originals passed `--no-git-ignore`, which is unnecessary outside a Git work tree and hid the default path. - Create the sandbox from the target's default image. - Drop the claim that files arrive before the command runs. The CLI rejects `--upload` together with a command, so each test checks the files once `create` returns and documents the limitation. Remove the now-unused `SandboxGuard::create_with_upload` and `create_with_uploads` from the legacy harness, and the old test file. Contributes to #3954 Co-authored-by: Evan Lezar <elezar@nvidia.com> Signed-off-by: politerealism <burdcat17@gmail.com>
1 parent 3572474 commit 4e45045

5 files changed

Lines changed: 132 additions & 194 deletions

File tree

‎e2e/rust/src/harness/sandbox.rs‎

Lines changed: 0 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -400,87 +400,6 @@ impl SandboxGuard {
400400
})
401401
}
402402

403-
/// Create a detached scratch sandbox with pre-loaded files, then exec a
404-
/// command in it.
405-
///
406-
/// Equivalent to:
407-
/// ```text
408-
/// openshell sandbox create --detach --upload <local>:<dest> [extra_args...]
409-
/// openshell sandbox exec <name> -- <command>
410-
/// ```
411-
///
412-
/// The `--no-git-ignore` flag is passed to avoid needing a git repository.
413-
///
414-
/// # Errors
415-
///
416-
/// Returns an error if the CLI exits with a non-zero status or the sandbox
417-
/// name cannot be parsed.
418-
pub async fn create_with_upload(
419-
upload_local: &str,
420-
upload_dest: &str,
421-
command: &[&str],
422-
) -> Result<Self, String> {
423-
Self::create_with_uploads(&[(upload_local, upload_dest)], command).await
424-
}
425-
426-
/// Create a sandbox with multiple `--upload` specs.
427-
///
428-
/// Each element of `uploads` is a `(local_path, sandbox_dest)` pair.
429-
/// `--no-git-ignore` is applied to all uploads.
430-
pub async fn create_with_uploads(
431-
uploads: &[(&str, &str)],
432-
command: &[&str],
433-
) -> Result<Self, String> {
434-
let mut cmd = openshell_cmd();
435-
cmd.arg("sandbox").arg("create").arg("--detach");
436-
add_unique_name_if_missing(&mut cmd, &[]);
437-
add_test_image_if_missing(&mut cmd, &[]);
438-
for (local, dest) in uploads {
439-
cmd.arg("--upload").arg(format!("{local}:{dest}"));
440-
}
441-
cmd.arg("--no-git-ignore");
442-
cmd.stdout(Stdio::piped()).stderr(Stdio::piped());
443-
444-
let output = timeout(SANDBOX_READY_TIMEOUT, cmd.output())
445-
.await
446-
.map_err(|_| {
447-
format!("sandbox create --upload timed out after {SANDBOX_READY_TIMEOUT:?}")
448-
})?
449-
.map_err(|e| format!("failed to spawn openshell: {e}"))?;
450-
451-
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
452-
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
453-
let combined = format!("{stdout}{stderr}");
454-
455-
if !output.status.success() {
456-
return Err(format!(
457-
"sandbox create --upload failed (exit {:?}):\n{combined}",
458-
output.status.code()
459-
));
460-
}
461-
462-
let name = extract_sandbox_name(&combined).ok_or_else(|| {
463-
format!("could not parse sandbox name from create output:\n{combined}")
464-
})?;
465-
466-
let mut guard = Self {
467-
name,
468-
create_output: combined,
469-
child: None,
470-
cleaned_up: false,
471-
};
472-
473-
match guard.exec(command).await {
474-
Ok(exec_output) => guard.create_output.push_str(&exec_output),
475-
Err(err) => {
476-
guard.cleanup().await;
477-
return Err(err);
478-
}
479-
}
480-
481-
Ok(guard)
482-
}
483-
484403
/// Upload local files to the sandbox via `openshell sandbox upload`.
485404
///
486405
/// # Arguments

‎e2e/rust/tests/upload_create.rs‎

Lines changed: 0 additions & 113 deletions
This file was deleted.

‎tests/suites/conformance/README.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ nextest run the tests as the library's unit tests. The crate also builds the
1414
| `persistent-workspace` | Files in `/sandbox` survive stop and start |
1515
| `policy-advisor` | `policy.local` accepts a proposed rule; a denied address or hostname yields one draft rule |
1616
| `file-transfer` | `sandbox upload` and `download`: round trips, `.gitignore` filtering, workspace-escape and option-looking names |
17+
| `create-upload` | `sandbox create --upload`: directory basename, multiple uploads, single file |
1718

1819
`smoke` is the smallest end-to-end check that the runner can run a real test in a
1920
tmachine environment on CI. The other suites are ports of the old
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
//! `sandbox create --upload`.
5+
//!
6+
//! Migrated from `e2e/rust/tests/upload_create.rs`. The behavior is not
7+
//! driver-specific, so it runs against any prepared gateway.
8+
//!
9+
//! `--upload` cannot be combined with a command, so these tests check the files
10+
//! once `create` returns, not that they arrived before a main process started.
11+
12+
use std::fs;
13+
use std::path::Path;
14+
15+
use testkit::{Context, Error, Sandbox};
16+
17+
/// `--upload <directory>:<destination>` keeps the directory's basename under
18+
/// the destination and preserves nested files.
19+
#[testkit::test]
20+
async fn create_with_upload_directory_preserves_source_basename(
21+
cx: &mut Context,
22+
) -> testkit::Result {
23+
let local = scratch()?;
24+
let source = local.path().join("project");
25+
write(&source.join("marker.txt"), "upload-create-marker")?;
26+
write(&source.join("src/main.py"), "print('hello')")?;
27+
28+
let sandbox = create_with_uploads(cx, "cu1", &[(&source, "/sandbox/data")]).await?;
29+
expect_file(
30+
&sandbox,
31+
"/sandbox/data/project/marker.txt",
32+
"upload-create-marker",
33+
)
34+
.await?;
35+
expect_file(
36+
&sandbox,
37+
"/sandbox/data/project/src/main.py",
38+
"print('hello')",
39+
)
40+
.await?;
41+
sandbox
42+
.sh("test ! -e /sandbox/data/marker.txt")
43+
.await?
44+
.success()?;
45+
sandbox.delete().await
46+
}
47+
48+
/// Several `--upload` flags on one `sandbox create` each place their files at
49+
/// their own destination.
50+
#[testkit::test]
51+
async fn create_with_multiple_uploads(cx: &mut Context) -> testkit::Result {
52+
let local = scratch()?;
53+
let alpha = local.path().join("alpha");
54+
let beta = local.path().join("beta");
55+
write(&alpha.join("a.txt"), "content-alpha")?;
56+
write(&beta.join("b.txt"), "content-beta")?;
57+
58+
let sandbox = create_with_uploads(
59+
cx,
60+
"cu2",
61+
&[(&alpha, "/sandbox/alpha"), (&beta, "/sandbox/beta")],
62+
)
63+
.await?;
64+
expect_file(&sandbox, "/sandbox/alpha/alpha/a.txt", "content-alpha").await?;
65+
expect_file(&sandbox, "/sandbox/beta/beta/b.txt", "content-beta").await?;
66+
sandbox.delete().await
67+
}
68+
69+
/// `--upload <file>:<destination>` places a single file at the destination.
70+
#[testkit::test]
71+
async fn create_with_upload_single_file(cx: &mut Context) -> testkit::Result {
72+
let local = scratch()?;
73+
let source = local.path().join("config.txt");
74+
write(&source, "single-file-upload-test")?;
75+
76+
let sandbox = create_with_uploads(cx, "cu3", &[(&source, "/sandbox")]).await?;
77+
expect_file(&sandbox, "/sandbox/config.txt", "single-file-upload-test").await?;
78+
sandbox.delete().await
79+
}
80+
81+
fn scratch() -> testkit::Result<tempfile::TempDir> {
82+
tempfile::tempdir()
83+
.map_err(|error| Error::msg(format!("create a local scratch directory: {error}")))
84+
}
85+
86+
fn write(path: &Path, content: &str) -> testkit::Result {
87+
if let Some(parent) = path.parent() {
88+
fs::create_dir_all(parent)
89+
.map_err(|error| Error::msg(format!("create {}: {error}", parent.display())))?;
90+
}
91+
fs::write(path, content)
92+
.map_err(|error| Error::msg(format!("write {}: {error}", path.display())))
93+
}
94+
95+
/// Create a detached sandbox with one `--upload <source>:<destination>` flag
96+
/// per entry and return a handle to it.
97+
async fn create_with_uploads(
98+
cx: &Context,
99+
suffix: &str,
100+
uploads: &[(&Path, &str)],
101+
) -> testkit::Result<Sandbox> {
102+
let name = cx.name(suffix);
103+
// Track before the CLI runs so cleanup deletes the sandbox even if the
104+
// create succeeds but a later step fails.
105+
cx.track(&name);
106+
let mut args = vec![
107+
"sandbox".to_string(),
108+
"create".to_string(),
109+
"--name".to_string(),
110+
name.clone(),
111+
"--detach".to_string(),
112+
];
113+
for (source, destination) in uploads {
114+
args.push("--upload".to_string());
115+
args.push(format!("{}:{destination}", source.display()));
116+
}
117+
cx.cli(args).await?.success()?;
118+
cx.adopt(&name).await
119+
}
120+
121+
/// Require the sandbox file at `path` to contain exactly `expected`.
122+
async fn expect_file(sandbox: &Sandbox, path: &str, expected: &str) -> testkit::Result {
123+
let output = sandbox.sh(&format!("cat '{path}'")).await?.success()?;
124+
testkit::ensure!(
125+
output.stdout == expected,
126+
"{path} contains {:?}; expected {expected:?}",
127+
output.stdout
128+
);
129+
Ok(())
130+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
//! program in `src/main.rs` runs them by linking this library.
1010
1111
mod cli;
12+
mod create_upload;
1213
mod file_transfer;
1314
mod lifecycle;
1415
mod persistent_workspace;

0 commit comments

Comments
 (0)