Skip to content

Commit 3a46fd0

Browse files
branchseerclaude
andauthored
test(e2e): make snapshot tests self-contained (#302)
## Why Enable cross-compiling e2e snapshot tests and running them on another machine (e.g. Windows VM via cargo-xtest). The target machine has no guarantees about installed tools — no bash, no node, no system PATH. Tests must be fully self-contained, relying only on the `vt` and `vtt` binaries built by cargo. ## Summary Make e2e snapshot tests fully self-contained by removing all external dependencies (bash, node, system PATH). Tests now spawn `vt`/`vtt` directly without a shell wrapper. - Replace shell command strings with structured `argv` arrays in all fixtures - Add `comment` and `envs` fields to step config for metadata and env vars - Spawn processes directly via `CommandBuilder` instead of `sh -c` - Add vtt subcommands to replace external tools: `pipe-stdin`, `write-file`, `cp`, `mkdir`, `rm`, `exit` - Replace `node` usage in fixtures with vtt equivalents - Remove `replay-logs-chronological-order` fixture (required node) - Remove `INSTA_REQUIRE_FULL_MATCH` (incompatible with remote execution) - Document Conventional Commits format for PR titles in CLAUDE.md 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent c9da525 commit 3a46fd0

File tree

74 files changed

+1082
-773
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

74 files changed

+1082
-773
lines changed

CLAUDE.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ just doc # Documentation generation
3838

3939
If `gt` (Graphite CLI) is available in PATH, use it instead of `gh` to create pull requests.
4040

41+
PR titles must use [Conventional Commits](https://www.conventionalcommits.org) format: `type(scope): summary` (scope is optional), e.g. `feat(cache): add LRU eviction`, `fix: handle symlink loops`, `test(e2e): add ctrl-c propagation test`.
42+
4143
## Tests
4244

4345
```bash

Cargo.lock

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

crates/vite_task_bin/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,10 @@ pty_terminal = { workspace = true }
3636
pty_terminal_test = { workspace = true }
3737
regex = { workspace = true }
3838
serde = { workspace = true, features = ["derive", "rc"] }
39+
shell-escape = { workspace = true }
3940
tempfile = { workspace = true }
4041
toml = { workspace = true }
42+
vec1 = { workspace = true, features = ["serde"] }
4143
vite_path = { workspace = true, features = ["absolute-redaction"] }
4244
vite_workspace = { workspace = true }
4345

crates/vite_task_bin/src/vtt/cp.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
pub fn run(args: &[String]) -> Result<(), Box<dyn std::error::Error>> {
2+
if args.len() != 2 {
3+
return Err("Usage: vtt cp <src> <dst>".into());
4+
}
5+
std::fs::copy(&args[0], &args[1])?;
6+
Ok(())
7+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
pub fn run(args: &[String]) -> Result<(), Box<dyn std::error::Error>> {
2+
let code: i32 = args.first().map(|s| s.parse()).transpose()?.unwrap_or(0);
3+
std::process::exit(code);
4+
}

crates/vite_task_bin/src/vtt/main.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,26 @@
88

99
mod barrier;
1010
mod check_tty;
11+
mod cp;
12+
mod exit;
13+
mod mkdir;
14+
mod pipe_stdin;
1115
mod print;
1216
mod print_cwd;
1317
mod print_env;
1418
mod print_file;
1519
mod read_stdin;
1620
mod replace_file_content;
21+
mod rm;
1722
mod touch_file;
23+
mod write_file;
1824

1925
fn main() {
2026
let args: Vec<String> = std::env::args().collect();
2127
if args.len() < 2 {
2228
eprintln!("Usage: vtt <subcommand> [args...]");
2329
eprintln!(
24-
"Subcommands: barrier, check-tty, print, print-cwd, print-env, print-file, read-stdin, replace-file-content, touch-file"
30+
"Subcommands: barrier, check-tty, cp, exit, mkdir, pipe-stdin, print, print-cwd, print-env, print-file, read-stdin, replace-file-content, rm, touch-file, write-file"
2531
);
2632
std::process::exit(1);
2733
}
@@ -32,6 +38,9 @@ fn main() {
3238
check_tty::run();
3339
Ok(())
3440
}
41+
"cp" => cp::run(&args[2..]),
42+
"exit" => exit::run(&args[2..]),
43+
"mkdir" => mkdir::run(&args[2..]),
3544
"print" => {
3645
print::run(&args[2..]);
3746
Ok(())
@@ -41,7 +50,10 @@ fn main() {
4150
"print-file" => print_file::run(&args[2..]),
4251
"read-stdin" => read_stdin::run(),
4352
"replace-file-content" => replace_file_content::run(&args[2..]),
53+
"pipe-stdin" => pipe_stdin::run(&args[2..]),
54+
"rm" => rm::run(&args[2..]),
4455
"touch-file" => touch_file::run(&args[2..]),
56+
"write-file" => write_file::run(&args[2..]),
4557
other => {
4658
eprintln!("Unknown subcommand: {other}");
4759
std::process::exit(1);
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
pub fn run(args: &[String]) -> Result<(), Box<dyn std::error::Error>> {
2+
let mut parents = false;
3+
let mut paths = Vec::new();
4+
for arg in args {
5+
match arg.as_str() {
6+
"-p" => parents = true,
7+
_ => paths.push(arg.as_str()),
8+
}
9+
}
10+
if paths.is_empty() {
11+
return Err("Usage: vtt mkdir [-p] <path>...".into());
12+
}
13+
for path in paths {
14+
if parents {
15+
std::fs::create_dir_all(path)?;
16+
} else {
17+
std::fs::create_dir(path)?;
18+
}
19+
}
20+
Ok(())
21+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/// pipe-stdin `<data>` -- `<command>` \[`<args>`...\]
2+
///
3+
/// Spawns `<command>` with `<data>` piped to its stdin, then exits with
4+
/// the child's exit code. If `<data>` is empty, an empty stdin is provided.
5+
pub fn run(args: &[String]) -> Result<(), Box<dyn std::error::Error>> {
6+
let sep = args
7+
.iter()
8+
.position(|a| a == "--")
9+
.ok_or("Usage: vtt pipe-stdin <data> -- <command> [args...]")?;
10+
let data = &args[..sep].join(" ");
11+
let cmd_args = &args[sep + 1..];
12+
if cmd_args.is_empty() {
13+
return Err("Usage: vtt pipe-stdin <data> -- <command> [args...]".into());
14+
}
15+
16+
let mut child = std::process::Command::new(&cmd_args[0])
17+
.args(&cmd_args[1..])
18+
.stdin(std::process::Stdio::piped())
19+
.spawn()?;
20+
21+
{
22+
use std::io::Write;
23+
let mut stdin = child.stdin.take().unwrap();
24+
if !data.is_empty() {
25+
stdin.write_all(data.as_bytes())?;
26+
}
27+
stdin.write_all(b"\n")?;
28+
// stdin is closed when dropped, signaling EOF
29+
}
30+
31+
let status = child.wait()?;
32+
std::process::exit(status.code().unwrap_or(1));
33+
}

crates/vite_task_bin/src/vtt/rm.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
pub fn run(args: &[String]) -> Result<(), Box<dyn std::error::Error>> {
2+
let mut recursive = false;
3+
let mut paths = Vec::new();
4+
for arg in args {
5+
match arg.as_str() {
6+
"-r" | "-rf" | "-f" => recursive = true,
7+
_ => paths.push(arg.as_str()),
8+
}
9+
}
10+
if paths.is_empty() {
11+
return Err("Usage: vtt rm [-rf] <path>...".into());
12+
}
13+
for path in paths {
14+
let p = std::path::Path::new(path);
15+
if p.is_dir() && recursive {
16+
std::fs::remove_dir_all(p)?;
17+
} else {
18+
std::fs::remove_file(p)?;
19+
}
20+
}
21+
Ok(())
22+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
pub fn run(args: &[String]) -> Result<(), Box<dyn std::error::Error>> {
2+
if args.len() < 2 {
3+
return Err("Usage: vtt write-file <filename> <content>".into());
4+
}
5+
std::fs::write(&args[0], &args[1])?;
6+
Ok(())
7+
}

0 commit comments

Comments
 (0)