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