Skip to content

Commit 514be4c

Browse files
CopilotbedaHovorka
andcommitted
Apply reviewer feedback: fix resource leak, enable report types, clear modificationTracker, add setSpeed tests
Agent-Logs-Url: https://github.com/bedaHovorka/interlockSim/sessions/d9498f97-4781-4986-98c1-96cb2a57850d Co-authored-by: bedaHovorka <5263405+bedaHovorka@users.noreply.github.com>
1 parent c4f8f09 commit 514be4c

2 files changed

Lines changed: 109 additions & 1 deletion

File tree

desktop-ui/src/main/kotlin/cz/vutbr/fit/interlockSim/gui/MenuBar.kt

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,20 @@ class MenuBar : JMenuBar() {
273273
/**
274274
* Shows a file chooser, loads the selected XML as a [SimulationContext], sets it on the
275275
* [Frame] and immediately starts the simulation.
276+
*
277+
* **Resource management:** The intermediate [EditingContext] created by
278+
* [JvmEditingContextFactory.createContext] is wrapped in `use {}` to ensure its Koin
279+
* scope is closed after the [SimulationContext] transformation, preventing a resource
280+
* leak of the temporary editing context.
281+
*
282+
* **Report types:** All report types are enabled on the [SimulationContext] before
283+
* passing it to [Frame.setContext] so that [AnimationController] and
284+
* [cz.vutbr.fit.interlockSim.gui.animation.EventTimelinePanel] receive property-change
285+
* events and the animation is not visually frozen.
286+
*
287+
* **Modification tracker:** The tracker is cleared before switching to simulation mode
288+
* so that the "unsaved changes" path in [Frame.handleWindowClosing] does not attempt to
289+
* save a [SimulationContext] through the editor's save logic.
276290
*/
277291
private inner class StartSimulationAction : AbstractAction("Start...") {
278292
override fun actionPerformed(e: ActionEvent) {
@@ -285,9 +299,28 @@ class MenuBar : JMenuBar() {
285299
val selectedFile: File = fileChooser.selectedFile
286300

287301
try {
302+
val editingContextFactory = getKoin().get<JvmEditingContextFactory>()
288303
val simulationContextFactory = getKoin().get<SimulationContextFactory>()
289-
val simContext = simulationContextFactory.createContext(selectedFile) as SimulationContext
304+
305+
// Wrap intermediate EditingContext in use{} to close its Koin scope after
306+
// transformation, avoiding a resource leak.
307+
val simContext =
308+
editingContextFactory.createContext(selectedFile).use { editCtx ->
309+
simulationContextFactory.createContext(editCtx as EditingContext)
310+
}
311+
312+
// Enable all report types so AnimationController and EventTimelinePanel receive
313+
// property-change events (without this the animation stays visually frozen).
314+
simContext.addReportTypes(*SimulationContext.ReportType.values())
315+
290316
val frame = getKoin().get<Frame>()
317+
318+
// Clear dirty flag before switching to simulation mode. If the user had unsaved
319+
// edits, continuing in simulation mode implicitly discards them; the window-close
320+
// handler must not try to save a non-existent EditingContext afterwards.
321+
frame.modificationTracker.markClean()
322+
frame.modificationTracker.setCurrentFile(null)
323+
291324
frame.setContext(simContext)
292325
frame.startSimulation()
293326
} catch (exception: Exception) {

desktop-ui/src/test/kotlin/cz/vutbr/fit/interlockSim/gui/SimulationControllerTest.kt

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,81 @@ class SimulationControllerTest {
443443
controller.stop()
444444
}
445445

446+
// ── setSpeed ──────────────────────────────────────────────────────────────
447+
448+
@Test
449+
@Timeout(value = 5, unit = TimeUnit.SECONDS)
450+
@DisplayName("setSpeed rejects value below MIN_SPEED")
451+
fun setSpeedBelowMinThrows() {
452+
val controller = SimulationController(controlPanel)
453+
org.junit.jupiter.api.assertThrows<IllegalArgumentException> {
454+
controller.setSpeed(SimulationRunner.MIN_SPEED - 0.001)
455+
}
456+
}
457+
458+
@Test
459+
@Timeout(value = 5, unit = TimeUnit.SECONDS)
460+
@DisplayName("setSpeed rejects value above MAX_SPEED")
461+
fun setSpeedAboveMaxThrows() {
462+
val controller = SimulationController(controlPanel)
463+
org.junit.jupiter.api.assertThrows<IllegalArgumentException> {
464+
controller.setSpeed(SimulationRunner.MAX_SPEED + 0.001)
465+
}
466+
}
467+
468+
@Test
469+
@Timeout(value = 5, unit = TimeUnit.SECONDS)
470+
@DisplayName("setSpeed accepts boundary values MIN_SPEED and MAX_SPEED")
471+
fun setSpeedAcceptsBoundaryValues() {
472+
val controller = SimulationController(controlPanel)
473+
controller.setSpeed(SimulationRunner.MIN_SPEED)
474+
controller.setSpeed(SimulationRunner.MAX_SPEED)
475+
}
476+
477+
@Test
478+
@Timeout(value = 10, unit = TimeUnit.SECONDS)
479+
@DisplayName("setSpeed applies immediately to an active runner")
480+
fun setSpeedAppliedImmediatelyToActiveRunner() {
481+
val started = CountDownLatch(1)
482+
val blockSim = CountDownLatch(1)
483+
every { context.run() } answers {
484+
started.countDown()
485+
blockSim.await(10, TimeUnit.SECONDS)
486+
}
487+
488+
val controller = SimulationController(controlPanel)
489+
controller.start(context)
490+
assertThat(started.await(5, TimeUnit.SECONDS)).isTrue()
491+
492+
controller.setSpeed(2.0)
493+
assertThat(controller.runner!!.speedMultiplier).isEqualTo(2.0)
494+
495+
blockSim.countDown()
496+
controller.stop()
497+
}
498+
499+
@Test
500+
@Timeout(value = 10, unit = TimeUnit.SECONDS)
501+
@DisplayName("setSpeed before start is carried into the next start()")
502+
fun setSpeedCarriedIntoNextStart() {
503+
val started = CountDownLatch(1)
504+
val blockSim = CountDownLatch(1)
505+
every { context.run() } answers {
506+
started.countDown()
507+
blockSim.await(10, TimeUnit.SECONDS)
508+
}
509+
510+
val controller = SimulationController(controlPanel)
511+
controller.setSpeed(0.5) // pre-select before start
512+
controller.start(context)
513+
assertThat(started.await(5, TimeUnit.SECONDS)).isTrue()
514+
515+
assertThat(controller.runner!!.speedMultiplier).isEqualTo(0.5)
516+
517+
blockSim.countDown()
518+
controller.stop()
519+
}
520+
446521
// ── helpers ───────────────────────────────────────────────────────────────
447522

448523
private fun findStopButton(): JButton? =

0 commit comments

Comments
 (0)