forge
ExploreLog in

Tahinli/forge

public
0 watching
★ 0
3.3 KiB
1//! `forge runner` is the forge CI runner, the same command line as `forge-runner`. These
2//! tests check that the subcommand exists and that the sandbox helper, which the runner starts as
3//! `<this executable> __sandbox`, is dispatched by this binary.
4
5use std::fs;
6use std::path::Path;
7use std::process::Command;
8
9use tempfile::TempDir;
10
11fn forge() -> Command {
12 Command::new(env!("CARGO_BIN_EXE_forge"))
13}
14
15/// Unprivileged user namespaces, as `crates/forge-runner/tests/sandbox.rs` checks them.
16fn userns_available() -> bool {
17 let max = fs::read_to_string("/proc/sys/user/max_user_namespaces")
18 .ok()
19 .and_then(|s| s.trim().parse::<u64>().ok())
20 .unwrap_or(0);
21 if max == 0 {
22 return false;
23 }
24 Command::new("unshare")
25 .args(["--user", "--map-root-user", "true"])
26 .status()
27 .is_ok_and(|s| s.success())
28}
29
30/// Gives every directory under `dir` owner access. Overlay work dirs are left without
31/// permissions, and `TempDir` cannot remove them otherwise. Same approach as the executor.
32fn make_removable(dir: &Path) {
33 let _ = fs::set_permissions(dir, std::os::unix::fs::PermissionsExt::from_mode(0o700));
34 let Ok(entries) = fs::read_dir(dir) else {
35 return;
36 };
37 for entry in entries.flatten() {
38 if entry.file_type().is_ok_and(|t| t.is_dir()) {
39 make_removable(&entry.path());
40 }
41 }
42}
43
44/// A work dir under the cargo target dir, removed on drop even when a job left overlay dirs
45/// behind.
46struct WorkDir(TempDir);
47
48impl WorkDir {
49 fn new() -> Self {
50 let dir = tempfile::Builder::new()
51 .prefix("runner-check-")
52 .tempdir_in(env!("CARGO_TARGET_TMPDIR"))
53 .expect("create work dir under the target dir");
54 Self(dir)
55 }
56}
57
58impl Drop for WorkDir {
59 fn drop(&mut self) {
60 make_removable(self.0.path());
61 }
62}
63
64#[test]
65fn runner_help_lists_subcommands() {
66 let out = forge()
67 .args(["runner", "--help"])
68 .output()
69 .expect("run forge runner --help");
70 let stdout = String::from_utf8_lossy(&out.stdout);
71 assert!(
72 out.status.success(),
73 "stderr: {}",
74 String::from_utf8_lossy(&out.stderr)
75 );
76 assert!(
77 stdout.contains("forge runner"),
78 "help should name forge runner: {stdout}"
79 );
80 for word in ["register", "run", "check"] {
81 assert!(
82 stdout.contains(word),
83 "help should mention {word}: {stdout}"
84 );
85 }
86}
87
88#[test]
89fn sandbox_helper_runs_through_forge() {
90 if !userns_available() {
91 eprintln!("skipping: unprivileged user namespaces are not available on this host");
92 return;
93 }
94 let work = WorkDir::new();
95 // `runner check` runs one trivial job through the real sandbox. It is started as
96 // `forge __sandbox`, so a successful exit proves that dispatch works in this binary.
97 let out = forge()
98 .args(["runner", "check", "--work-dir"])
99 .arg(work.0.path())
100 .output()
101 .expect("run forge runner check");
102 let stdout = String::from_utf8_lossy(&out.stdout);
103 let stderr = String::from_utf8_lossy(&out.stderr);
104 assert!(
105 out.status.success(),
106 "runner check failed\nstdout: {stdout}\nstderr: {stderr}"
107 );
108 assert!(
109 stdout.contains("ok"),
110 "expected ok lines in the report: {stdout}"
111 );
112}