mirror of
https://github.com/zhom/banderole.git
synced 2026-04-28 06:45:57 +02:00
395 lines
12 KiB
Rust
395 lines
12 KiB
Rust
use std::process::Command;
|
|
use std::time::Duration;
|
|
use tempfile::TempDir;
|
|
|
|
#[tokio::test]
|
|
async fn test_bundle_and_run() -> Result<(), Box<dyn std::error::Error>> {
|
|
let temp_dir = TempDir::new()?;
|
|
let test_app_path = temp_dir.path().join("test-app");
|
|
|
|
// Create a simple test app
|
|
std::fs::create_dir_all(&test_app_path)?;
|
|
|
|
let package_json = r#"{
|
|
"name": "integration-test-app",
|
|
"version": "1.0.0",
|
|
"main": "index.js",
|
|
"scripts": {
|
|
"start": "node index.js"
|
|
}
|
|
}"#;
|
|
|
|
let index_js = r#"
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
console.log("Hello from integration test!");
|
|
console.log("Node version:", process.version);
|
|
console.log("Platform:", process.platform);
|
|
console.log("Architecture:", process.arch);
|
|
|
|
// Test file system access
|
|
const testFile = path.join(__dirname, 'test.txt');
|
|
fs.writeFileSync(testFile, 'test content');
|
|
const content = fs.readFileSync(testFile, 'utf8');
|
|
console.log("File content:", content);
|
|
|
|
// Test environment variables
|
|
console.log("Test env var:", process.env.TEST_VAR || 'not set');
|
|
|
|
// Test process arguments
|
|
console.log("Process args:", process.argv.slice(2));
|
|
|
|
// Test module resolution
|
|
try {
|
|
const uuid = require('uuid');
|
|
console.log("UUID:", uuid.v4());
|
|
} catch (e) {
|
|
console.error("Failed to require uuid:", e.message);
|
|
}
|
|
|
|
process.exit(0);"#;
|
|
|
|
// Write test files
|
|
std::fs::write(test_app_path.join("package.json"), package_json)?;
|
|
std::fs::write(test_app_path.join("index.js"), index_js)?;
|
|
|
|
// Install test dependency
|
|
let npm_install = if cfg!(windows) {
|
|
Command::new("cmd")
|
|
.args(["/C", "npm", "install", "uuid"])
|
|
.current_dir(&test_app_path)
|
|
.output()?
|
|
} else {
|
|
Command::new("sh")
|
|
.args(["-c", "npm install uuid"])
|
|
.current_dir(&test_app_path)
|
|
.output()?
|
|
};
|
|
|
|
if !npm_install.status.success() {
|
|
eprintln!("npm install failed: {}", String::from_utf8_lossy(&npm_install.stderr));
|
|
}
|
|
|
|
// Build banderole if not already built
|
|
println!("Building banderole...");
|
|
let target_dir = std::env::current_dir()?.join("target");
|
|
let banderole_path = target_dir.join("debug/banderole");
|
|
|
|
if !banderole_path.exists() {
|
|
let output = Command::new("cargo")
|
|
.args(["build"])
|
|
.output()
|
|
.expect("Failed to build banderole");
|
|
|
|
assert!(
|
|
output.status.success(),
|
|
"Failed to build banderole: {}",
|
|
String::from_utf8_lossy(&output.stderr)
|
|
);
|
|
}
|
|
|
|
// Bundle the test app
|
|
println!("Bundling test app...");
|
|
|
|
let mut bundle_cmd = Command::new(&banderole_path);
|
|
bundle_cmd
|
|
.args(["bundle", test_app_path.to_str().unwrap()])
|
|
.current_dir(temp_dir.path());
|
|
|
|
let bundle_output = run_with_timeout(&mut bundle_cmd, Duration::from_secs(300))?;
|
|
|
|
if !bundle_output.status.success() {
|
|
println!(
|
|
"Bundle stdout: {}",
|
|
String::from_utf8_lossy(&bundle_output.stdout)
|
|
);
|
|
println!(
|
|
"Bundle stderr: {}",
|
|
String::from_utf8_lossy(&bundle_output.stderr)
|
|
);
|
|
return Err("Bundle command failed".into());
|
|
}
|
|
|
|
// Find the created executable
|
|
let executable_path = temp_dir.path().join(if cfg!(windows) {
|
|
"integration-test-app-1.0.0-win32-x64.exe"
|
|
} else if cfg!(target_os = "macos") && cfg!(target_arch = "aarch64") {
|
|
"integration-test-app-1.0.0-darwin-arm64"
|
|
} else if cfg!(target_os = "macos") {
|
|
"integration-test-app-1.0.0-darwin-x64"
|
|
} else if cfg!(target_arch = "aarch64") {
|
|
"integration-test-app-1.0.0-linux-arm64"
|
|
} else {
|
|
"integration-test-app-1.0.0-linux-x64"
|
|
});
|
|
|
|
if !executable_path.exists() {
|
|
let entries: Vec<_> = std::fs::read_dir(temp_dir.path())
|
|
.unwrap()
|
|
.filter_map(Result::ok)
|
|
.map(|e| e.file_name())
|
|
.collect();
|
|
panic!(
|
|
"Executable was not created at {}. Directory contents: {:?}",
|
|
executable_path.display(),
|
|
entries
|
|
);
|
|
}
|
|
|
|
// Make executable on Unix
|
|
#[cfg(unix)]
|
|
{
|
|
use std::os::unix::fs::PermissionsExt;
|
|
let perms = std::fs::metadata(&executable_path)?.permissions();
|
|
let mut perms = perms.clone();
|
|
perms.set_mode(0o755);
|
|
std::fs::set_permissions(&executable_path, perms)?;
|
|
}
|
|
|
|
// Test 1: Run the executable directly
|
|
println!("Running test 1: Direct execution");
|
|
let output = Command::new(&executable_path)
|
|
.env("TEST_VAR", "test_value")
|
|
.args(["--test-arg1", "value1"])
|
|
.output()?;
|
|
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
|
|
println!("Test 1 - Exit status: {}", output.status);
|
|
println!("Test 1 - Stdout: {}", stdout);
|
|
println!("Test 1 - Stderr: {}", stderr);
|
|
|
|
// Check for expected output
|
|
assert!(output.status.success(), "First run failed");
|
|
assert!(
|
|
stdout.contains("Hello from integration test!"),
|
|
"Expected greeting not found in output"
|
|
);
|
|
assert!(
|
|
stdout.contains("File content: test content"),
|
|
"File operations test failed"
|
|
);
|
|
assert!(
|
|
stdout.contains("Test env var: test_value"),
|
|
"Environment variable test failed"
|
|
);
|
|
assert!(
|
|
stdout.contains("--test-arg1"),
|
|
"Command line arguments test failed"
|
|
);
|
|
assert!(
|
|
stdout.contains("UUID: "),
|
|
"Module resolution test failed"
|
|
);
|
|
|
|
// Test 2: Run again to test cached execution
|
|
println!("Running test 2: Cached execution");
|
|
let output = Command::new(&executable_path)
|
|
.env("TEST_VAR", "cached_run")
|
|
.output()?;
|
|
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
println!("Test 2 - Exit status: {}", output.status);
|
|
println!("Test 2 - Output: {}", stdout);
|
|
|
|
assert!(output.status.success(), "Cached run failed");
|
|
assert!(
|
|
stdout.contains("Hello from integration test!"),
|
|
"Cached run output incorrect"
|
|
);
|
|
assert!(
|
|
stdout.contains("Test env var: cached_run"),
|
|
"Cached run env var not set correctly"
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_node_version_detection() {
|
|
let temp_dir = TempDir::new().unwrap();
|
|
let test_app_path = temp_dir.path().join("test-app");
|
|
|
|
// Create a simple test app with .nvmrc
|
|
std::fs::create_dir_all(&test_app_path).unwrap();
|
|
|
|
let package_json = r#"{
|
|
"name": "nvmrc-test-app",
|
|
"version": "1.0.0",
|
|
"main": "index.js"
|
|
}"#;
|
|
|
|
let index_js = r#"console.log("Node version:", process.version);
|
|
console.log("Platform:", process.platform);
|
|
process.exit(0);"#;
|
|
|
|
let nvmrc = "20.18.1";
|
|
|
|
std::fs::write(test_app_path.join("package.json"), package_json).unwrap();
|
|
std::fs::write(test_app_path.join("index.js"), index_js).unwrap();
|
|
std::fs::write(test_app_path.join(".nvmrc"), nvmrc).unwrap();
|
|
|
|
// Build banderole
|
|
println!("Building banderole for Node version test...");
|
|
let output = Command::new("cargo")
|
|
.args(&["build", "--release"])
|
|
.output()
|
|
.expect("Failed to build banderole");
|
|
|
|
assert!(
|
|
output.status.success(),
|
|
"Failed to build banderole: {}",
|
|
String::from_utf8_lossy(&output.stderr)
|
|
);
|
|
|
|
let banderole_path = std::env::current_dir()
|
|
.unwrap()
|
|
.join("target/release/banderole");
|
|
|
|
// Bundle the test app
|
|
println!("Bundling test app with .nvmrc...");
|
|
let mut bundle_cmd = Command::new(&banderole_path);
|
|
bundle_cmd
|
|
.args(&["bundle", test_app_path.to_str().unwrap()])
|
|
.current_dir(temp_dir.path());
|
|
|
|
let bundle_output = bundle_cmd.output()
|
|
.expect("Failed to bundle test app");
|
|
|
|
if !bundle_output.status.success() {
|
|
println!(
|
|
"Bundle stdout: {}",
|
|
String::from_utf8_lossy(&bundle_output.stdout)
|
|
);
|
|
println!(
|
|
"Bundle stderr: {}",
|
|
String::from_utf8_lossy(&bundle_output.stderr)
|
|
);
|
|
}
|
|
|
|
assert!(
|
|
bundle_output.status.success(),
|
|
"Bundle command failed: {}",
|
|
String::from_utf8_lossy(&bundle_output.stderr)
|
|
);
|
|
|
|
// Check that the output mentions the correct Node.js version
|
|
let stdout = String::from_utf8_lossy(&bundle_output.stdout);
|
|
let stderr = String::from_utf8_lossy(&bundle_output.stderr);
|
|
let combined_output = format!("{}{}", stdout, stderr);
|
|
println!("Bundle output: {}", combined_output);
|
|
|
|
assert!(
|
|
combined_output.contains("Node.js v20.18.1"),
|
|
"Expected Node.js version not found in output: {}",
|
|
combined_output
|
|
);
|
|
|
|
// Find and run the created executable to verify it uses the correct Node version
|
|
let executable_name = if cfg!(target_os = "windows") {
|
|
"nvmrc-test-app-1.0.0-win32-x64.exe"
|
|
} else if cfg!(target_os = "macos") && cfg!(target_arch = "aarch64") {
|
|
"nvmrc-test-app-1.0.0-darwin-arm64"
|
|
} else if cfg!(target_os = "macos") {
|
|
"nvmrc-test-app-1.0.0-darwin-x64"
|
|
} else if cfg!(target_arch = "aarch64") {
|
|
"nvmrc-test-app-1.0.0-linux-arm64"
|
|
} else {
|
|
"nvmrc-test-app-1.0.0-linux-x64"
|
|
};
|
|
|
|
let executable_path = temp_dir.path().join(executable_name);
|
|
assert!(
|
|
executable_path.exists(),
|
|
"Executable was not created: {}. Directory contents: {:?}",
|
|
executable_path.display(),
|
|
std::fs::read_dir(temp_dir.path())
|
|
.unwrap()
|
|
.map(|entry| entry.unwrap().file_name())
|
|
.collect::<Vec<_>>()
|
|
);
|
|
|
|
// Run the executable and check Node version
|
|
println!(
|
|
"Running executable to check Node version: {}",
|
|
executable_path.display()
|
|
);
|
|
|
|
let mut cmd = Command::new(&executable_path);
|
|
cmd.env("DEBUG", "1"); // Enable debug mode
|
|
|
|
// Use simple output instead of timeout to capture output correctly
|
|
let run_output = cmd.output()
|
|
.expect("Failed to run executable");
|
|
|
|
if !run_output.status.success() {
|
|
println!(
|
|
"Executable stdout: {}",
|
|
String::from_utf8_lossy(&run_output.stdout)
|
|
);
|
|
println!(
|
|
"Executable stderr: {}",
|
|
String::from_utf8_lossy(&run_output.stderr)
|
|
);
|
|
println!("Exit code: {:?}", run_output.status.code());
|
|
}
|
|
|
|
assert!(
|
|
run_output.status.success(),
|
|
"Executable failed with exit code {:?}. Stderr: {}",
|
|
run_output.status.code(),
|
|
String::from_utf8_lossy(&run_output.stderr)
|
|
);
|
|
|
|
let stdout = String::from_utf8_lossy(&run_output.stdout);
|
|
println!("Executable output: {}", stdout);
|
|
|
|
assert!(
|
|
stdout.contains("v20.18.1"),
|
|
"Expected Node.js version not found in executable output: {}",
|
|
stdout
|
|
);
|
|
}
|
|
|
|
fn run_with_timeout(cmd: &mut Command, timeout: Duration) -> std::io::Result<std::process::Output> {
|
|
use std::sync::mpsc;
|
|
use std::thread;
|
|
|
|
let child = cmd.spawn().expect("Failed to spawn process");
|
|
let (tx, rx) = mpsc::channel();
|
|
|
|
// Spawn a thread to wait for the process
|
|
let child_id = child.id();
|
|
thread::spawn(move || {
|
|
let result = child.wait_with_output();
|
|
let _ = tx.send(result);
|
|
});
|
|
|
|
// Wait for either completion or timeout
|
|
match rx.recv_timeout(timeout) {
|
|
Ok(result) => result,
|
|
Err(_) => {
|
|
// Timeout occurred, kill the process
|
|
#[cfg(unix)]
|
|
{
|
|
let _ = std::process::Command::new("kill")
|
|
.args(&["-9", &child_id.to_string()])
|
|
.output();
|
|
}
|
|
#[cfg(windows)]
|
|
{
|
|
let _ = std::process::Command::new("taskkill")
|
|
.args(&["/F", "/PID", &child_id.to_string()])
|
|
.output();
|
|
}
|
|
|
|
Err(std::io::Error::new(
|
|
std::io::ErrorKind::TimedOut,
|
|
"Process timed out",
|
|
))
|
|
}
|
|
}
|
|
}
|