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