Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 21 additions & 21 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ targets = [
]

[workspace.package]
version = "2.5.42"
version = "2.5.43"
edition = "2021"
rust-version = "1.95.0"
license = "AGPL-3.0-only"
Expand Down
2 changes: 1 addition & 1 deletion ci/platform_boundary_research.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ crates/fbuild-core/src/platform/windows/usb_pnp.rs 40 native_path windows_sys::
crates/fbuild-core/tests/it/platformio_package_resolution.rs 5 attr_cfg #[cfg(windows)] host host_mechanic
crates/fbuild-core/tests/it/platformio_package_resolution.rs 107 attr_cfg #[cfg(windows)] host host_mechanic
crates/fbuild-daemon/Cargo.toml 74 native_dependency libc process host_mechanic
crates/fbuild-daemon/src/handlers/emulator/tests_npm_cache.rs 146 attr_cfg #[cfg(windows)] host_executable host_artifact_policy
crates/fbuild-daemon/src/handlers/emulator/tests_npm_cache.rs 224 attr_cfg #[cfg(windows)] host_executable host_artifact_policy
crates/fbuild-daemon/src/handlers/emulator/tests_process.rs 9 attr_cfg #[cfg(windows)] host_executable host_artifact_policy
crates/fbuild-daemon/src/handlers/emulator/tests_process.rs 21 attr_cfg #[cfg(not(windows))] host_executable host_artifact_policy
crates/fbuild-daemon/src/handlers/health.rs 51 attr_cfg #[cfg(target_os=)] host host_mechanic
Expand Down
30 changes: 16 additions & 14 deletions crates/fbuild-daemon/src/handlers/emulator/avr8js_npm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,33 @@ use std::path::{Path, PathBuf};

pub(crate) async fn find_node() -> fbuild_core::Result<PathBuf> {
let node = fbuild_core::platform::executable::name("node", "node.exe");
// Route through fbuild-core's `run_command` so the probe spawn is
// captured by the daemon's containment group (issue #32). The probe
// is short-lived (`node --version`) but a missing binary should
// still bubble up the same way.
//
// FastLED/fbuild#844: explicit 5 s timeout. `node --version` is a
// sub-100ms operation on every supported host — anything longer is a
// wedged process node and we want to fall through to the
// "not found" error path quickly so the user sees the install
// hint instead of a stalled emulator launch.
match fbuild_core::subprocess::run_command(
&[node, "--version"],
// nodejs-wheel exposes a Python launcher on PATH. Spawning that launcher
// for emulation means Child::kill stops Python while native Node retains
// the output pipes. Ask Node for its own executable so the runner owns
// the emulator process directly (#1694). Keep discovery bounded and
// contained through the existing subprocess boundary (#32, #844).
let executable = match fbuild_core::subprocess::run_command(
&[node, "--print", "process.execPath"],
None,
None,
Some(std::time::Duration::from_secs(5)),
)
.await
{
Ok(output) if output.success() => Ok(PathBuf::from(node)),
_ => Err(fbuild_core::FbuildError::DeployFailed(
Ok(output) if output.success() => PathBuf::from(output.stdout.trim()),
_ => return Err(fbuild_core::FbuildError::DeployFailed(
"Node.js is required for headless avr8js emulation but 'node' was not found on PATH. \
Install Node.js 18+ from https://nodejs.org/"
.to_string(),
)),
};
if !executable.is_absolute() {
return Err(fbuild_core::FbuildError::DeployFailed(format!(
"Node.js returned an invalid executable path: {}",
executable.display()
)));
}
Ok(executable)
}

/// Env-var that forces `ensure_avr8js_npm` to wipe and reinstall the cache
Expand Down
78 changes: 78 additions & 0 deletions crates/fbuild-daemon/src/handlers/emulator/tests_npm_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,84 @@ async fn env_lock() -> tokio::sync::MutexGuard<'static, ()> {
LOCK.get_or_init(|| Mutex::new(())).lock().await
}

#[tokio::test]
#[ignore = "requires Node.js on PATH; run under nodejs-wheel's uv environment for launcher regression"]
async fn node_discovery_returns_native_executable() {
let _guard = env_lock().await;
let node = super::avr8js_npm::find_node().await.unwrap();
assert!(
node.is_absolute(),
"must resolve the native executable, not a PATH launcher"
);
let output = fbuild_core::subprocess::run_command(
&[node.to_str().unwrap(), "--print", "process.execPath"],
None,
None,
Some(std::time::Duration::from_secs(5)),
)
.await
.unwrap();
assert!(output.success());
assert_eq!(node.as_path(), std::path::Path::new(output.stdout.trim()));
}

#[tokio::test]
#[ignore = "requires Node.js on PATH; exercises native process shutdown without npm or firmware"]
async fn resolved_node_headless_shutdown() {
use super::avr8js_headless::{RunAvr8jsHeadlessOptions, run_avr8js_headless};
use crate::handlers::operations::MonitorOutcome;

let _guard = env_lock().await;
let node = super::avr8js_npm::find_node().await.unwrap();
assert!(node.is_absolute());
let temp = tempfile::tempdir_in(fbuild_paths::temp_subdir("daemon-tests")).unwrap();
let cache = temp.path();
let module = cache.join("node_modules/avr8js");
std::fs::create_dir_all(&module).unwrap();
std::fs::write(module.join("package.json"), "{}").unwrap();
let script = cache.join("headless.mjs");
let hex = cache.join("unused.hex");
std::fs::write(
&script,
"console.log('READY'); setInterval(() => {}, 1000);",
)
.unwrap();
for (success, error) in [(Some("READY"), None), (None, Some("READY")), (None, None)] {
let run = run_avr8js_headless(
&node,
&script,
&hex,
16_000_000,
cache,
RunAvr8jsHeadlessOptions {
timeout_secs: Some(if success.is_some() || error.is_some() {
5.0
} else {
1.0
}),
halt_on_error: error,
halt_on_success: success,
expect: None,
show_timestamp: false,
verbose: false,
},
);
let result = tokio::time::timeout(std::time::Duration::from_secs(10), run)
.await
.expect("native Node shutdown must finish")
.unwrap();
if success.is_some() {
assert!(matches!(result.outcome, MonitorOutcome::Success(_)));
assert!(result.stdout.contains("READY"));
} else if error.is_some() {
assert!(matches!(result.outcome, MonitorOutcome::Error(_)));
assert!(result.stdout.contains("READY"));
} else {
assert!(matches!(result.outcome, MonitorOutcome::Timeout { .. }));
}
}
}

#[test]
fn avr8js_cache_is_intact_detects_missing_dir() {
let tmp = tempfile::TempDir::new().unwrap();
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "fbuild"
version = "2.5.42"
version = "2.5.43"
description = "PlatformIO-compatible embedded build tool (Rust implementation)"
readme = "README.md"
license = "AGPL-3.0-only"
Expand Down
Loading