#![allow(clippy::expect_used, clippy::unwrap_used)]
use ferrijs::{Permissions, RunOptions, Runtime};
async fn runtime() -> Runtime {
Runtime::builder()
.permissions(Permissions::none().allow_all_sys())
.build()
.await
.expect("runtime")
}
async fn run(source: &str, rt: &Runtime) -> serde_json::Value {
let out = rt
.eval_module_source("entry.mjs", source, &[], RunOptions::default())
.await;
match out.result {
Ok(value) => value,
Err(error) => panic!("expected ok, got error: {error:?}"),
}
}
#[tokio::test]
async fn os_reports_the_host_it_runs_on() {
let rt = runtime().await;
let value = run(
r"
import os from 'node:os';
export default {
platform: os.platform(),
arch: os.arch(),
type: os.type(),
release: os.release(),
version: os.version(),
machine: os.machine(),
endianness: os.endianness(),
eol: os.EOL,
devNull: os.devNull,
homedir: os.homedir(),
tmpdir: os.tmpdir(),
hostname: os.hostname(),
parallelism: os.availableParallelism(),
cpuCount: os.cpus().length,
firstCpu: os.cpus()[0],
totalmem: os.totalmem(),
freemem: os.freemem(),
uptime: os.uptime(),
loadavg: os.loadavg(),
user: os.userInfo(),
priority: os.getPriority(),
interfaces: os.networkInterfaces(),
};
",
&rt,
)
.await;
assert_identity(&value);
assert_resources(&value);
assert_user_and_network(&value);
}
fn assert_identity(value: &serde_json::Value) {
let expected_platform = if cfg!(target_os = "macos") { "darwin" } else { "linux" };
let expected_type = if cfg!(target_os = "macos") { "Darwin" } else { "Linux" };
assert_eq!(value["platform"], expected_platform);
assert_eq!(value["type"], expected_type);
assert_eq!(
value["endianness"],
if cfg!(target_endian = "little") { "LE" } else { "BE" }
);
assert_eq!(value["eol"], "\n");
assert_eq!(value["devNull"], "/dev/null");
assert_eq!(
value["arch"],
match std::env::consts::ARCH {
"aarch64" => "arm64",
"x86_64" => "x64",
other => other,
}
);
assert!(
value["release"].as_str().is_some_and(|r| !r.is_empty()),
"release comes from uname: {value:?}"
);
assert!(
value["version"].as_str().is_some_and(|v| !v.is_empty()),
"version: {value:?}"
);
assert!(
value["machine"].as_str().is_some_and(|m| !m.is_empty()),
"machine: {value:?}"
);
assert!(
value["hostname"].as_str().is_some_and(|h| !h.is_empty()),
"hostname: {value:?}"
);
assert_eq!(
value["homedir"].as_str(),
std::env::var("HOME").ok().as_deref(),
"homedir matches the environment"
);
assert_eq!(
value["tmpdir"].as_str(),
std::env::temp_dir().to_str(),
"tmpdir matches the environment"
);
}
fn assert_resources(value: &serde_json::Value) {
assert!(value["parallelism"].as_u64().is_some_and(|n| n > 0));
assert!(value["cpuCount"].as_u64().is_some_and(|n| n > 0));
assert!(value["totalmem"].as_u64().is_some_and(|n| n > 0), "totalmem: {value:?}");
let total = value["totalmem"].as_u64().unwrap_or(0);
assert!(
value["freemem"].as_u64().is_some_and(|n| n > 0 && n <= total),
"freemem is within totalmem: {value:?}"
);
assert!(value["uptime"].as_u64().is_some_and(|n| n > 0), "uptime: {value:?}");
assert_eq!(value["loadavg"].as_array().map(Vec::len), Some(3));
let times = &value["firstCpu"]["times"];
let busy: u64 = ["user", "nice", "sys", "idle", "irq"]
.iter()
.filter_map(|k| times[*k].as_u64())
.sum();
assert!(busy > 0, "cpu times are read from the kernel, not zeroed: {times:?}");
assert!(
value["firstCpu"]["model"].as_str().is_some_and(|m| !m.is_empty()),
"cpu model: {value:?}"
);
}
fn assert_user_and_network(value: &serde_json::Value) {
let user = &value["user"];
assert!(user["uid"].as_u64().is_some(), "userInfo uid: {user:?}");
assert!(user["gid"].as_u64().is_some(), "userInfo gid: {user:?}");
assert!(
user["username"].as_str().is_some_and(|u| !u.is_empty()),
"userInfo username comes from the password database: {user:?}"
);
assert!(
user["shell"].as_str().is_some_and(|s| s.starts_with('/')),
"userInfo shell: {user:?}"
);
assert!(value["priority"].as_i64().is_some(), "getPriority: {value:?}");
let interfaces = value["interfaces"].as_object().expect("networkInterfaces object");
let loopback = interfaces
.values()
.flat_map(|entries| entries.as_array().cloned().unwrap_or_default())
.find(|entry| entry["address"] == "127.0.0.1");
let loopback = loopback.unwrap_or_else(|| panic!("no loopback interface in {interfaces:?}"));
assert_eq!(loopback["family"], "IPv4");
assert_eq!(loopback["internal"], serde_json::Value::Bool(true));
assert_eq!(loopback["netmask"], "255.0.0.0");
assert!(
loopback["cidr"].as_str().is_some_and(|c| c.starts_with("127.0.0.1/")),
"loopback cidr: {loopback:?}"
);
}
#[tokio::test]
async fn os_serves_the_same_surface_through_require() {
let rt = runtime().await;
let value = run(
r"
const os = require('os');
const named = require('node:os');
export default {
platform: os.platform(),
sameThroughPrefixedSpecifier: named.platform() === os.platform(),
keys: Object.keys(os).sort(),
};
",
&rt,
)
.await;
assert_eq!(
value["platform"],
if cfg!(target_os = "macos") { "darwin" } else { "linux" }
);
assert_eq!(value["sameThroughPrefixedSpecifier"], serde_json::Value::Bool(true));
let keys: Vec<&str> = value["keys"]
.as_array()
.expect("keys")
.iter()
.filter_map(serde_json::Value::as_str)
.collect();
for expected in [
"EOL",
"arch",
"availableParallelism",
"cpus",
"devNull",
"endianness",
"freemem",
"getPriority",
"homedir",
"hostname",
"loadavg",
"machine",
"networkInterfaces",
"platform",
"release",
"setPriority",
"tmpdir",
"totalmem",
"type",
"uptime",
"userInfo",
"version",
] {
assert!(
keys.contains(&expected),
"require('os') is missing {expected}: {keys:?}"
);
}
}