use std::io::{BufRead, BufReader};
use std::path::PathBuf;
use std::process::{Command, Stdio};
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
fn gtdx_bin() -> PathBuf {
let mut p = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
p.pop();
p.pop();
p.push("target/debug/gtdx");
p
}
fn gate() -> bool {
std::env::var("GTDX_RUN_SMOKE").ok().as_deref() == Some("1")
}
#[test]
fn dev_watch_rebuilds_and_reinstalls_on_source_edit() {
if !gate() {
eprintln!("skipped: set GTDX_RUN_SMOKE=1 to enable");
return;
}
let tmp = tempfile::tempdir().unwrap();
let proj = tmp.path().join("demo");
let home = tmp.path().join("home");
let status = Command::new(gtdx_bin())
.arg("new")
.arg("demo")
.arg("--dir")
.arg(&proj)
.arg("--author")
.arg("tester")
.arg("-y")
.arg("--no-git")
.status()
.unwrap();
assert!(status.success());
let mut child = Command::new(gtdx_bin())
.env("GREENTIC_HOME", &home)
.arg("dev")
.arg("--watch")
.arg("--manifest")
.arg(proj.join("Cargo.toml"))
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap();
let stdout = child.stdout.take().unwrap();
let seen = Arc::new(Mutex::new(String::new()));
let sink = Arc::clone(&seen);
std::thread::spawn(move || {
for line in BufReader::new(stdout).lines().map_while(Result::ok) {
eprintln!("child: {line}");
if let Ok(mut buf) = sink.lock() {
buf.push_str(&line);
buf.push('\n');
}
}
});
let deadline = Instant::now() + Duration::from_secs(30);
let mut saw_ready = false;
while Instant::now() < deadline {
if seen.lock().is_ok_and(|b| b.contains("ready.")) {
saw_ready = true;
break;
}
std::thread::sleep(Duration::from_millis(100));
}
assert!(saw_ready, "gtdx dev never emitted ready within 30s");
let src = proj.join("src/lib.rs");
let orig = std::fs::read_to_string(&src).unwrap();
std::fs::write(&src, format!("{orig}\n// bump")).unwrap();
std::thread::sleep(Duration::from_secs(15));
let installed = home.join("extensions/design/greentic.demo-0.1.0");
let _ = child.kill();
let _ = child.wait();
assert!(
installed.exists(),
"expected reinstall at {}",
installed.display()
);
}
#[test]
fn dev_watch_survives_build_failure() {
if !gate() {
eprintln!("skipped: set GTDX_RUN_SMOKE=1 to enable");
return;
}
let tmp = tempfile::tempdir().unwrap();
let proj = tmp.path().join("demo");
let home = tmp.path().join("home");
Command::new(gtdx_bin())
.arg("new")
.arg("demo")
.arg("--dir")
.arg(&proj)
.arg("--author")
.arg("tester")
.arg("-y")
.arg("--no-git")
.status()
.unwrap();
std::fs::write(proj.join("src/lib.rs"), "not rust").unwrap();
let mut child = Command::new(gtdx_bin())
.env("GREENTIC_HOME", &home)
.arg("dev")
.arg("--watch")
.arg("--manifest")
.arg(proj.join("Cargo.toml"))
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap();
std::thread::sleep(Duration::from_secs(5));
let status = child.try_wait().expect("try_wait");
assert!(status.is_none(), "dev exited prematurely on build failure");
let _ = child.kill();
let _ = child.wait();
}
#[test]
fn dev_watch_does_not_rebuild_itself() {
if !gate() {
eprintln!("skipped: set GTDX_RUN_SMOKE=1 to enable");
return;
}
let tmp = tempfile::tempdir().unwrap();
let proj = tmp.path().join("quiet");
let home = tmp.path().join("home");
let status = Command::new(gtdx_bin())
.arg("new")
.arg("quiet")
.arg("--dir")
.arg(&proj)
.arg("--author")
.arg("tester")
.arg("-y")
.arg("--no-git")
.status()
.unwrap();
assert!(status.success());
let mut child = Command::new(gtdx_bin())
.env("GREENTIC_HOME", &home)
.arg("dev")
.arg("--watch")
.arg("--manifest")
.arg(proj.join("Cargo.toml"))
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap();
let stdout = child.stdout.take().unwrap();
let builds = Arc::new(Mutex::new(0usize));
let counter = Arc::clone(&builds);
std::thread::spawn(move || {
for line in BufReader::new(stdout).lines().map_while(Result::ok) {
if line.contains("building (")
&& let Ok(mut n) = counter.lock()
{
*n += 1;
}
}
});
std::thread::sleep(Duration::from_secs(20));
let _ = child.kill();
let _ = child.wait();
let builds = *builds.lock().unwrap();
assert!(
builds <= 2,
"watcher rebuilt {builds} times without any edit — it is retriggering on its own output"
);
}