Adds WEEVIL_HOME-based test isolation so cargo test never touches the real system. All commands run against a fresh TempDir per test. Environment tests cover doctor, uninstall, new, and setup across every combination of missing/present dependencies. Project lifecycle tests cover creation, config persistence, upgrade, and build scripts. Full round-trip lifecycle test: new → gradlew test → gradlew compileJava → uninstall → doctor (unhealthy) → setup → doctor (healthy). Confirms skeleton projects build and pass tests out of the box, and that uninstall leaves user projects untouched. 34 tests, zero warnings.
238 lines
8.9 KiB
Rust
238 lines
8.9 KiB
Rust
// File: tests/integration/project_lifecycle_tests.rs
|
|
// Integration tests - full project lifecycle
|
|
//
|
|
// Same strategy as environment_tests: WEEVIL_HOME points to a TempDir,
|
|
// mock SDKs are created manually with fs, and we invoke the compiled
|
|
// binary directly rather than going through `cargo run`.
|
|
|
|
use tempfile::TempDir;
|
|
use std::fs;
|
|
use std::process::Command;
|
|
|
|
/// Helper: returns a configured Command pointing at the weevil binary with
|
|
/// WEEVIL_HOME set to the given temp directory.
|
|
fn weevil_cmd(weevil_home: &TempDir) -> Command {
|
|
let mut cmd = Command::new(assert_cmd::cargo::cargo_bin!("weevil"));
|
|
cmd.env("WEEVIL_HOME", weevil_home.path());
|
|
cmd
|
|
}
|
|
|
|
/// Helper: create a minimal mock FTC SDK at the given path.
|
|
fn create_mock_ftc_sdk(path: &std::path::Path) {
|
|
fs::create_dir_all(path.join("TeamCode/src/main/java")).unwrap();
|
|
fs::create_dir_all(path.join("FtcRobotController")).unwrap();
|
|
fs::write(path.join("build.gradle"), "// mock").unwrap();
|
|
fs::write(path.join(".version"), "v10.1.1\n").unwrap();
|
|
}
|
|
|
|
/// Helper: create a minimal mock Android SDK at the given path.
|
|
fn create_mock_android_sdk(path: &std::path::Path) {
|
|
fs::create_dir_all(path.join("platform-tools")).unwrap();
|
|
fs::write(path.join("platform-tools/adb"), "").unwrap();
|
|
}
|
|
|
|
/// Helper: populate a WEEVIL_HOME with both mock SDKs (fully healthy system)
|
|
fn populate_healthy(weevil_home: &TempDir) {
|
|
create_mock_ftc_sdk(&weevil_home.path().join("ftc-sdk"));
|
|
create_mock_android_sdk(&weevil_home.path().join("android-sdk"));
|
|
}
|
|
|
|
/// Helper: print labeled output from a test so it's visually distinct from test assertions
|
|
fn print_output(test_name: &str, output: &std::process::Output) {
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
println!("\n╔══ {} ══════════════════════════════════════════════╗", test_name);
|
|
if !stdout.is_empty() {
|
|
println!("║ stdout:");
|
|
for line in stdout.lines() {
|
|
println!("║ {}", line);
|
|
}
|
|
}
|
|
if !stderr.is_empty() {
|
|
println!("║ stderr:");
|
|
for line in stderr.lines() {
|
|
println!("║ {}", line);
|
|
}
|
|
}
|
|
println!("╚════════════════════════════════════════════════════════╝\n");
|
|
}
|
|
|
|
#[test]
|
|
fn test_project_creation_with_mock_sdk() {
|
|
let home = TempDir::new().unwrap();
|
|
populate_healthy(&home);
|
|
|
|
let output = weevil_cmd(&home)
|
|
.arg("new")
|
|
.arg("test-robot")
|
|
.current_dir(home.path())
|
|
.output()
|
|
.expect("Failed to run weevil new");
|
|
print_output("test_project_creation_with_mock_sdk", &output);
|
|
|
|
let project_dir = home.path().join("test-robot");
|
|
assert!(output.status.success(), "weevil new failed");
|
|
assert!(project_dir.join(".weevil.toml").exists(), ".weevil.toml missing");
|
|
assert!(project_dir.join("build.gradle.kts").exists(), "build.gradle.kts missing");
|
|
assert!(project_dir.join("src/main/java/robot").exists(), "src/main/java/robot missing");
|
|
}
|
|
|
|
#[test]
|
|
fn test_project_config_persistence() {
|
|
let home = TempDir::new().unwrap();
|
|
populate_healthy(&home);
|
|
|
|
let output = weevil_cmd(&home)
|
|
.arg("new")
|
|
.arg("config-test")
|
|
.current_dir(home.path())
|
|
.output()
|
|
.expect("Failed to run weevil new");
|
|
print_output("test_project_config_persistence", &output);
|
|
assert!(output.status.success(), "weevil new failed");
|
|
|
|
let project_dir = home.path().join("config-test");
|
|
let config_content = fs::read_to_string(project_dir.join(".weevil.toml"))
|
|
.expect(".weevil.toml not found");
|
|
|
|
assert!(config_content.contains("project_name = \"config-test\""),
|
|
"project_name missing from config:\n{}", config_content);
|
|
assert!(config_content.contains("ftc_sdk_path"),
|
|
"ftc_sdk_path missing from config:\n{}", config_content);
|
|
}
|
|
|
|
#[test]
|
|
fn test_project_upgrade_preserves_code() {
|
|
let home = TempDir::new().unwrap();
|
|
populate_healthy(&home);
|
|
|
|
// Create project
|
|
let output = weevil_cmd(&home)
|
|
.arg("new")
|
|
.arg("upgrade-test")
|
|
.current_dir(home.path())
|
|
.output()
|
|
.expect("Failed to run weevil new");
|
|
print_output("test_project_upgrade_preserves_code (new)", &output);
|
|
assert!(output.status.success(), "weevil new failed");
|
|
|
|
let project_dir = home.path().join("upgrade-test");
|
|
|
|
// Add custom code
|
|
let custom_file = project_dir.join("src/main/java/robot/CustomCode.java");
|
|
fs::write(&custom_file, "// My custom robot code").unwrap();
|
|
|
|
// Upgrade
|
|
let output = weevil_cmd(&home)
|
|
.arg("upgrade")
|
|
.arg(project_dir.to_str().unwrap())
|
|
.output()
|
|
.expect("Failed to run weevil upgrade");
|
|
print_output("test_project_upgrade_preserves_code (upgrade)", &output);
|
|
|
|
// Custom code survives
|
|
assert!(custom_file.exists(), "custom code file was deleted by upgrade");
|
|
let content = fs::read_to_string(&custom_file).unwrap();
|
|
assert!(content.contains("My custom robot code"), "custom code was overwritten");
|
|
|
|
// Config still present
|
|
assert!(project_dir.join(".weevil.toml").exists(), ".weevil.toml missing after upgrade");
|
|
}
|
|
|
|
#[test]
|
|
fn test_build_scripts_read_from_config() {
|
|
let home = TempDir::new().unwrap();
|
|
populate_healthy(&home);
|
|
|
|
let output = weevil_cmd(&home)
|
|
.arg("new")
|
|
.arg("build-test")
|
|
.current_dir(home.path())
|
|
.output()
|
|
.expect("Failed to run weevil new");
|
|
print_output("test_build_scripts_read_from_config", &output);
|
|
assert!(output.status.success(), "weevil new failed");
|
|
|
|
let project_dir = home.path().join("build-test");
|
|
|
|
let build_sh = fs::read_to_string(project_dir.join("build.sh"))
|
|
.expect("build.sh not found");
|
|
assert!(build_sh.contains(".weevil.toml"), "build.sh doesn't reference .weevil.toml");
|
|
assert!(build_sh.contains("ftc_sdk_path"), "build.sh doesn't reference ftc_sdk_path");
|
|
|
|
let build_bat = fs::read_to_string(project_dir.join("build.bat"))
|
|
.expect("build.bat not found");
|
|
assert!(build_bat.contains(".weevil.toml"), "build.bat doesn't reference .weevil.toml");
|
|
assert!(build_bat.contains("ftc_sdk_path"), "build.bat doesn't reference ftc_sdk_path");
|
|
}
|
|
|
|
#[test]
|
|
fn test_config_command_show() {
|
|
let home = TempDir::new().unwrap();
|
|
populate_healthy(&home);
|
|
|
|
// Create project
|
|
let output = weevil_cmd(&home)
|
|
.arg("new")
|
|
.arg("config-show-test")
|
|
.current_dir(home.path())
|
|
.output()
|
|
.expect("Failed to run weevil new");
|
|
print_output("test_config_command_show (new)", &output);
|
|
assert!(output.status.success(), "weevil new failed");
|
|
|
|
let project_dir = home.path().join("config-show-test");
|
|
|
|
// Show config
|
|
let output = weevil_cmd(&home)
|
|
.arg("config")
|
|
.arg(project_dir.to_str().unwrap())
|
|
.output()
|
|
.expect("Failed to run weevil config");
|
|
print_output("test_config_command_show (config)", &output);
|
|
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
assert!(stdout.contains("config-show-test"), "project name missing from config output");
|
|
}
|
|
|
|
#[test]
|
|
fn test_multiple_projects_different_sdks() {
|
|
let home = TempDir::new().unwrap();
|
|
populate_healthy(&home);
|
|
|
|
// Create a second FTC SDK with a different version
|
|
let sdk2 = home.path().join("ftc-sdk-v11");
|
|
create_mock_ftc_sdk(&sdk2);
|
|
fs::write(sdk2.join(".version"), "v11.0.0\n").unwrap();
|
|
|
|
// Create first project (uses default ftc-sdk in WEEVIL_HOME)
|
|
let output = weevil_cmd(&home)
|
|
.arg("new")
|
|
.arg("robot1")
|
|
.current_dir(home.path())
|
|
.output()
|
|
.expect("Failed to create robot1");
|
|
print_output("test_multiple_projects_different_sdks (robot1)", &output);
|
|
assert!(output.status.success(), "weevil new robot1 failed");
|
|
|
|
// Create second project — would need --ftc-sdk flag if supported,
|
|
// otherwise both use the same default. Verify they each have valid configs.
|
|
let output = weevil_cmd(&home)
|
|
.arg("new")
|
|
.arg("robot2")
|
|
.current_dir(home.path())
|
|
.output()
|
|
.expect("Failed to create robot2");
|
|
print_output("test_multiple_projects_different_sdks (robot2)", &output);
|
|
assert!(output.status.success(), "weevil new robot2 failed");
|
|
|
|
let config1 = fs::read_to_string(home.path().join("robot1/.weevil.toml"))
|
|
.expect("robot1 .weevil.toml missing");
|
|
let config2 = fs::read_to_string(home.path().join("robot2/.weevil.toml"))
|
|
.expect("robot2 .weevil.toml missing");
|
|
|
|
assert!(config1.contains("project_name = \"robot1\""), "robot1 config wrong");
|
|
assert!(config2.contains("project_name = \"robot2\""), "robot2 config wrong");
|
|
assert!(config1.contains("ftc_sdk_path"), "robot1 missing ftc_sdk_path");
|
|
assert!(config2.contains("ftc_sdk_path"), "robot2 missing ftc_sdk_path");
|
|
} |