summaryrefslogtreecommitdiff
path: root/src/test/java/core/VSTaskManagerCrashRecoveryIntegrationTest.java
blob: ce1346c665b590afc3909596a70883632ce4322e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
package core;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.LinkedList;
import java.util.PriorityQueue;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import events.implementations.VSProcessCrashEvent;
import events.implementations.VSProcessRecoverEvent;
import prefs.VSSerializablePrefs;
import serialize.VSSerialize;
import simulator.VSSimulator;
import simulator.VSSimulatorVisualization;
import simulator.builder.SimulationBuilder;
import testing.HeadlessLoader;

class VSTaskManagerCrashRecoveryIntegrationTest {
    private static final long ADVANCE_STEP_MS = 1L;
    private static final String RAFT_REPLAY = "saved-simulations/raft.dat";

    private VSSimulator simulatorToStop;
    private VSSimulator loadedSimulatorToStop;

    @AfterEach
    void tearDown() {
        stopSimulatorThread(simulatorToStop);
        stopSimulatorThread(loadedSimulatorToStop);
    }

    @Test
    @DisplayName("Runtime supports recover then later crash of a different process")
    void runtimeSupportsRecoverThenCrashAnotherProcess() throws Exception {
        VSSimulator simulator = new SimulationBuilder()
            .withProcesses(3)
            .withDuration(1000)
            .getSimulator();
        simulatorToStop = simulator;

        VSSimulatorVisualization visualization = simulator.getSimulatorCanvas();
        VSInternalProcess process0 = visualization.getProcess(0);
        VSInternalProcess process1 = visualization.getProcess(1);

        VSTaskManager taskManager = visualization.getTaskManager();
        taskManager.addTask(new VSTask(5, process0, new VSProcessCrashEvent(), VSTask.GLOBAL));
        taskManager.addTask(new VSTask(10, process0, new VSProcessRecoverEvent(), VSTask.GLOBAL));
        taskManager.addTask(new VSTask(15, process1, new VSProcessCrashEvent(), VSTask.GLOBAL));

        runUntil(visualization, 20);

        assertFalse(process0.isCrashed(), "process 0 should have recovered");
        assertTrue(process1.isCrashed(), "process 1 should crash after process 0 recovers");
    }

    @Test
    @DisplayName("Saved replay preserves recover then later crash of another process")
    void savedReplayPreservesRecoverThenCrashAnotherProcess() throws Exception {
        Path datFile = Files.createTempFile("crash-recover-replay", ".dat");

        VSSimulator builtSimulator = new SimulationBuilder()
            .withProcesses(3)
            .withDuration(1000)
            .addCrashEvent(0, 5)
            .addRecoveryEvent(0, 10)
            .addCrashEvent(1, 15)
            .save(datFile.toString())
            .getSimulator();
        simulatorToStop = builtSimulator;

        HeadlessLoader.LoadedSimulation loaded =
            HeadlessLoader.load(datFile.toString(), builtSimulator.getPrefs());
        loadedSimulatorToStop = loaded.getSimulator();

        VSSimulatorVisualization visualization = loaded.getVisualization();
        assertFalse(visualization.getProcess(0).isCrashed(),
                    "process 0 should start alive after replay load");
        assertFalse(visualization.getProcess(1).isCrashed(),
                    "process 1 should start alive after replay load");

        runUntil(visualization, 4);
        assertFalse(visualization.getProcess(0).isCrashed(),
                    "process 0 should stay alive before its crash point");
        assertFalse(visualization.getProcess(1).isCrashed(),
                    "process 1 should stay alive before process 0 crashes");

        runUntil(visualization, 6);
        assertTrue(visualization.getProcess(0).isCrashed(),
                   "process 0 should crash immediately after its scheduled crash point");
        assertFalse(visualization.getProcess(1).isCrashed(),
                    "process 1 should still be alive while process 0 is crashed");

        runUntil(visualization, 10);
        assertTrue(visualization.getProcess(0).isCrashed(),
                   "process 0 should remain crashed before its recover point");
        assertFalse(visualization.getProcess(1).isCrashed(),
                    "process 1 should still be alive before its later crash");

        runUntil(visualization, 11);
        assertFalse(visualization.getProcess(0).isCrashed(),
                    "process 0 should recover immediately after its scheduled recover point");
        assertFalse(visualization.getProcess(1).isCrashed(),
                    "process 1 should still be alive before its crash point");

        runUntil(visualization, 15);
        assertFalse(visualization.getProcess(0).isCrashed(),
                    "process 0 should stay recovered before process 1 crashes");
        assertFalse(visualization.getProcess(1).isCrashed(),
                    "process 1 should stay alive before its later crash point");

        runUntil(visualization, 16);
        assertFalse(visualization.getProcess(0).isCrashed(),
                    "process 0 should remain recovered after replay load");
        assertTrue(visualization.getProcess(1).isCrashed(),
                   "process 1 should crash immediately after its later replay point");
    }

