Background
App.acquireInstanceLock() uses NIO FileLock to prevent multiple GUI instances from running simultaneously. When a second instance is detected, it calls System.exit(0) inside a SwingUtilities.invokeLater lambda:
SwingUtilities.invokeLater(() -> {
JOptionPane.showMessageDialog(...);
System.exit(0); // ← terminates the test JVM if called during tests
});
This is correct production behavior, but it creates a test hazard: any future test that exercises the GUI startup path and encounters an already-held lock will terminate the entire Maven Surefire JVM rather than failing the individual test cleanly.
Problem
System.exit in test JVM — if a test triggers the "already running" branch, the whole test suite crashes with a cryptic Surefire failure instead of a clean test failure.
- No coverage of the lock lifecycle — there are no tests verifying: first call acquires lock →
true; second call finds it held → false; lock releases on channel close; next startup succeeds.
Proposed fix
Extract the exit and dialog side-effects behind an injectable strategy (e.g. a Runnable or BiConsumer parameter defaulting to the real System.exit + JOptionPane call). This makes acquireInstanceLock() unit-testable by passing a no-op or capture lambda in tests.
// Example seam
static boolean acquireInstanceLock(Runnable onAlreadyRunning) { ... }
// Production call (unchanged behavior)
acquireInstanceLock(() -> {
SwingUtilities.invokeLater(() -> {
JOptionPane.showMessageDialog(...);
System.exit(0);
});
});
Acceptance criteria
acquireInstanceLock() does not call System.exit directly — exit/dialog behavior is injectable
- Unit test: first call acquires lock, returns
true
- Unit test: second call with lock held returns
false and invokes the onAlreadyRunning callback
- Unit test: releasing the channel allows a subsequent call to succeed
- Existing GUI startup behavior unchanged
References
- Flagged in PR code review (Copilot, medium severity) on icon-base branch, App.java lines 312–314
Background
App.acquireInstanceLock() uses NIO FileLock to prevent multiple GUI instances from running simultaneously. When a second instance is detected, it calls
System.exit(0)inside aSwingUtilities.invokeLaterlambda:This is correct production behavior, but it creates a test hazard: any future test that exercises the GUI startup path and encounters an already-held lock will terminate the entire Maven Surefire JVM rather than failing the individual test cleanly.
Problem
System.exitin test JVM — if a test triggers the "already running" branch, the whole test suite crashes with a cryptic Surefire failure instead of a clean test failure.true; second call finds it held →false; lock releases on channel close; next startup succeeds.Proposed fix
Extract the exit and dialog side-effects behind an injectable strategy (e.g. a
RunnableorBiConsumerparameter defaulting to the realSystem.exit+JOptionPanecall). This makesacquireInstanceLock()unit-testable by passing a no-op or capture lambda in tests.Acceptance criteria
acquireInstanceLock()does not callSystem.exitdirectly — exit/dialog behavior is injectabletruefalseand invokes theonAlreadyRunningcallbackReferences