use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, Mutex, MutexGuard};
use rightkit_daemon::Suite;
use rightkit_tauri_shell::{ClaimOutcome, InstanceMessage, SingleInstance};
const ID: &str = "test-shell-destroy";
static SERIAL: Mutex<()> = Mutex::new(());
fn serial() -> MutexGuard<'static, ()> {
SERIAL.lock().unwrap_or_else(|e| e.into_inner())
}
fn claim(suite: &Suite) -> ClaimOutcome {
SingleInstance::claim_in(suite, ID, InstanceMessage::default(), |_| {})
}
fn settled_primary(suite: &Suite) -> ClaimOutcome {
let first = claim(suite);
assert!(first.is_primary(), "the first claim must be the primary");
assert!(
matches!(claim(suite), ClaimOutcome::Forwarded),
"the primary must be serving before it is released"
);
first
}
#[test]
fn destroy_releases_the_claim_so_a_relaunch_can_become_primary() {
let _serial = serial();
let suite = Suite::per_run_temp("shell-single-instance-destroy").unwrap();
let first = claim(&suite);
assert!(first.is_primary());
let second = claim(&suite);
assert!(!second.is_primary());
SingleInstance::destroy();
assert!(claim(&suite).is_primary());
drop(first);
SingleInstance::destroy();
}
#[test]
fn destroy_is_idempotent_and_a_guard_dropped_afterwards_is_harmless() {
let _serial = serial();
let suite = Suite::per_run_temp("shell-single-instance-idem").unwrap();
SingleInstance::destroy();
SingleInstance::destroy();
let guard = match settled_primary(&suite) {
ClaimOutcome::Primary(guard) => guard,
_ => unreachable!(),
};
SingleInstance::destroy();
SingleInstance::destroy();
let relaunched = match claim(&suite) {
ClaimOutcome::Primary(guard) => guard,
_ => panic!("the claim after destroy must be the primary"),
};
drop(guard);
assert!(
!claim(&suite).is_primary(),
"dropping a guard that destroy already released must not release the new claim"
);
drop(relaunched);
SingleInstance::destroy();
}
#[test]
fn destroy_releases_every_claim_in_every_suite() {
let _serial = serial();
let a = Suite::per_run_temp("shell-single-instance-a").unwrap();
let b = Suite::per_run_temp("shell-single-instance-b").unwrap();
let ga = claim(&a);
let gb = claim(&b);
assert!(ga.is_primary() && gb.is_primary());
SingleInstance::destroy();
assert!(claim(&a).is_primary());
assert!(claim(&b).is_primary());
drop((ga, gb));
SingleInstance::destroy();
}
#[test]
fn a_registered_legacy_release_runs_once_per_destroy_and_before_the_claims_go() {
let _serial = serial();
let suite = Suite::per_run_temp("shell-single-instance-hook").unwrap();
let ran = Arc::new(AtomicUsize::new(0));
let guard = settled_primary(&suite);
let counter = ran.clone();
SingleInstance::on_destroy(move || {
counter.fetch_add(1, Ordering::SeqCst);
});
assert_eq!(
ran.load(Ordering::SeqCst),
0,
"registering must not run the release"
);
SingleInstance::destroy();
assert_eq!(ran.load(Ordering::SeqCst), 1);
SingleInstance::destroy();
assert_eq!(
ran.load(Ordering::SeqCst),
1,
"a release runs once, not on every destroy"
);
assert!(claim(&suite).is_primary());
drop(guard);
SingleInstance::destroy();
}
#[test]
fn a_panicking_legacy_release_does_not_stop_the_other_releases() {
let _serial = serial();
let suite = Suite::per_run_temp("shell-single-instance-panic").unwrap();
let ran = Arc::new(AtomicUsize::new(0));
let guard = settled_primary(&suite);
SingleInstance::on_destroy(|| panic!("legacy release failed"));
let counter = ran.clone();
SingleInstance::on_destroy(move || {
counter.fetch_add(1, Ordering::SeqCst);
});
SingleInstance::destroy();
assert_eq!(ran.load(Ordering::SeqCst), 1);
assert!(
claim(&suite).is_primary(),
"the claim is released even though a hook panicked"
);
drop(guard);
SingleInstance::destroy();
}
#[cfg(windows)]
#[test]
fn after_destroy_with_the_plugin_release_a_claim_with_the_legacy_identifier_succeeds() {
use rightkit_tauri_shell::{ClaimFailure, ClaimOptions, LegacySingleInstanceNames};
#[link(name = "kernel32")]
extern "system" {
fn CreateMutexW(attrs: *const u8, initial_owner: i32, name: *const u16) -> isize;
fn CloseHandle(handle: isize) -> i32;
}
let _serial = serial();
let identifier = format!("rk.legacy-destroy-{}", std::process::id());
let mutex_name = LegacySingleInstanceNames::for_identifier(&identifier).windows_mutex;
let wide: Vec<u16> = mutex_name
.encode_utf16()
.chain(std::iter::once(0))
.collect();
let handle = unsafe { CreateMutexW(std::ptr::null(), 1, wide.as_ptr()) };
assert_ne!(handle, 0, "could not create the stand-in legacy mutex");
let suite = Suite::per_run_temp("shell-legacy-destroy").unwrap();
let options = || ClaimOptions::with_legacy(&identifier);
let held =
SingleInstance::claim_in_with(&suite, ID, options(), InstanceMessage::default(), |_| {});
assert!(
matches!(held, ClaimOutcome::Failed(ClaimFailure::Legacy(_))),
"a live legacy mutex must refuse the claim, not let it become a second primary"
);
let raw = handle;
SingleInstance::on_destroy(move || {
unsafe { CloseHandle(raw) };
});
SingleInstance::destroy();
let after =
SingleInstance::claim_in_with(&suite, ID, options(), InstanceMessage::default(), |_| {});
assert!(
after.is_primary(),
"after destroy the legacy identifier must no longer block the claim"
);
drop(after);
SingleInstance::destroy();
}