    @Test
    @DisplayName("Loaded raft replay keeps crash and recover events visible in Event view collections")
    void loadedRaftReplayKeepsCrashAndRecoverEventsVisible() throws Exception {
        HeadlessLoader.LoadedSimulation loaded =
            HeadlessLoader.load(RAFT_REPLAY, prefs.VSDefaultPrefs.init());
        loadedSimulatorToStop = loaded.getSimulator();

        VSSimulatorVisualization visualization = loaded.getVisualization();
        VSTaskManager taskManager = visualization.getTaskManager();
        VSInternalProcess process0 = visualization.getProcess(0);
        VSInternalProcess process2 = visualization.getProcess(2);

        assertEquals(6, taskManager.getGlobalTasks().size(),
                     "loaded raft replay should expose all saved global tasks");
        assertEquals(3, taskManager.getProcessGlobalTasks(process0).size(),
                     "process 0 should expose its activation, crash, and recover events");
        assertEquals(2, taskManager.getProcessGlobalTasks(process2).size(),
                     "process 2 should expose its activation and later crash event");

        runUntil(visualization, 12001);
        assertEquals(3, taskManager.getProcessGlobalTasks(process0).size(),
                     "process 0 recover event should remain visible after it executes");

        runUntil(visualization, 20001);
        assertEquals(2, taskManager.getProcessGlobalTasks(process2).size(),
                     "process 2 later crash event should remain visible after it executes");
    }

    @Test
    @DisplayName("Loading preserves programmed replay tasks but not ordinary runtime tasks")
    void loadingPreservesProgrammedReplayTasksWithoutPromotingRuntimeTasks()
    throws Exception {
        Path replayFile = Files.createTempFile("programmed-replay", ".dat");
        Path runtimeFile = Files.createTempFile("runtime-task", ".dat");

        VSSimulator replaySimulator = new SimulationBuilder()
            .withProcesses(3)
            .withDuration(1000)
            .addCrashEvent(0, 5)
            .save(replayFile.toString())
            .getSimulator();
        simulatorToStop = replaySimulator;

        HeadlessLoader.LoadedSimulation replayLoaded =
            HeadlessLoader.load(replayFile.toString(), replaySimulator.getPrefs());
        loadedSimulatorToStop = replayLoaded.getSimulator();

        VSTaskManager replayTaskManager = replayLoaded.getVisualization().getTaskManager();
        assertEquals(1, replayTaskManager.getProcessGlobalTasks(
                         replayLoaded.getVisualization().getProcess(0)).size(),
                     "builder-authored replay task should remain visible after load");

        loadedSimulatorToStop.getSimulatorCanvas().stopThread();
        loadedSimulatorToStop = null;

        VSSimulator runtimeSimulator = new SimulationBuilder()
            .withProcesses(3)
            .withDuration(1000)
            .getSimulator();
        simulatorToStop = runtimeSimulator;

        VSSimulatorVisualization runtimeVisualization = runtimeSimulator.getSimulatorCanvas();
        VSInternalProcess runtimeProcess = runtimeVisualization.getProcess(0);
        VSTask runtimeTask = new VSTask(5, runtimeProcess, new VSProcessCrashEvent(), VSTask.GLOBAL);
        runtimeVisualization.getTaskManager().addTask(runtimeTask);
        assertFalse(runtimeTask.isProgrammed(), "runtime task should remain non-programmed before save");

        saveSimulation(runtimeFile, runtimeSimulator);

        HeadlessLoader.LoadedSimulation runtimeLoaded =
            HeadlessLoader.load(runtimeFile.toString(), runtimeSimulator.getPrefs());
        loadedSimulatorToStop = runtimeLoaded.getSimulator();

        VSSimulatorVisualization loadedRuntimeVisualization = runtimeLoaded.getVisualization();
        VSTaskManager runtimeTaskManager = runtimeLoaded.getVisualization().getTaskManager();
        assertEquals(1, getPendingGlobalTaskCount(runtimeTaskManager),
                     "ordinary non-programmed runtime task should still be restored after load");
        assertEquals(0, runtimeTaskManager.getProcessGlobalTasks(
                         loadedRuntimeVisualization.getProcess(0)).size(),
                     "ordinary non-programmed runtime task must stay hidden after load");

        assertFalse(loadedRuntimeVisualization.getProcess(0).isCrashed(),
                    "restored runtime task should not execute before its scheduled time");
        runUntil(loadedRuntimeVisualization, 6);
        assertTrue(loadedRuntimeVisualization.getProcess(0).isCrashed(),
                   "restored runtime task should still execute after load");
        assertEquals(0, getPendingGlobalTaskCount(runtimeTaskManager),
                     "runtime task should leave the pending queue after execution");
        assertEquals(0, getFulfilledProgrammedTaskCount(runtimeTaskManager),
                     "executed runtime task must not become a programmed Event-view entry");
        assertEquals(0, runtimeTaskManager.getProcessGlobalTasks(
                         loadedRuntimeVisualization.getProcess(0)).size(),
                     "executed runtime task must remain hidden from the Event view");
    }

