|
1 | 1 | mod test_utils; |
2 | 2 |
|
3 | 3 | use std::{ |
4 | | - env::{current_dir, vars_os}, |
5 | | - ffi::OsStr, |
| 4 | + env::{current_dir, join_paths, split_paths, var_os, vars_os}, |
| 5 | + ffi::{OsStr, OsString}, |
| 6 | + iter, |
| 7 | + path::PathBuf, |
6 | 8 | }; |
7 | 9 |
|
8 | 10 | use fspy::{AccessMode, PathAccessIterable}; |
9 | | -use test_log::test; |
| 11 | +use ntest::test_case; |
10 | 12 | use test_utils::assert_contains; |
11 | 13 |
|
12 | | -async fn track_node_script(script: &str, args: &[&OsStr]) -> anyhow::Result<PathAccessIterable> { |
13 | | - let mut command = fspy::Command::new("node"); |
| 14 | +fn resolve_runtime(runtime: &str) -> anyhow::Result<(PathBuf, OsString)> { |
| 15 | + let manifest_dir = PathBuf::from(var_os("CARGO_MANIFEST_DIR").unwrap()); |
| 16 | + let tools_bin = |
| 17 | + manifest_dir.parent().unwrap().parent().unwrap().join("packages/tools/node_modules/.bin"); |
| 18 | + let path = |
| 19 | + join_paths(iter::once(tools_bin).chain(var_os("PATH").iter().flat_map(split_paths)))?; |
| 20 | + let program = which::which_in(runtime, Some(&path), current_dir()?)?; |
| 21 | + Ok((program, path)) |
| 22 | +} |
| 23 | + |
| 24 | +fn track_script( |
| 25 | + runtime: &str, |
| 26 | + script: &str, |
| 27 | + args: &[&OsStr], |
| 28 | +) -> anyhow::Result<PathAccessIterable> { |
| 29 | + let (program, path) = resolve_runtime(runtime)?; |
| 30 | + |
| 31 | + let mut command = fspy::Command::new(program); |
14 | 32 | command |
15 | | - .arg("-e") |
16 | | - .envs(vars_os()) // https://github.com/jdx/mise/discussions/5968 |
17 | | - .arg(script) |
18 | | - .args(args); |
19 | | - let child = command.spawn(tokio_util::sync::CancellationToken::new()).await?; |
20 | | - let termination = child.wait_handle.await?; |
21 | | - assert!(termination.status.success()); |
22 | | - Ok(termination.path_accesses) |
| 33 | + .envs(vars_os().filter(|(name, _)| !name.eq_ignore_ascii_case("PATH"))) |
| 34 | + .env("PATH", path); // https://github.com/jdx/mise/discussions/5968 |
| 35 | + let script = format!( |
| 36 | + "const fs = require('node:fs'); \ |
| 37 | + const child_process = require('node:child_process'); \ |
| 38 | + {script}" |
| 39 | + ); |
| 40 | + if runtime == "deno" { |
| 41 | + command.args(["eval", "--ext=cjs"]).arg(script).args(args); |
| 42 | + } else { |
| 43 | + command.arg("-e").arg(script).args(args); |
| 44 | + } |
| 45 | + |
| 46 | + // `ntest::test_case` generates synchronous `#[test]` functions, so drive |
| 47 | + // fspy's asynchronous process tracking from this shared helper. |
| 48 | + tokio::runtime::Runtime::new()?.block_on(async { |
| 49 | + let child = command.spawn(tokio_util::sync::CancellationToken::new()).await?; |
| 50 | + let termination = child.wait_handle.await?; |
| 51 | + assert!(termination.status.success()); |
| 52 | + Ok(termination.path_accesses) |
| 53 | + }) |
23 | 54 | } |
24 | 55 |
|
25 | | -#[test(tokio::test)] |
| 56 | +// Deno does not distribute a musl runtime, so only its generated cases are |
| 57 | +// excluded there. Node remains covered by the same tests on musl. |
| 58 | +#[test_case("node")] |
26 | 59 | #[ignore = "requires node"] |
27 | | -async fn read_sync() -> anyhow::Result<()> { |
28 | | - let accesses = track_node_script("try { fs.readFileSync('hello') } catch {}", &[]).await?; |
| 60 | +#[cfg_attr(not(target_env = "musl"), test_case("deno"))] |
| 61 | +#[cfg_attr(not(target_env = "musl"), ignore = "requires node")] |
| 62 | +fn read_sync(runtime: &str) -> anyhow::Result<()> { |
| 63 | + let accesses = track_script(runtime, "try { fs.readFileSync('hello') } catch {}", &[])?; |
29 | 64 | assert_contains(&accesses, current_dir().unwrap().join("hello").as_path(), AccessMode::READ); |
30 | 65 | Ok(()) |
31 | 66 | } |
32 | 67 |
|
33 | | -#[test(tokio::test)] |
| 68 | +#[test_case("node")] |
34 | 69 | #[ignore = "requires node"] |
35 | | -async fn exist_sync() -> anyhow::Result<()> { |
36 | | - let accesses = track_node_script("try { fs.existsSync('hello') } catch {}", &[]).await?; |
| 70 | +#[cfg_attr(not(target_env = "musl"), test_case("deno"))] |
| 71 | +#[cfg_attr(not(target_env = "musl"), ignore = "requires node")] |
| 72 | +fn exist_sync(runtime: &str) -> anyhow::Result<()> { |
| 73 | + let accesses = track_script(runtime, "try { fs.existsSync('hello') } catch {}", &[])?; |
37 | 74 | assert_contains(&accesses, current_dir().unwrap().join("hello").as_path(), AccessMode::READ); |
38 | 75 | Ok(()) |
39 | 76 | } |
40 | 77 |
|
41 | | -#[test(tokio::test)] |
| 78 | +#[test_case("node")] |
42 | 79 | #[ignore = "requires node"] |
43 | | -async fn stat_sync() -> anyhow::Result<()> { |
44 | | - let accesses = track_node_script("try { fs.statSync('hello') } catch {}", &[]).await?; |
| 80 | +#[cfg_attr(not(target_env = "musl"), test_case("deno"))] |
| 81 | +#[cfg_attr(not(target_env = "musl"), ignore = "requires node")] |
| 82 | +fn stat_sync(runtime: &str) -> anyhow::Result<()> { |
| 83 | + let accesses = track_script(runtime, "try { fs.statSync('hello') } catch {}", &[])?; |
45 | 84 | assert_contains(&accesses, current_dir().unwrap().join("hello").as_path(), AccessMode::READ); |
46 | 85 | Ok(()) |
47 | 86 | } |
48 | 87 |
|
49 | | -#[test(tokio::test)] |
| 88 | +#[test_case("node")] |
50 | 89 | #[ignore = "requires node"] |
51 | | -async fn create_read_stream() -> anyhow::Result<()> { |
52 | | - let accesses = track_node_script( |
| 90 | +#[cfg_attr(not(target_env = "musl"), test_case("deno"))] |
| 91 | +#[cfg_attr(not(target_env = "musl"), ignore = "requires node")] |
| 92 | +fn create_read_stream(runtime: &str) -> anyhow::Result<()> { |
| 93 | + let accesses = track_script( |
| 94 | + runtime, |
53 | 95 | "try { fs.createReadStream('hello').on('error', () => {}) } catch {}", |
54 | 96 | &[], |
55 | | - ) |
56 | | - .await?; |
| 97 | + )?; |
57 | 98 | assert_contains(&accesses, current_dir().unwrap().join("hello").as_path(), AccessMode::READ); |
58 | 99 | Ok(()) |
59 | 100 | } |
60 | 101 |
|
61 | | -#[test(tokio::test)] |
| 102 | +#[test_case("node")] |
62 | 103 | #[ignore = "requires node"] |
63 | | -async fn create_write_stream() -> anyhow::Result<()> { |
| 104 | +#[cfg_attr(not(target_env = "musl"), test_case("deno"))] |
| 105 | +#[cfg_attr(not(target_env = "musl"), ignore = "requires node")] |
| 106 | +fn create_write_stream(runtime: &str) -> anyhow::Result<()> { |
64 | 107 | let tmpdir = tempfile::tempdir()?; |
65 | 108 | let file_path = tmpdir.path().join("hello"); |
66 | | - let accesses = track_node_script( |
67 | | - "try { fs.createWriteStream(process.argv[1]).on('error', () => {}) } catch {}", |
| 109 | + let accesses = track_script( |
| 110 | + runtime, |
| 111 | + "try { fs.createWriteStream(process.argv.at(-1)).on('error', () => {}) } catch {}", |
68 | 112 | &[file_path.as_os_str()], |
69 | | - ) |
70 | | - .await?; |
| 113 | + )?; |
71 | 114 | assert_contains(&accesses, file_path.as_path(), AccessMode::WRITE); |
72 | 115 | Ok(()) |
73 | 116 | } |
74 | 117 |
|
75 | | -#[test(tokio::test)] |
| 118 | +#[test_case("node")] |
76 | 119 | #[ignore = "requires node"] |
77 | | -async fn write_sync() -> anyhow::Result<()> { |
| 120 | +#[cfg_attr(not(target_env = "musl"), test_case("deno"))] |
| 121 | +#[cfg_attr(not(target_env = "musl"), ignore = "requires node")] |
| 122 | +fn write_sync(runtime: &str) -> anyhow::Result<()> { |
78 | 123 | let tmpdir = tempfile::tempdir()?; |
79 | 124 | let file_path = tmpdir.path().join("hello"); |
80 | | - let accesses = track_node_script( |
81 | | - "try { fs.writeFileSync(process.argv[1], '') } catch {}", |
| 125 | + let accesses = track_script( |
| 126 | + runtime, |
| 127 | + "try { fs.writeFileSync(process.argv.at(-1), '') } catch {}", |
82 | 128 | &[file_path.as_os_str()], |
83 | | - ) |
84 | | - .await?; |
| 129 | + )?; |
85 | 130 | assert_contains(&accesses, &file_path, AccessMode::WRITE); |
86 | 131 | Ok(()) |
87 | 132 | } |
88 | 133 |
|
89 | | -#[test(tokio::test)] |
| 134 | +#[test_case("node")] |
90 | 135 | #[ignore = "requires node"] |
91 | | -async fn read_dir_sync() -> anyhow::Result<()> { |
92 | | - let accesses = track_node_script("try { fs.readdirSync('.') } catch {}", &[]).await?; |
| 136 | +#[cfg_attr(not(target_env = "musl"), test_case("deno"))] |
| 137 | +#[cfg_attr(not(target_env = "musl"), ignore = "requires node")] |
| 138 | +fn read_dir_sync(runtime: &str) -> anyhow::Result<()> { |
| 139 | + let accesses = track_script(runtime, "try { fs.readdirSync('.') } catch {}", &[])?; |
93 | 140 | assert_contains(&accesses, ¤t_dir().unwrap(), AccessMode::READ_DIR); |
94 | 141 | Ok(()) |
95 | 142 | } |
96 | 143 |
|
97 | | -#[test(tokio::test)] |
| 144 | +#[test_case("node")] |
98 | 145 | #[ignore = "requires node"] |
99 | | -async fn subprocess() -> anyhow::Result<()> { |
| 146 | +#[cfg_attr(not(target_env = "musl"), test_case("deno"))] |
| 147 | +#[cfg_attr(not(target_env = "musl"), ignore = "requires node")] |
| 148 | +fn subprocess(runtime: &str) -> anyhow::Result<()> { |
100 | 149 | let cmd = if cfg!(windows) { |
101 | 150 | r"'cmd', ['/c', 'type hello']" |
102 | 151 | } else { |
103 | 152 | r"'/bin/sh', ['-c', 'cat hello']" |
104 | 153 | }; |
105 | | - let accesses = track_node_script( |
| 154 | + let accesses = track_script( |
| 155 | + runtime, |
106 | 156 | &format!("try {{ child_process.spawnSync({cmd}, {{ stdio: 'ignore' }}) }} catch {{}}"), |
107 | 157 | &[], |
108 | | - ) |
109 | | - .await?; |
| 158 | + )?; |
110 | 159 | assert_contains(&accesses, current_dir().unwrap().join("hello").as_path(), AccessMode::READ); |
111 | 160 | Ok(()) |
112 | 161 | } |
0 commit comments