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