    @Test
    @DisplayName("Live GUI-style event injection supports recover and later crash")
    void liveEventInjectionSupportsRecoverAndLaterCrash() throws Exception {
        VSSimulator simulator = new SimulationBuilder()
            .withProcesses(3)
            .withDuration(1000)
            .getSimulator();
        simulatorToStop = simulator;

        VSSimulatorVisualization visualization = simulator.getSimulatorCanvas();
        VSInternalProcess process0 = visualization.getProcess(0);
        VSInternalProcess process1 = visualization.getProcess(1);
        VSTaskManager taskManager = visualization.getTaskManager();

        runUntil(visualization, 5);
        taskManager.addTask(new VSTask(process0.getGlobalTime(), process0,
                                       new VSProcessCrashEvent(), VSTask.GLOBAL));
        runUntil(visualization, 6);
        assertTrue(process0.isCrashed(), "process 0 should crash from live event");

        taskManager.addTask(new VSTask(process0.getGlobalTime(), process0,
                                       new VSProcessRecoverEvent(), VSTask.GLOBAL));
        runUntil(visualization, 7);
        assertFalse(process0.isCrashed(), "process 0 should recover from live event");

        taskManager.addTask(new VSTask(process1.getGlobalTime(), process1,
                                       new VSProcessCrashEvent(), VSTask.GLOBAL));
        runUntil(visualization, 8);
        assertTrue(process1.isCrashed(), "process 1 should crash after process 0 recovers");
    }

    private void runUntil(VSSimulatorVisualization visualization, long targetTime)
    throws Exception {
        setBooleanField(visualization, "isPaused", false);
        setBooleanField(visualization, "hasFinished", false);
        setDoubleField(visualization, "clockSpeed", 1.0d);
        Method updateSimulator = VSSimulatorVisualization.class.getDeclaredMethod(
            "updateSimulator", long.class, long.class);
        updateSimulator.setAccessible(true);

        long wallTime = visualization.getTime();
        while (visualization.getTime() < targetTime) {
            long nextWallTime = wallTime + ADVANCE_STEP_MS;
            updateSimulator.invoke(visualization, nextWallTime, wallTime);
            wallTime = nextWallTime;
        }
    }

    private void setBooleanField(Object target, String fieldName, boolean value)
    throws Exception {
        Field field = target.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.setBoolean(target, value);
    }

    private void setDoubleField(Object target, String fieldName, double value)
    throws Exception {
        Field field = target.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.setDouble(target, value);
    }

    private void stopSimulatorThread(VSSimulator simulator) {
        if (simulator != null) {
            simulator.getSimulatorCanvas().stopThread();
        }
    }

    @SuppressWarnings("unchecked")
    private int getPendingGlobalTaskCount(VSTaskManager taskManager) throws Exception {
        Field field = VSTaskManager.class.getDeclaredField("globalTasks");
        field.setAccessible(true);
        return ((PriorityQueue<VSTask>) field.get(taskManager)).size();
    }

    @SuppressWarnings("unchecked")
    private int getFulfilledProgrammedTaskCount(VSTaskManager taskManager) throws Exception {
        Field field = VSTaskManager.class.getDeclaredField("fullfilledProgrammedTasks");
        field.setAccessible(true);
        return ((LinkedList<VSTask>) field.get(taskManager)).size();
    }

    private void saveSimulation(Path file, VSSimulator simulator) throws Exception {
        VSSerialize serialize = new VSSerialize();
        try (FileOutputStream fos = new FileOutputStream(file.toFile());
             ObjectOutputStream oos = new ObjectOutputStream(fos)) {
            VSSerializablePrefs serializablePrefs = new VSSerializablePrefs();

            for (String key : simulator.getPrefs().getIntegerKeySet()) {
                serializablePrefs.initInteger(key, simulator.getPrefs().getInteger(key));
            }
            for (String key : simulator.getPrefs().getBooleanKeySet()) {
                serializablePrefs.initBoolean(key, simulator.getPrefs().getBoolean(key));
            }
            for (String key : simulator.getPrefs().getStringKeySet()) {
                serializablePrefs.initString(key, simulator.getPrefs().getString(key));
            }
            for (String key : simulator.getPrefs().getFloatKeySet()) {
                serializablePrefs.initFloat(key, simulator.getPrefs().getFloat(key));
            }
            for (String key : simulator.getPrefs().getColorKeySet()) {
                serializablePrefs.initColor(key, simulator.getPrefs().getColor(key));
            }
            for (String key : simulator.getPrefs().getVectorKeySet()) {
                serializablePrefs.initVector(key, simulator.getPrefs().getVector(key));
            }
            for (String key : simulator.getPrefs().getLongKeySet()) {
                serializablePrefs.initLong(key, simulator.getPrefs().getLong(key));
            }

            serializablePrefs.serialize(serialize, oos);
            simulator.serialize(serialize, oos);
        }
    }
}