use super::*;
use serde_json::json;
fn installation_fixture(root: &Path, package: &str) -> PathBuf {
fs::create_dir_all(root.join("bin")).unwrap();
let executable = root.join("bin").join(BINARY);
fs::write(&executable, b"fixture, never executed").unwrap();
fs::write(
root.join(".crates.toml"),
format!("[v1]\n{package:?} = [{BINARY:?}]\n"),
)
.unwrap();
fs::write(
root.join(".crates2.json"),
json!({"installs": {package: {"bins": [BINARY], "target": "aarch64-apple-darwin"}}})
.to_string(),
)
.unwrap();
executable
}
fn official_package() -> String {
format!("magi-code {} ({REGISTRY})", env!("CARGO_PKG_VERSION"))
}
#[test]
fn newer_versions_exclude_yanked_and_prereleases_and_compare_semver() {
let response = json!({"versions": [
{"num":"0.10.0", "yanked":false},
{"num":"1.0.0-rc.1", "yanked":false},
{"num":"0.11.0", "yanked":true},
{"num":"0.9.0", "yanked":false}
]});
let body = serde_json::to_vec(&response).unwrap();
assert_eq!(
newest_stable(&body, &Version::parse("0.9.0").unwrap()).unwrap(),
Some(Version::parse("0.10.0").unwrap())
);
assert_eq!(
newest_stable(&body, &Version::parse("0.10.0").unwrap()).unwrap(),
None
);
assert!(newest_stable(b"not registry data", &Version::new(0, 1, 0)).is_err());
}
#[test]
fn custom_cargo_root_is_inferred_from_executable_not_environment() {
let temp = tempfile::tempdir().unwrap();
let root = temp.path().join("custom root with spaces");
let executable = installation_fixture(&root, &official_package());
let installation = CargoInstallation::discover(&executable).unwrap();
assert_eq!(installation.root, root.canonicalize().unwrap());
let command = cargo_install_command(&installation, &Version::new(1, 2, 3), temp.path());
let args: Vec<_> = command.get_args().collect();
let root_index = args.iter().position(|arg| *arg == "--root").unwrap();
assert_eq!(args[root_index + 1], installation.root.as_os_str());
assert!(args.contains(&std::ffi::OsStr::new("=1.2.3")));
assert!(args.contains(&std::ffi::OsStr::new("--locked")));
assert!(!args.contains(&std::ffi::OsStr::new("--no-track")));
assert_eq!(command.get_current_dir(), Some(temp.path()));
}
#[test]
fn source_installs_and_inconsistent_or_ambiguous_records_are_rejected() {
let temp = tempfile::tempdir().unwrap();
let source_package = format!(
"magi-code {} (path+file:///checkout)",
env!("CARGO_PKG_VERSION")
);
let executable = installation_fixture(temp.path(), &source_package);
assert!(CargoInstallation::discover(&executable).is_err());
installation_fixture(temp.path(), &official_package());
fs::write(temp.path().join(".crates2.json"), r#"{"installs":{}}"#).unwrap();
assert!(CargoInstallation::discover(&executable).is_err());
installation_fixture(temp.path(), &official_package());
let mut file = fs::OpenOptions::new()
.append(true)
.open(temp.path().join(".crates.toml"))
.unwrap();
use std::io::Write;
writeln!(file, "\"other 1.0.0 ({REGISTRY})\" = [{BINARY:?}]").unwrap();
assert!(CargoInstallation::discover(&executable).is_err());
}
#[test]
fn untracked_build_and_stale_running_version_are_rejected() {
let temp = tempfile::tempdir().unwrap();
let executable = installation_fixture(temp.path(), &format!("magi-code 0.0.1 ({REGISTRY})"));
assert!(CargoInstallation::discover(&executable).is_err());
fs::remove_file(temp.path().join(".crates.toml")).unwrap();
assert!(CargoInstallation::discover(&executable).is_err());
}
#[cfg(unix)]
#[test]
fn symlinked_install_records_are_not_followed() {
let temp = tempfile::tempdir().unwrap();
let executable = installation_fixture(temp.path(), &official_package());
fs::rename(
temp.path().join(".crates.toml"),
temp.path().join("elsewhere"),
)
.unwrap();
std::os::unix::fs::symlink(
temp.path().join("elsewhere"),
temp.path().join(".crates.toml"),
)
.unwrap();
assert!(CargoInstallation::discover(&executable).is_err());
}
fn completed_check(result: Result<Option<Version>>) -> JoinHandle<Result<Option<Version>>> {
let handle = thread::spawn(move || result);
let deadline = Instant::now() + Duration::from_secs(2);
while !handle.is_finished() {
assert!(Instant::now() < deadline);
thread::sleep(Duration::from_millis(1));
}
handle
}
#[test]
fn checks_notify_once_and_failed_checks_do_not_prevent_later_notification() {
let now = Instant::now();
let mut checks = UpdateChecks {
next_check: Some(now + RECHECK_INTERVAL),
..Default::default()
};
checks.worker = Some(completed_check(Err(anyhow::anyhow!("offline"))));
assert!(checks.poll(now).is_none());
assert_eq!(checks.next_check, Some(now + RECHECK_INTERVAL));
let version = Version::new(1, 2, 3);
checks.worker = Some(completed_check(Ok(Some(version.clone()))));
assert_eq!(checks.poll(now), Some(version.clone()));
checks.worker = Some(completed_check(Ok(Some(version))));
assert!(checks.poll(now).is_none());
checks.worker = Some(completed_check(Ok(Some(Version::new(1, 2, 4)))));
assert_eq!(checks.poll(now), Some(Version::new(1, 2, 4)));
}
#[test]
fn checks_start_at_startup_and_four_hours_but_never_overlap() {
let now = Instant::now();
let mut checks = UpdateChecks::default();
let (release, waiting) = crossbeam_channel::bounded(1);
assert!(
checks
.poll_with_check(now, move || {
waiting.recv().unwrap();
Ok(None)
})
.is_none()
);
assert!(checks.worker.is_some());
checks.poll_with_check(now + RECHECK_INTERVAL, || panic!("overlapping check"));
release.send(()).unwrap();
let deadline = Instant::now() + Duration::from_secs(2);
while !checks.worker.as_ref().unwrap().is_finished() {
assert!(Instant::now() < deadline);
thread::sleep(Duration::from_millis(1));
}
checks.poll_with_check(now + RECHECK_INTERVAL - Duration::from_secs(1), || {
panic!("early check")
});
assert!(checks.worker.is_none());
checks.poll_with_check(now + RECHECK_INTERVAL, || Ok(None));
assert!(checks.worker.is_some());
assert_eq!(checks.next_check, Some(now + RECHECK_INTERVAL * 2));
}
#[test]
fn restart_waits_for_last_session_writer_clone_and_never_selects_latest() {
let temp = tempfile::tempdir().unwrap();
let root = temp.path().join("sessions");
let manager = crate::sessions::SessionManager::new(root.clone());
let session = manager.create().unwrap().admit_standalone_writer().unwrap();
session
.append(&crate::sessions::SessionEvent::new_kind(
crate::sessions::SessionEventKind::Diagnostic,
session.id().to_owned(),
temp.path().to_path_buf(),
json!({"lifecycle": "created"}),
))
.unwrap();
let id = session.id().to_owned();
let worker_session = session.clone();
drop(session);
let _newer_session = manager.create().unwrap();
assert!(wait_for_session_release(&root, &id, Duration::ZERO).is_err());
drop(worker_session);
assert!(wait_for_session_release(&root, &id, Duration::ZERO).is_ok());
}