1 package trafficcontrol;
2
3 import static org.junit.jupiter.api.Assertions.assertEquals;
4 import static org.junit.jupiter.api.Assertions.assertTrue;
5 import static org.junit.jupiter.api.Assertions.fail;
6
7 import java.io.ByteArrayOutputStream;
8 import java.io.PrintStream;
9 import java.util.LinkedHashMap;
10 import java.util.LinkedHashSet;
11 import java.util.Map;
12 import java.util.Set;
13
14 import javax.naming.NamingException;
15
16 import org.djunits.value.vdouble.scalar.Duration;
17 import org.djutils.immutablecollections.ImmutableSet;
18 import org.mockito.ArgumentMatchers;
19 import org.mockito.Mockito;
20 import org.mockito.invocation.InvocationOnMock;
21 import org.mockito.stubbing.Answer;
22 import org.opentrafficsim.core.dsol.OtsModelInterface;
23 import org.opentrafficsim.core.dsol.OtsSimulator;
24 import org.opentrafficsim.core.dsol.OtsSimulatorInterface;
25 import org.opentrafficsim.core.network.Network;
26 import org.opentrafficsim.core.network.NetworkException;
27 import org.opentrafficsim.core.perception.HistoryManagerDevs;
28 import org.opentrafficsim.road.network.object.trafficlight.TrafficLight;
29 import org.opentrafficsim.road.network.object.trafficlight.TrafficLightColor;
30 import org.opentrafficsim.trafficcontrol.FixedTimeController;
31 import org.opentrafficsim.trafficcontrol.FixedTimeController.SignalGroup;
32
33 import nl.tudelft.simulation.dsol.SimRuntimeException;
34
35
36
37
38
39
40
41
42
43
44
45 public final class TestFixedTimeController
46 {
47
48
49 private TestFixedTimeController()
50 {
51
52 }
53
54
55
56
57
58
59
60
61 public void testConstructors() throws SimRuntimeException, NamingException, NetworkException
62 {
63 String signalGroupId = "sgId";
64 Set<String> trafficLightIds = new LinkedHashSet<>();
65 String trafficLightId = "08.1";
66 trafficLightIds.add(trafficLightId);
67 Duration signalGroupOffset = Duration.ofSI(5);
68 Duration preGreen = Duration.ofSI(2);
69 Duration green = Duration.ofSI(10);
70 Duration yellow = Duration.ofSI(3.5);
71 try
72 {
73 new SignalGroup(null, trafficLightIds, signalGroupOffset, preGreen, green, yellow);
74 fail("Null pointer for signalGroupId should have thrown a null pointer exception");
75 }
76 catch (NullPointerException npe)
77 {
78
79 }
80 try
81 {
82 new SignalGroup(signalGroupId, null, signalGroupOffset, preGreen, green, yellow);
83 fail("Null pointer for trafficLightIds should have thrown a null pointer exception");
84 }
85 catch (NullPointerException npe)
86 {
87
88 }
89 try
90 {
91 new SignalGroup(signalGroupId, trafficLightIds, null, preGreen, green, yellow);
92 fail("Null pointer for signalGroupOffset should have thrown a null pointer exception");
93 }
94 catch (NullPointerException npe)
95 {
96
97 }
98 try
99 {
100 new SignalGroup(signalGroupId, trafficLightIds, signalGroupOffset, null, green, yellow);
101 fail("Null pointer for preGreen should have thrown a null pointer exception");
102 }
103 catch (NullPointerException npe)
104 {
105
106 }
107 try
108 {
109 new SignalGroup(signalGroupId, trafficLightIds, signalGroupOffset, preGreen, null, yellow);
110 fail("Null pointer for green should have thrown a null pointer exception");
111 }
112 catch (NullPointerException npe)
113 {
114
115 }
116 try
117 {
118 new SignalGroup(signalGroupId, trafficLightIds, signalGroupOffset, preGreen, green, null);
119 fail("Null pointer for yellow should have thrown a null pointer exception");
120 }
121 catch (NullPointerException npe)
122 {
123
124 }
125 try
126 {
127 new SignalGroup(signalGroupId, new LinkedHashSet<String>(), signalGroupOffset, preGreen, green, yellow);
128 fail("Empty list of traffic light ids should have thrown an illegal argument exception");
129 }
130 catch (IllegalArgumentException iae)
131 {
132
133 }
134
135 SignalGroup sg = new SignalGroup(signalGroupId, trafficLightIds, signalGroupOffset, green, yellow);
136 assertEquals(0, sg.getPreGreen().si, 0, "default for pre green");
137 assertEquals(green.si, sg.getGreen().si, 0, "green");
138 assertEquals(yellow.si, sg.getYellow().si, 0, "yellow");
139
140 sg = new SignalGroup(signalGroupId, trafficLightIds, signalGroupOffset, preGreen, green, yellow);
141 assertEquals(signalGroupId, sg.getId(), "group id");
142 assertTrue(sg.toString().startsWith("SignalGroup ["), "toString returns something descriptive");
143
144 String ftcId = "FTCid";
145 OtsSimulatorInterface simulator = new OtsSimulator("TestFixedTimeController");
146 simulator.initialize(Duration.ZERO, Duration.ZERO, Duration.ofSI(3600), createModelMock(),
147 HistoryManagerDevs.noHistory(simulator));
148 Map<String, TrafficLight> trafficLightMap = new LinkedHashMap<String, TrafficLight>();
149 String networkId = "networkID";
150 trafficLightMap.put(trafficLightId, createTrafficLightMock(trafficLightId, networkId, simulator));
151 Network network = new Network(networkId, simulator);
152 network.addObject(trafficLightMap.get(trafficLightId));
153
154 Duration cycleTime = Duration.ofSI(90);
155 Duration offset = Duration.ofSI(20);
156 Set<SignalGroup> signalGroups = new LinkedHashSet<>();
157 ImmutableSet<String> ids = sg.getTrafficLightIds();
158 for (String tlId : ids)
159 {
160 assertTrue(trafficLightMap.containsKey(tlId), "returned id is in provided set");
161 }
162 for (String tlId : trafficLightMap.keySet())
163 {
164 assertTrue(ids.contains(tlId), "provided id is returned");
165 }
166 signalGroups.add(sg);
167 try
168 {
169 new FixedTimeController(null, simulator, network, cycleTime, offset, signalGroups);
170 fail("Null pointer for controller id should have thrown an exception");
171 }
172 catch (NullPointerException npe)
173 {
174
175 }
176 try
177 {
178 new FixedTimeController(ftcId, null, network, cycleTime, offset, signalGroups);
179 fail("Null pointer for simulator should have thrown an exception");
180 }
181 catch (NullPointerException npe)
182 {
183
184 }
185 try
186 {
187 new FixedTimeController(ftcId, simulator, null, cycleTime, offset, signalGroups);
188 fail("Null pointer for network should have thrown an exception");
189 }
190 catch (NullPointerException npe)
191 {
192
193 }
194 try
195 {
196 new FixedTimeController(ftcId, simulator, network, null, offset, signalGroups);
197 fail("Null pointer for cycle time should have thrown an exception");
198 }
199 catch (NullPointerException npe)
200 {
201
202 }
203 try
204 {
205 new FixedTimeController(ftcId, simulator, network, cycleTime, null, signalGroups);
206 fail("Null pointer for offset should have thrown an exception");
207 }
208 catch (NullPointerException npe)
209 {
210
211 }
212 try
213 {
214 new FixedTimeController(ftcId, simulator, network, cycleTime, offset, null);
215 fail("Null pointer for signal groups should have thrown an exception");
216 }
217 catch (NullPointerException npe)
218 {
219
220 }
221 try
222 {
223 new FixedTimeController(ftcId, simulator, network, cycleTime, offset, new LinkedHashSet<SignalGroup>());
224 fail("Empty signal groups should have thrown an exception");
225 }
226 catch (IllegalArgumentException iae)
227 {
228
229 }
230 try
231 {
232 new FixedTimeController(ftcId, simulator, network, Duration.ofSI(0), offset, signalGroups);
233 fail("Illegal cycle time should hav thrown an exception");
234 }
235 catch (IllegalArgumentException iae)
236 {
237
238 }
239 try
240 {
241 new FixedTimeController(ftcId, simulator, network, Duration.ofSI(-10), offset, signalGroups);
242 fail("Illegal cycle time should hav thrown an exception");
243 }
244 catch (IllegalArgumentException iae)
245 {
246
247 }
248
249
250 FixedTimeController ftc = new FixedTimeController(ftcId, simulator, network, cycleTime, offset, signalGroups);
251 assertEquals(ftcId, ftc.getId(), "FTC id");
252 assertTrue(ftc.toString().startsWith("FixedTimeController ["), "toString returns something descriptive");
253
254 simulator.runUpTo(Duration.ONE);
255 while (simulator.isStartingOrRunning())
256 {
257 try
258 {
259 Thread.sleep(100);
260 }
261 catch (InterruptedException exception)
262 {
263 exception.printStackTrace();
264 }
265 }
266 for (TrafficLight tl : sg.getTrafficLights())
267 {
268 assertTrue(trafficLightMap.containsKey(tl.getId()), "acquired traffic light is in the proved set");
269 }
270 assertEquals(cycleTime.minus(preGreen).minus(green).minus(yellow).si, sg.getRed().si, 0.0001,
271 "red time makes up remainder of cycle time");
272 }
273
274
275
276
277
278
279
280
281 public void testDisjoint() throws SimRuntimeException, NamingException, NetworkException
282 {
283 String signalGroupId = "sgId1";
284 Set<String> trafficLightIds1 = new LinkedHashSet<>();
285 String trafficLightId = "08.1";
286 trafficLightIds1.add(trafficLightId);
287 Duration signalGroupOffset = Duration.ofSI(5);
288 Duration preGreen = Duration.ofSI(2);
289 Duration green = Duration.ofSI(10);
290 Duration yellow = Duration.ofSI(3.5);
291 SignalGroup sg1 = new SignalGroup(signalGroupId, trafficLightIds1, signalGroupOffset, preGreen, green, yellow);
292 String signalGroupId2 = "sgId2";
293 Set<String> trafficLightIds2 = new LinkedHashSet<>();
294 trafficLightIds2.add(trafficLightId);
295 SignalGroup sg2 = new SignalGroup(signalGroupId2, trafficLightIds2, signalGroupOffset, preGreen, green, yellow);
296
297 String ftcId = "FTCid";
298 OtsSimulatorInterface simulator = new OtsSimulator("TestFixedTimeController");
299 simulator.initialize(Duration.ZERO, Duration.ZERO, Duration.ofSI(3600), createModelMock(),
300 HistoryManagerDevs.noHistory(simulator));
301 Map<String, TrafficLight> trafficLightMap = new LinkedHashMap<String, TrafficLight>();
302 String networkId = "networkID";
303 trafficLightMap.put(trafficLightId, createTrafficLightMock(trafficLightId, networkId, simulator));
304 Network network = new Network(networkId, simulator);
305 network.addObject(trafficLightMap.get(trafficLightId));
306
307 Duration cycleTime = Duration.ofSI(90);
308 Duration offset = Duration.ofSI(20);
309 Set<SignalGroup> signalGroups = new LinkedHashSet<>();
310 signalGroups.add(sg1);
311 signalGroups.add(sg2);
312 try
313 {
314 new FixedTimeController(ftcId, simulator, network, cycleTime, offset, signalGroups);
315 fail("Same traffic light in different signal groups should have thrown an IllegalArgumnentException");
316 }
317 catch (IllegalArgumentException iae)
318 {
319
320 }
321 }
322
323
324
325
326
327
328
329
330 public void testTimings() throws SimRuntimeException, NamingException, NetworkException
331 {
332 String signalGroupId = "sgId";
333 Set<String> trafficLightIds = new LinkedHashSet<>();
334 String trafficLightId = "08.1";
335 trafficLightIds.add(trafficLightId);
336 Set<SignalGroup> signalGroups = new LinkedHashSet<>();
337 for (int cycleTime : new int[] {60, 90})
338 {
339 Duration cycle = Duration.ofSI(cycleTime);
340 for (int ftcOffsetTime : new int[] {-100, -10, 0, 10, 100})
341 {
342 Duration ftcOffset = Duration.ofSI(ftcOffsetTime);
343 for (int sgOffsetTime : new int[] {-99, -9, 0, 9, 99})
344 {
345 Duration sgOffset = Duration.ofSI(sgOffsetTime);
346 for (int preGreenTime : new int[] {0, 3})
347 {
348 Duration preGreen = Duration.ofSI(preGreenTime);
349 for (int greenTime : new int[] {5, 15, 100})
350 {
351 Duration green = Duration.ofSI(greenTime);
352 for (double yellowTime : new double[] {0, 3.5, 4.5})
353 {
354 Duration yellow = Duration.ofSI(yellowTime);
355 double minimumCycleTime = preGreenTime + greenTime + yellowTime;
356 SignalGroup sg =
357 new SignalGroup(signalGroupId, trafficLightIds, sgOffset, preGreen, green, yellow);
358 signalGroups.clear();
359 signalGroups.add(sg);
360 String ftcId = "FTCid";
361 OtsSimulatorInterface simulator = new OtsSimulator("TestFixedTimeController");
362 simulator.initialize(Duration.ZERO, Duration.ZERO, Duration.ofSI(3600), createModelMock(),
363 HistoryManagerDevs.noHistory(simulator));
364 Map<String, TrafficLight> trafficLightMap = new LinkedHashMap<String, TrafficLight>();
365 String networkId = "networkID";
366 trafficLightMap.put(trafficLightId,
367 createTrafficLightMock(trafficLightId, networkId, simulator));
368 Network network = new Network(networkId, simulator);
369 network.addObject(trafficLightMap.get(trafficLightId));
370
371 FixedTimeController ftc =
372 new FixedTimeController(ftcId, simulator, network, cycle, ftcOffset, signalGroups);
373
374 if (cycleTime < minimumCycleTime)
375 {
376 PrintStream originalError = System.err;
377 boolean exceptionThrown = false;
378 try
379 {
380 while (simulator.getSimulatorTime().si <= 0)
381 {
382 System.setErr(new PrintStream(new ByteArrayOutputStream()));
383 try
384 {
385 simulator.step();
386 }
387 finally
388 {
389 System.setErr(originalError);
390 }
391 }
392 }
393 catch (SimRuntimeException exception)
394 {
395 exceptionThrown = true;
396 assertTrue(exception.getCause().getCause().getMessage().contains("Cycle time shorter "),
397 "exception explains cycle time problem");
398 }
399 assertTrue(exceptionThrown,
400 "Too short cycle time should have thrown a SimRuntimeException");
401 }
402 else
403 {
404
405
406 for (int second = 0; second <= 300; second++)
407 {
408 simulator.scheduleEventAbs(Duration.ofSI(second + 0.25),
409 () -> checkState(simulator, ftc, true));
410 simulator.scheduleEventAbs(Duration.ofSI(second + 0.75),
411 () -> checkState(simulator, ftc, true));
412 }
413 Duration stopTime = Duration.ofSI(300.0);
414 simulator.runUpTo(stopTime);
415 while (simulator.isStartingOrRunning())
416 {
417 try
418 {
419 Thread.sleep(1);
420 }
421 catch (InterruptedException exception)
422 {
423 exception.printStackTrace();
424 }
425 }
426 if (simulator.getSimulatorTime().lt(stopTime))
427 {
428
429 checkState(simulator, ftc, Boolean.FALSE);
430 fail("checkState should have thrown an assert error");
431 }
432 }
433 }
434 }
435 }
436 }
437 }
438 }
439 }
440
441
442
443
444
445
446
447 public void checkState(final OtsSimulatorInterface simulator, final FixedTimeController ftc,
448 final boolean stopSimulatorOnError)
449 {
450 double cycleTime = ftc.getCycleTime().si;
451 double time = simulator.getSimulatorTime().si;
452 double mainOffset = ftc.getOffset().si;
453 for (SignalGroup sg : ftc.getSignalGroups())
454 {
455 double phaseOffset = sg.getOffset().si + mainOffset;
456 double phase = time + phaseOffset;
457 while (phase < 0)
458 {
459 phase += cycleTime;
460 }
461 phase %= cycleTime;
462 TrafficLightColor expectedColor = null;
463 if (phase < sg.getPreGreen().si)
464 {
465 expectedColor = TrafficLightColor.PREGREEN;
466 }
467 else if (phase < sg.getPreGreen().plus(sg.getGreen()).si)
468 {
469 expectedColor = TrafficLightColor.GREEN;
470 }
471 else if (phase < sg.getPreGreen().plus(sg.getGreen()).plus(sg.getYellow()).si)
472 {
473 expectedColor = TrafficLightColor.YELLOW;
474 }
475 else
476 {
477 expectedColor = TrafficLightColor.RED;
478 }
479
480 for (TrafficLight tl : sg.getTrafficLights())
481 {
482 if (!expectedColor.equals(tl.getTrafficLightColor()))
483 {
484 if (stopSimulatorOnError)
485 {
486 try
487 {
488 simulator.stop();
489 }
490 catch (SimRuntimeException exception)
491 {
492 exception.printStackTrace();
493 }
494 }
495 else
496 {
497 assertEquals(expectedColor + " which is in phase " + phase + " of cycle time " + cycleTime,
498 tl.getTrafficLightColor(), "Traffic light color mismatch at simulator time "
499 + simulator.getSimulatorTime() + " of signal group " + sg);
500 }
501 }
502 }
503 }
504 }
505
506
507
508
509
510 public OtsModelInterface createModelMock()
511 {
512 return Mockito.mock(OtsModelInterface.class);
513 }
514
515
516 private Map<String, TrafficLightColor> currentTrafficLightColors = new LinkedHashMap<>();
517
518
519
520
521
522
523
524
525 public TrafficLight createTrafficLightMock(final String id, final String networkId, final OtsSimulatorInterface simulator)
526 {
527 TrafficLight result = Mockito.mock(TrafficLight.class);
528 Mockito.when(result.getId()).thenReturn(id);
529 Mockito.when(result.getFullId()).thenReturn(networkId + "." + id);
530 Mockito.when(result.getTrafficLightColor()).thenAnswer(new Answer<TrafficLightColor>()
531 {
532 @Override
533 public TrafficLightColor answer(final InvocationOnMock invocation) throws Throwable
534 {
535 return TestFixedTimeController.this.currentTrafficLightColors.get(result.getFullId());
536 }
537 });
538 Mockito.doAnswer((Answer<Void>) invocation ->
539 {
540 TrafficLightColor tlc = invocation.getArgument(0);
541
542
543 this.currentTrafficLightColors.put(result.getFullId(), tlc);
544 return null;
545 }).when(result).setTrafficLightColor(ArgumentMatchers.any(TrafficLightColor.class));
546 this.currentTrafficLightColors.put(result.getFullId(), TrafficLightColor.BLACK);
547 return result;
548 }
549
550 }