use kopi::archive::{JdkStructureType, detect_jdk_root};
use kopi::config::KopiConfig;
use kopi::paths::install;
use kopi::storage::{InstalledJdk, JdkLister};
use kopi::version::Version;
use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::str::FromStr;
use tempfile::TempDir;
mod common;
use common::TestHomeGuard;
fn run_kopi_with_home(home: &TestHomeGuard, args: &[&str]) -> (String, String, bool) {
let mut cmd = Command::new(env!("CARGO_BIN_EXE_kopi"));
cmd.args(args);
cmd.env("KOPI_HOME", home.kopi_home());
cmd.env("HOME", home.path());
cmd.current_dir(home.path());
let output = cmd.output().expect("Failed to execute kopi");
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
(stdout, stderr, output.status.success())
}
fn create_mock_jdk_structure(base_path: &Path, structure_type: &str) -> PathBuf {
let java_binary = if cfg!(windows) { "java.exe" } else { "java" };
let javac_binary = if cfg!(windows) { "javac.exe" } else { "javac" };
match structure_type {
"direct" => {
let bin_dir = base_path.join("bin");
fs::create_dir_all(&bin_dir).unwrap();
fs::write(bin_dir.join(java_binary), "#!/bin/sh\necho \"Mock Java\"").unwrap();
fs::write(bin_dir.join(javac_binary), "#!/bin/sh\necho \"Mock Javac\"").unwrap();
base_path.to_path_buf()
}
"bundle" => {
let home_dir = install::bundle_java_home(base_path);
let bin_dir = home_dir.join("bin");
fs::create_dir_all(&bin_dir).unwrap();
fs::write(bin_dir.join(java_binary), "#!/bin/sh\necho \"Mock Java\"").unwrap();
fs::write(bin_dir.join(javac_binary), "#!/bin/sh\necho \"Mock Javac\"").unwrap();
base_path.to_path_buf()
}
"hybrid" => {
let jdk_dir = base_path.join("zulu-21.jdk");
let home_dir = install::bundle_java_home(&jdk_dir);
let bin_dir = home_dir.join("bin");
fs::create_dir_all(&bin_dir).unwrap();
fs::write(bin_dir.join(java_binary), "#!/bin/sh\necho \"Mock Java\"").unwrap();
fs::write(bin_dir.join(javac_binary), "#!/bin/sh\necho \"Mock Javac\"").unwrap();
#[cfg(unix)]
{
use std::os::unix::fs::symlink;
symlink("zulu-21.jdk/Contents/Home/bin", base_path.join("bin")).unwrap();
}
#[cfg(windows)]
{
use std::os::windows::fs::symlink_dir;
symlink_dir(&bin_dir, base_path.join("bin")).unwrap();
}
base_path.to_path_buf()
}
_ => panic!("Unknown structure type: {structure_type}"),
}
}
#[test]
fn test_structure_detection_integration() {
let temp_dir = TempDir::new().unwrap();
let direct_jdk = temp_dir.path().join("direct-jdk");
create_mock_jdk_structure(&direct_jdk, "direct");
let info = detect_jdk_root(&direct_jdk).unwrap();
assert_eq!(info.jdk_root, direct_jdk);
assert_eq!(info.java_home_suffix, "");
assert!(matches!(info.structure_type, JdkStructureType::Direct));
#[cfg(target_os = "macos")]
{
let bundle_jdk = temp_dir.path().join("bundle-jdk");
create_mock_jdk_structure(&bundle_jdk, "bundle");
let info = detect_jdk_root(&bundle_jdk).unwrap();
assert_eq!(info.jdk_root, install::bundle_java_home(&bundle_jdk));
assert_eq!(info.java_home_suffix, "Contents/Home");
assert!(matches!(info.structure_type, JdkStructureType::Bundle));
let hybrid_jdk = temp_dir.path().join("hybrid-jdk");
create_mock_jdk_structure(&hybrid_jdk, "hybrid");
let info = detect_jdk_root(&hybrid_jdk).unwrap();
assert_eq!(info.jdk_root, hybrid_jdk);
assert_eq!(info.java_home_suffix, "zulu-21.jdk/Contents/Home");
assert!(matches!(info.structure_type, JdkStructureType::Hybrid));
}
}
#[test]
fn test_installed_jdk_path_resolution() {
let guard = TestHomeGuard::new();
let test_home = guard.setup_kopi_structure();
let config = KopiConfig::new(test_home.kopi_home()).unwrap();
let jdks_dir = config.jdks_dir().unwrap();
let direct_jdk_path = jdks_dir.join("liberica-21");
create_mock_jdk_structure(&direct_jdk_path, "direct");
#[cfg(target_os = "macos")]
let bundle_jdk_path = jdks_dir.join("temurin-21");
#[cfg(target_os = "macos")]
create_mock_jdk_structure(&bundle_jdk_path, "bundle");
let direct_jdk = InstalledJdk::new(
"liberica".to_string(),
Version::from_str("21.0.0").unwrap(),
direct_jdk_path.clone(),
false,
);
let java_home = direct_jdk.resolve_java_home();
assert_eq!(java_home, direct_jdk_path);
let bin_path = direct_jdk.resolve_bin_path().unwrap();
assert_eq!(bin_path, direct_jdk_path.join("bin"));
assert!(bin_path.exists());
#[cfg(target_os = "macos")]
{
let bundle_jdk = InstalledJdk::new(
"temurin".to_string(),
Version::from_str("21.0.0").unwrap(),
bundle_jdk_path.clone(),
false,
);
let java_home = bundle_jdk.resolve_java_home();
assert_eq!(java_home, install::bundle_java_home(&bundle_jdk_path));
let bin_path = bundle_jdk.resolve_bin_path().unwrap();
assert_eq!(
bin_path,
install::bin_directory(&install::bundle_java_home(&bundle_jdk_path))
);
assert!(bin_path.exists());
}
}
#[test]
fn test_version_switching_workflow() {
let guard = TestHomeGuard::new();
let test_home = guard.setup_kopi_structure();
let config = KopiConfig::new(test_home.kopi_home()).unwrap();
let jdks_dir = config.jdks_dir().unwrap();
let liberica_path = jdks_dir.join("liberica-17");
create_mock_jdk_structure(&liberica_path, "direct");
#[cfg(target_os = "macos")]
let temurin_path = jdks_dir.join("temurin-21");
#[cfg(target_os = "macos")]
create_mock_jdk_structure(&temurin_path, "bundle");
fs::write(test_home.path().join(".kopi-version"), "liberica@17").unwrap();
let jdks = JdkLister::list_installed_jdks(&jdks_dir).unwrap();
assert!(
jdks.iter()
.any(|j| j.distribution == "liberica" && j.version.to_string() == "17")
);
#[cfg(target_os = "macos")]
{
assert!(
jdks.iter()
.any(|j| j.distribution == "temurin" && j.version.to_string() == "21")
);
fs::write(test_home.path().join(".kopi-version"), "temurin@21").unwrap();
let selected_jdk = jdks
.iter()
.find(|j| j.distribution == "temurin" && j.version.to_string() == "21")
.unwrap();
let java_home = selected_jdk.resolve_java_home();
assert!(java_home.join("bin").join("java").exists());
}
}
#[test]
fn test_env_command_with_different_structures() {
let guard = TestHomeGuard::new();
let test_home = guard.setup_kopi_structure();
let config = KopiConfig::new(test_home.kopi_home()).unwrap();
let jdks_dir = config.jdks_dir().unwrap();
let liberica_path = jdks_dir.join("liberica-17");
create_mock_jdk_structure(&liberica_path, "direct");
fs::write(test_home.path().join(".kopi-version"), "liberica@17").unwrap();
let (stdout, stderr, success) = run_kopi_with_home(test_home, &["env"]);
if !success {
eprintln!("env command failed with stderr: {stderr}");
eprintln!("stdout: {stdout}");
}
assert!(success);
assert!(
stdout.contains("JAVA_HOME"),
"Output should contain JAVA_HOME: {stdout}"
);
let liberica_path_str = liberica_path.to_string_lossy();
let liberica_path_escaped = liberica_path_str.replace('\\', "\\\\");
assert!(
stdout.contains(liberica_path_str.as_ref()) || stdout.contains(&liberica_path_escaped),
"Output should contain liberica path {} (or escaped version): {stdout}",
liberica_path.display()
);
#[cfg(target_os = "macos")]
{
let temurin_path = jdks_dir.join("temurin-21");
create_mock_jdk_structure(&temurin_path, "bundle");
fs::write(test_home.path().join(".kopi-version"), "temurin@21").unwrap();
let (stdout, _, success) = run_kopi_with_home(test_home, &["env"]);
assert!(success);
let expected_java_home = install::bundle_java_home(&temurin_path);
assert!(
stdout.contains("JAVA_HOME"),
"Output should contain JAVA_HOME: {stdout}"
);
assert!(
stdout.contains(&expected_java_home.to_string_lossy().to_string()),
"Output should contain temurin path {}: {stdout}",
expected_java_home.display()
);
}
}
#[test]
#[cfg(target_os = "macos")]
fn test_shim_execution_performance() {
use std::time::Instant;
let guard = TestHomeGuard::new();
let test_home = guard.setup_kopi_structure();
let config = KopiConfig::new(test_home.kopi_home()).unwrap();
let jdks_dir = config.jdks_dir().unwrap();
let shims_dir = config.bin_dir().unwrap();
let temurin_path = jdks_dir.join("temurin-21");
create_mock_jdk_structure(&temurin_path, "bundle");
let java_path = install::bin_directory(&install::bundle_java_home(&temurin_path)).join("java");
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = fs::metadata(&java_path).unwrap().permissions();
perms.set_mode(0o755);
fs::set_permissions(&java_path, perms).unwrap();
}
let shim_src = env!("CARGO_BIN_EXE_kopi-shim");
let shim_dst = shims_dir.join("java");
fs::copy(shim_src, &shim_dst).unwrap();
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = fs::metadata(&shim_dst).unwrap().permissions();
perms.set_mode(0o755);
fs::set_permissions(&shim_dst, perms).unwrap();
}
fs::write(test_home.path().join(".kopi-version"), "temurin@21").unwrap();
let mut total_time = std::time::Duration::ZERO;
let iterations = 5;
for _ in 0..iterations {
let start = Instant::now();
let mut cmd = Command::new(&shim_dst);
cmd.env("KOPI_HOME", test_home.kopi_home());
cmd.env("HOME", test_home.path());
cmd.arg("--version");
let output = cmd.output().expect("Failed to execute shim");
let elapsed = start.elapsed();
if output.status.success() {
total_time += elapsed;
}
}
let avg_time = total_time / iterations as u32;
println!("Average shim execution time: {avg_time:?}");
assert!(
avg_time.as_millis() < 50,
"Shim execution time {avg_time:?} exceeds 50ms threshold"
);
}
#[test]
fn test_error_handling_invalid_structure() {
let temp_dir = TempDir::new().unwrap();
let invalid_jdk = temp_dir.path().join("invalid-jdk");
fs::create_dir_all(&invalid_jdk).unwrap();
fs::write(invalid_jdk.join("README.txt"), "This is not a JDK").unwrap();
let result = detect_jdk_root(&invalid_jdk);
assert!(result.is_err());
}
#[test]
fn test_concurrent_access() {
use std::sync::Arc;
use std::thread;
let guard = TestHomeGuard::new();
let test_home_guard = guard.setup_kopi_structure();
let test_home = Arc::new(test_home_guard.path().to_path_buf());
let kopi_home = test_home.join(".kopi");
let config = Arc::new(KopiConfig::new(kopi_home).unwrap());
let jdks_dir = config.jdks_dir().unwrap();
for i in 0..3 {
let jdk_path = jdks_dir.join(format!("test-jdk-{i}"));
create_mock_jdk_structure(&jdk_path, "direct");
}
let mut handles = vec![];
for i in 0..5 {
let config_clone = Arc::clone(&config);
let handle = thread::spawn(move || {
for _ in 0..10 {
let jdks_dir = config_clone.jdks_dir().unwrap();
let jdks = JdkLister::list_installed_jdks(&jdks_dir).unwrap();
assert!(jdks.len() >= 3);
for jdk in &jdks {
let _java_home = jdk.resolve_java_home();
let _bin_path = jdk.resolve_bin_path();
}
}
println!("Thread {i} completed");
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
}