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