mod common;
use assert_cmd::Command;
use common::TestHomeGuard;
use kopi::paths::install;
use predicates::prelude::*;
use serial_test::serial;
use std::fs;
use std::path::Path;
fn create_legacy_jdk_installation(kopi_home: &Path, distribution: &str, version: &str) {
let jdk_dir = kopi_home
.join("jdks")
.join(format!("{distribution}-{version}"));
fs::create_dir_all(&jdk_dir).unwrap();
#[cfg(target_os = "macos")]
{
if distribution == "temurin" {
let bundle_home = install::bundle_java_home(&jdk_dir);
let bundle_bin = install::bin_directory(&bundle_home);
fs::create_dir_all(&bundle_bin).unwrap();
fs::write(bundle_bin.join("java"), "#!/bin/sh\necho 'mock java'").unwrap();
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = fs::metadata(bundle_bin.join("java")).unwrap().permissions();
perms.set_mode(0o755);
fs::set_permissions(bundle_bin.join("java"), perms).unwrap();
}
} else {
let bin_dir = jdk_dir.join("bin");
fs::create_dir_all(&bin_dir).unwrap();
fs::write(bin_dir.join("java"), "#!/bin/sh\necho 'mock java'").unwrap();
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = fs::metadata(bin_dir.join("java")).unwrap().permissions();
perms.set_mode(0o755);
fs::set_permissions(bin_dir.join("java"), perms).unwrap();
}
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = fs::metadata(bin_dir.join("java")).unwrap().permissions();
perms.set_mode(0o755);
fs::set_permissions(bin_dir.join("java"), perms).unwrap();
}
}
}
#[cfg(not(target_os = "macos"))]
{
let bin_dir = jdk_dir.join("bin");
fs::create_dir_all(&bin_dir).unwrap();
fs::write(bin_dir.join("java"), "#!/bin/sh\necho 'mock java'").unwrap();
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = fs::metadata(bin_dir.join("java")).unwrap().permissions();
perms.set_mode(0o755);
fs::set_permissions(bin_dir.join("java"), perms).unwrap();
}
}
let lib_dir = if cfg!(target_os = "macos") && distribution == "temurin" {
install::bundle_java_home(&jdk_dir).join("lib")
} else {
jdk_dir.join("lib")
};
fs::create_dir_all(&lib_dir).unwrap();
let release_path = if cfg!(target_os = "macos") && distribution == "temurin" {
install::bundle_java_home(&jdk_dir).join("release")
} else {
jdk_dir.join("release")
};
fs::write(
&release_path,
format!("JAVA_VERSION=\"{version}\"\nIMPLEMENTOR=\"Mock\""),
)
.unwrap();
}
#[test]
#[serial]
fn test_existing_installations_work_without_metadata() {
let test_home = TestHomeGuard::new();
test_home.setup_kopi_structure();
let kopi_home = test_home.kopi_home();
create_legacy_jdk_installation(&kopi_home, "temurin", "21.0.1");
create_legacy_jdk_installation(&kopi_home, "liberica", "17.0.9");
create_legacy_jdk_installation(&kopi_home, "zulu", "11.0.21");
let mut cmd = Command::cargo_bin("kopi").unwrap();
cmd.env("KOPI_HOME", kopi_home.to_str().unwrap())
.env("HOME", test_home.path())
.arg("list")
.assert()
.success()
.stdout(predicate::str::contains("temurin@21.0.1"))
.stdout(predicate::str::contains("liberica@17.0.9"))
.stdout(predicate::str::contains("zulu@11.0.21"));
for (dist, ver) in [
("temurin", "21.0.1"),
("liberica", "17.0.9"),
("zulu", "11.0.21"),
] {
fs::write(
test_home.path().join(".kopi-version"),
format!("{dist}@{ver}"),
)
.unwrap();
let mut cmd = Command::cargo_bin("kopi").unwrap();
cmd.env("KOPI_HOME", kopi_home.to_str().unwrap())
.env("HOME", test_home.path())
.current_dir(test_home.path())
.arg("env")
.assert()
.success()
.stdout(predicate::str::contains("JAVA_HOME"))
.stdout(predicate::str::contains(format!("{dist}-{ver}")));
}
}
#[test]
#[serial]
fn test_mixed_environment_old_and_new_installations() {
let test_home = TestHomeGuard::new();
test_home.setup_kopi_structure();
let kopi_home = test_home.kopi_home();
create_legacy_jdk_installation(&kopi_home, "liberica", "17.0.9");
let new_jdk_dir = kopi_home.join("jdks").join("temurin-21.0.1");
fs::create_dir_all(&new_jdk_dir).unwrap();
#[cfg(target_os = "macos")]
{
let bundle_bin = install::bin_directory(&install::bundle_java_home(&new_jdk_dir));
fs::create_dir_all(&bundle_bin).unwrap();
fs::write(bundle_bin.join("java"), "#!/bin/sh\necho 'mock java'").unwrap();
}
#[cfg(not(target_os = "macos"))]
{
let bin_dir = new_jdk_dir.join("bin");
fs::create_dir_all(&bin_dir).unwrap();
fs::write(bin_dir.join("java"), "#!/bin/sh\necho 'mock java'").unwrap();
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = fs::metadata(bin_dir.join("java")).unwrap().permissions();
perms.set_mode(0o755);
fs::set_permissions(bin_dir.join("java"), perms).unwrap();
}
}
let metadata_content = r#"{
"id": "test-id",
"archive_type": "tar.gz",
"distribution": "temurin",
"major_version": 21,
"java_version": "21.0.1",
"distribution_version": "21.0.1+35.1",
"jdk_version": 21,
"directly_downloadable": true,
"filename": "test.tar.gz",
"links": {
"pkg_download_redirect": "https://example.com",
"pkg_info_uri": null
},
"free_use_in_production": true,
"tck_tested": "yes",
"tck_cert_uri": "https://example.com/cert",
"aqavit_certified": "yes",
"aqavit_cert_uri": "https://example.com/aqavit",
"size": 100000000,
"feature": [],
"installation_metadata": {
"java_home_suffix": "Contents/Home",
"structure_type": "bundle",
"platform": "macos",
"metadata_version": 1
}
}"#;
let metadata_path = kopi_home.join("jdks").join("temurin-21.0.1.meta.json");
fs::write(&metadata_path, metadata_content).unwrap();
let mut cmd = Command::cargo_bin("kopi").unwrap();
cmd.env("KOPI_HOME", kopi_home.to_str().unwrap())
.env("HOME", test_home.path())
.arg("list")
.assert()
.success()
.stdout(predicate::str::contains("liberica@17.0.9"))
.stdout(predicate::str::contains("temurin@21.0.1"));
fs::write(test_home.path().join(".kopi-version"), "liberica@17.0.9").unwrap();
let mut cmd = Command::cargo_bin("kopi").unwrap();
cmd.env("KOPI_HOME", kopi_home.to_str().unwrap())
.env("HOME", test_home.path())
.current_dir(test_home.path())
.arg("env")
.assert()
.success()
.stdout(predicate::str::contains("JAVA_HOME"))
.stdout(predicate::str::contains("liberica-17.0.9"));
fs::write(test_home.path().join(".kopi-version"), "temurin@21.0.1").unwrap();
let mut cmd = Command::cargo_bin("kopi").unwrap();
cmd.env("KOPI_HOME", kopi_home.to_str().unwrap())
.env("HOME", test_home.path())
.current_dir(test_home.path())
.arg("env")
.assert()
.success()
.stdout(predicate::str::contains("JAVA_HOME"))
.stdout(predicate::str::contains("temurin-21.0.1"));
}
#[test]
#[serial]
fn test_upgrade_scenario_from_old_to_new() {
let test_home = TestHomeGuard::new();
test_home.setup_kopi_structure();
let kopi_home = test_home.kopi_home();
create_legacy_jdk_installation(&kopi_home, "temurin", "17.0.1");
let mut cmd = Command::cargo_bin("kopi").unwrap();
cmd.env("KOPI_HOME", kopi_home.to_str().unwrap())
.env("HOME", test_home.path())
.arg("list")
.assert()
.success()
.stdout(predicate::str::contains("temurin@17.0.1"));
fs::write(test_home.path().join(".kopi-version"), "temurin@17.0.1").unwrap();
let mut cmd = Command::cargo_bin("kopi").unwrap();
cmd.env("KOPI_HOME", kopi_home.to_str().unwrap())
.env("HOME", test_home.path())
.current_dir(test_home.path())
.arg("env")
.assert()
.success()
.stdout(predicate::str::contains("JAVA_HOME"))
.stdout(predicate::str::contains("temurin-17.0.1"));
let new_jdk_dir = kopi_home.join("jdks").join("temurin-21.0.1");
fs::create_dir_all(&new_jdk_dir).unwrap();
#[cfg(target_os = "macos")]
{
let bundle_bin = install::bin_directory(&install::bundle_java_home(&new_jdk_dir));
fs::create_dir_all(&bundle_bin).unwrap();
fs::write(bundle_bin.join("java"), "#!/bin/sh\necho 'mock java'").unwrap();
}
#[cfg(not(target_os = "macos"))]
{
let bin_dir = new_jdk_dir.join("bin");
fs::create_dir_all(&bin_dir).unwrap();
fs::write(bin_dir.join("java"), "#!/bin/sh\necho 'mock java'").unwrap();
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = fs::metadata(bin_dir.join("java")).unwrap().permissions();
perms.set_mode(0o755);
fs::set_permissions(bin_dir.join("java"), perms).unwrap();
}
}
let metadata_content = r#"{
"id": "new-test-id",
"archive_type": "tar.gz",
"distribution": "temurin",
"major_version": 21,
"java_version": "21.0.1",
"distribution_version": "21.0.1+35.1",
"jdk_version": 21,
"directly_downloadable": true,
"filename": "test.tar.gz",
"links": {
"pkg_download_redirect": "https://example.com",
"pkg_info_uri": null
},
"free_use_in_production": true,
"tck_tested": "yes",
"tck_cert_uri": "https://example.com/cert",
"aqavit_certified": "yes",
"aqavit_cert_uri": "https://example.com/aqavit",
"size": 100000000,
"feature": [],
"installation_metadata": {
"java_home_suffix": "Contents/Home",
"structure_type": "bundle",
"platform": "macos",
"metadata_version": 1
}
}"#;
let metadata_path = kopi_home.join("jdks").join("temurin-21.0.1.meta.json");
fs::write(&metadata_path, metadata_content).unwrap();
let mut cmd = Command::cargo_bin("kopi").unwrap();
cmd.env("KOPI_HOME", kopi_home.to_str().unwrap())
.env("HOME", test_home.path())
.arg("list")
.assert()
.success()
.stdout(predicate::str::contains("temurin@17.0.1"))
.stdout(predicate::str::contains("temurin@21.0.1"));
fs::write(test_home.path().join(".kopi-version"), "temurin@17.0.1").unwrap();
let mut cmd = Command::cargo_bin("kopi").unwrap();
cmd.env("KOPI_HOME", kopi_home.to_str().unwrap())
.env("HOME", test_home.path())
.current_dir(test_home.path())
.arg("env")
.assert()
.success()
.stdout(predicate::str::contains("JAVA_HOME"))
.stdout(predicate::str::contains("temurin-17.0.1"));
fs::write(test_home.path().join(".kopi-version"), "temurin@21.0.1").unwrap();
let mut cmd = Command::cargo_bin("kopi").unwrap();
cmd.env("KOPI_HOME", kopi_home.to_str().unwrap())
.env("HOME", test_home.path())
.current_dir(test_home.path())
.arg("env")
.assert()
.success()
.stdout(predicate::str::contains("JAVA_HOME"))
.stdout(predicate::str::contains("temurin-21.0.1"));
}
#[test]
#[serial]
fn test_rollback_scenario() {
let test_home = TestHomeGuard::new();
test_home.setup_kopi_structure();
let kopi_home = test_home.kopi_home();
create_legacy_jdk_installation(&kopi_home, "corretto", "17.0.9");
let new_jdk_dir = kopi_home.join("jdks").join("corretto-21.0.1");
fs::create_dir_all(&new_jdk_dir).unwrap();
#[cfg(target_os = "macos")]
{
let bin_dir = new_jdk_dir.join("bin");
fs::create_dir_all(&bin_dir).unwrap();
fs::write(bin_dir.join("java"), "#!/bin/sh\necho 'mock java'").unwrap();
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = fs::metadata(bin_dir.join("java")).unwrap().permissions();
perms.set_mode(0o755);
fs::set_permissions(bin_dir.join("java"), perms).unwrap();
}
}
#[cfg(not(target_os = "macos"))]
{
let bin_dir = new_jdk_dir.join("bin");
fs::create_dir_all(&bin_dir).unwrap();
fs::write(bin_dir.join("java"), "#!/bin/sh\necho 'mock java'").unwrap();
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = fs::metadata(bin_dir.join("java")).unwrap().permissions();
perms.set_mode(0o755);
fs::set_permissions(bin_dir.join("java"), perms).unwrap();
}
}
let bad_metadata_content = r#"{
"id": "test-id",
"installation_metadata": {
"java_home_suffix": "Contents/Home",
"structure_type": "bundle",
"platform": "macos",
"metadata_version": 1
}
}"#;
let metadata_path = kopi_home.join("jdks").join("corretto-21.0.1.meta.json");
fs::write(&metadata_path, bad_metadata_content).unwrap();
let mut cmd = Command::cargo_bin("kopi").unwrap();
cmd.env("KOPI_HOME", kopi_home.to_str().unwrap())
.env("HOME", test_home.path())
.arg("list")
.assert()
.success()
.stdout(predicate::str::contains("corretto@17.0.9"))
.stdout(predicate::str::contains("corretto@21.0.1"));
fs::write(test_home.path().join(".kopi-version"), "corretto@17.0.9").unwrap();
let mut cmd = Command::cargo_bin("kopi").unwrap();
cmd.env("KOPI_HOME", kopi_home.to_str().unwrap())
.env("HOME", test_home.path())
.current_dir(test_home.path())
.arg("env")
.assert()
.success()
.stdout(predicate::str::contains("JAVA_HOME"))
.stdout(predicate::str::contains("corretto-17.0.9"));
fs::write(test_home.path().join(".kopi-version"), "corretto@21.0.1").unwrap();
let mut cmd = Command::cargo_bin("kopi").unwrap();
cmd.env("KOPI_HOME", kopi_home.to_str().unwrap())
.env("HOME", test_home.path())
.current_dir(test_home.path())
.arg("env")
.assert()
.success()
.stdout(predicate::str::contains("JAVA_HOME"))
.stdout(predicate::str::contains("corretto-21.0.1"));
}
#[test]
#[serial]
fn test_shim_with_legacy_installation() {
let test_home = TestHomeGuard::new();
test_home.setup_kopi_structure();
let kopi_home = test_home.kopi_home();
create_legacy_jdk_installation(&kopi_home, "temurin", "21.0.1");
let project_dir = test_home.path().join("test-project");
fs::create_dir_all(&project_dir).unwrap();
fs::write(project_dir.join(".kopi-version"), "temurin@21.0.1").unwrap();
let mut cmd = Command::cargo_bin("kopi").unwrap();
cmd.env("KOPI_HOME", kopi_home.to_str().unwrap())
.env("HOME", test_home.path())
.current_dir(&project_dir)
.arg("env")
.assert()
.success()
.stdout(predicate::str::contains("JAVA_HOME"))
.stdout(predicate::str::contains("temurin-21.0.1"));
let mut cmd = Command::cargo_bin("kopi").unwrap();
cmd.env("KOPI_HOME", kopi_home.to_str().unwrap())
.env("HOME", test_home.path())
.current_dir(&project_dir)
.arg("current")
.assert()
.success()
.stdout(predicate::str::contains("temurin@21.0.1"));
}
#[test]
#[serial]
fn test_performance_comparison() {
use std::time::Instant;
let test_home = TestHomeGuard::new();
test_home.setup_kopi_structure();
let kopi_home = test_home.kopi_home();
create_legacy_jdk_installation(&kopi_home, "liberica", "17.0.9");
let new_jdk_dir = kopi_home.join("jdks").join("temurin-21.0.1");
fs::create_dir_all(&new_jdk_dir).unwrap();
#[cfg(target_os = "macos")]
{
let bundle_bin = install::bin_directory(&install::bundle_java_home(&new_jdk_dir));
fs::create_dir_all(&bundle_bin).unwrap();
fs::write(bundle_bin.join("java"), "#!/bin/sh\necho 'mock java'").unwrap();
}
#[cfg(not(target_os = "macos"))]
{
let bin_dir = new_jdk_dir.join("bin");
fs::create_dir_all(&bin_dir).unwrap();
fs::write(bin_dir.join("java"), "#!/bin/sh\necho 'mock java'").unwrap();
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = fs::metadata(bin_dir.join("java")).unwrap().permissions();
perms.set_mode(0o755);
fs::set_permissions(bin_dir.join("java"), perms).unwrap();
}
}
let metadata_content = r#"{
"id": "test-id",
"archive_type": "tar.gz",
"distribution": "temurin",
"major_version": 21,
"java_version": "21.0.1",
"distribution_version": "21.0.1+35.1",
"jdk_version": 21,
"directly_downloadable": true,
"filename": "test.tar.gz",
"links": {
"pkg_download_redirect": "https://example.com",
"pkg_info_uri": null
},
"free_use_in_production": true,
"tck_tested": "yes",
"tck_cert_uri": "https://example.com/cert",
"aqavit_certified": "yes",
"aqavit_cert_uri": "https://example.com/aqavit",
"size": 100000000,
"feature": [],
"installation_metadata": {
"java_home_suffix": "Contents/Home",
"structure_type": "bundle",
"platform": "macos",
"metadata_version": 1
}
}"#;
let metadata_path = kopi_home.join("jdks").join("temurin-21.0.1.meta.json");
fs::write(&metadata_path, metadata_content).unwrap();
fs::write(test_home.path().join(".kopi-version"), "liberica@17.0.9").unwrap();
let start = Instant::now();
for _ in 0..10 {
let mut cmd = Command::cargo_bin("kopi").unwrap();
cmd.env("KOPI_HOME", kopi_home.to_str().unwrap())
.env("HOME", test_home.path())
.current_dir(test_home.path())
.arg("env")
.assert()
.success();
}
let legacy_time = start.elapsed();
fs::write(test_home.path().join(".kopi-version"), "temurin@21.0.1").unwrap();
let start = Instant::now();
for _ in 0..10 {
let mut cmd = Command::cargo_bin("kopi").unwrap();
cmd.env("KOPI_HOME", kopi_home.to_str().unwrap())
.env("HOME", test_home.path())
.current_dir(test_home.path())
.arg("env")
.assert()
.success();
}
let new_time = start.elapsed();
println!("Legacy (runtime detection): {legacy_time:?} for 10 calls");
println!("New (with metadata cache): {new_time:?} for 10 calls");
}
#[test]
#[serial]
fn test_env_command_with_legacy_installation() {
use assert_cmd::Command;
let test_home = TestHomeGuard::new();
test_home.setup_kopi_structure();
let kopi_home = test_home.kopi_home();
create_legacy_jdk_installation(&kopi_home, "zulu", "11.0.21");
fs::write(test_home.path().join(".kopi-version"), "zulu@11.0.21").unwrap();
let mut cmd = Command::cargo_bin("kopi").unwrap();
cmd.env("KOPI_HOME", kopi_home.to_str().unwrap())
.env("HOME", test_home.path())
.current_dir(test_home.path())
.arg("env")
.assert()
.success();
}
#[test]
#[serial]
fn test_uninstall_legacy_installation() {
use assert_cmd::Command;
let test_home = TestHomeGuard::new();
test_home.setup_kopi_structure();
let kopi_home = test_home.kopi_home();
create_legacy_jdk_installation(&kopi_home, "liberica", "17.0.9");
let jdk_dir = kopi_home.join("jdks").join("liberica-17.0.9");
assert!(jdk_dir.exists(), "Legacy JDK should exist");
let mut cmd = Command::cargo_bin("kopi").unwrap();
cmd.env("KOPI_HOME", kopi_home.to_str().unwrap())
.env("HOME", test_home.path())
.arg("uninstall")
.arg("liberica@17.0.9")
.arg("--force") .assert()
.success();
assert!(!jdk_dir.exists(), "Legacy JDK should be uninstalled");
}