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.HashMap;
10 import java.util.HashSet;
11 import java.util.Map;
12 import java.util.Set;
13
14 import javax.naming.NamingException;
15
16 import nl.tudelft.simulation.dsol.SimRuntimeException;
17
18 import org.djunits.value.vdouble.scalar.Duration;
19 import org.djunits.value.vdouble.scalar.Time;
20 import org.djutils.immutablecollections.ImmutableSet;
21 import org.junit.Test;
22 import org.mockito.ArgumentMatchers;
23 import org.mockito.Mockito;
24 import org.mockito.invocation.InvocationOnMock;
25 import org.mockito.stubbing.Answer;
26 import org.opentrafficsim.core.dsol.OTSModelInterface;
27 import org.opentrafficsim.core.dsol.OTSSimulator;
28 import org.opentrafficsim.core.dsol.OTSSimulatorInterface;
29 import org.opentrafficsim.core.network.NetworkException;
30 import org.opentrafficsim.core.network.OTSNetwork;
31 import org.opentrafficsim.road.network.lane.object.trafficlight.TrafficLight;
32 import org.opentrafficsim.road.network.lane.object.trafficlight.TrafficLightColor;
33 import org.opentrafficsim.trafficcontrol.FixedTimeController;
34 import org.opentrafficsim.trafficcontrol.FixedTimeController.SignalGroup;
35
36
37
38
39
40
41
42
43
44
45
46
47 public class TestFixedTimeController
48 {
49
50
51
52
53
54
55
56
57 public void testConstructors() throws SimRuntimeException, NamingException, NetworkException
58 {
59 String signalGroupId = "sgId";
60 Set<String> trafficLightIds = new HashSet<>();
61 String trafficLightId = "08.1";
62 trafficLightIds.add(trafficLightId);
63 Duration signalGroupOffset = Duration.createSI(5);
64 Duration preGreen = Duration.createSI(2);
65 Duration green = Duration.createSI(10);
66 Duration yellow = Duration.createSI(3.5);
67 try
68 {
69 new SignalGroup(null, trafficLightIds, signalGroupOffset, preGreen, green, yellow);
70 fail("Null pointer for signalGroupId should have thrown a null pointer exception");
71 }
72 catch (NullPointerException npe)
73 {
74
75 }
76 try
77 {
78 new SignalGroup(signalGroupId, null, signalGroupOffset, preGreen, green, yellow);
79 fail("Null pointer for trafficLightIds should have thrown a null pointer exception");
80 }
81 catch (NullPointerException npe)
82 {
83
84 }
85 try
86 {
87 new SignalGroup(signalGroupId, trafficLightIds, null, preGreen, green, yellow);
88 fail("Null pointer for signalGroupOffset should have thrown a null pointer exception");
89 }
90 catch (NullPointerException npe)
91 {
92
93 }
94 try
95 {
96 new SignalGroup(signalGroupId, trafficLightIds, signalGroupOffset, null, green, yellow);
97 fail("Null pointer for preGreen should have thrown a null pointer exception");
98 }
99 catch (NullPointerException npe)
100 {
101
102 }
103 try
104 {
105 new SignalGroup(signalGroupId, trafficLightIds, signalGroupOffset, preGreen, null, yellow);
106 fail("Null pointer for green should have thrown a null pointer exception");
107 }
108 catch (NullPointerException npe)
109 {
110
111 }
112 try
113 {
114 new SignalGroup(signalGroupId, trafficLightIds, signalGroupOffset, preGreen, green, null);
115 fail("Null pointer for yellow should have thrown a null pointer exception");
116 }
117 catch (NullPointerException npe)
118 {
119
120 }
121 try
122 {
123 new SignalGroup(signalGroupId, new HashSet<String>(), signalGroupOffset, preGreen, green, yellow);
124 fail("Empty list of traffic light ids should have thrown an illegal argument exception");
125 }
126 catch (IllegalArgumentException iae)
127 {
128
129 }
130
131 SignalGroup sg = new SignalGroup(signalGroupId, trafficLightIds, signalGroupOffset, green, yellow);
132 assertEquals("default for pre green", 0, sg.getPreGreen().si, 0);
133 assertEquals("green", green.si, sg.getGreen().si, 0);
134 assertEquals("yellow", yellow.si, sg.getYellow().si, 0);
135
136 sg = new SignalGroup(signalGroupId, trafficLightIds, signalGroupOffset, preGreen, green, yellow);
137 assertEquals("group id", signalGroupId, sg.getId());
138 assertTrue("toString returns something descriptive", sg.toString().startsWith("SignalGroup ["));
139
140 String ftcId = "FTCid";
141 OTSSimulatorInterface simulator = new OTSSimulator();
142 simulator.initialize(Time.ZERO, Duration.ZERO, Duration.createSI(3600), createModelMock());
143 Map<String, TrafficLight> trafficLightMap = new HashMap<String, TrafficLight>();
144 String networkId = "networkID";
145 trafficLightMap.put(trafficLightId, createTrafficLightMock(trafficLightId, networkId, simulator));
146 OTSNetwork network = new OTSNetwork(networkId, true);
147 network.addObject(trafficLightMap.get(trafficLightId));
148
149 Duration cycleTime = Duration.createSI(90);
150 Duration offset = Duration.createSI(20);
151 Set<SignalGroup> signalGroups = new HashSet<>();
152 ImmutableSet<String> ids = sg.getTrafficLightIds();
153 for (String tlId : ids)
154 {
155 assertTrue("returned id is in provided set", trafficLightMap.containsKey(tlId));
156 }
157 for (String tlId : trafficLightMap.keySet())
158 {
159 assertTrue("provided id is returned", ids.contains(tlId));
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 HashSet<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.createSI(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.createSI(-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("FTC id", ftcId, ftc.getId());
247 assertTrue("toString returns something descriptive", ftc.toString().startsWith("FixedTimeController ["));
248
249 simulator.runUpTo(Time.createSI(1));
250 while (simulator.isRunning())
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("acquired traffic light is in the proved set", trafficLightMap.containsKey(tl.getId()));
264 }
265 assertEquals("red time makes up remainder of cycle time", cycleTime.minus(preGreen).minus(green).minus(yellow).si,
266 sg.getRed().si, 0.0001);
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 HashSet<>();
280 String trafficLightId = "08.1";
281 trafficLightIds1.add(trafficLightId);
282 Duration signalGroupOffset = Duration.createSI(5);
283 Duration preGreen = Duration.createSI(2);
284 Duration green = Duration.createSI(10);
285 Duration yellow = Duration.createSI(3.5);
286 SignalGroup sg1 = new SignalGroup(signalGroupId, trafficLightIds1, signalGroupOffset, preGreen, green, yellow);
287 String signalGroupId2 = "sgId2";
288 Set<String> trafficLightIds2 = new HashSet<>();
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();
294 simulator.initialize(Time.ZERO, Duration.ZERO, Duration.createSI(3600), createModelMock());
295 Map<String, TrafficLight> trafficLightMap = new HashMap<String, TrafficLight>();
296 String networkId = "networkID";
297 trafficLightMap.put(trafficLightId, createTrafficLightMock(trafficLightId, networkId, simulator));
298 OTSNetwork network = new OTSNetwork(networkId, true);
299 network.addObject(trafficLightMap.get(trafficLightId));
300
301 Duration cycleTime = Duration.createSI(90);
302 Duration offset = Duration.createSI(20);
303 Set<SignalGroup> signalGroups = new HashSet<>();
304 signalGroups.add(sg1);
305 signalGroups.add(sg2);
306 try
307 {
308 new FixedTimeController(ftcId, simulator, network, cycleTime, offset, signalGroups);
309 fail("Same traffic light in different signal groups should have thrown an IllegalArgumnentException");
310 }
311 catch (IllegalArgumentException iae)
312 {
313
314 }
315 }
316
317
318
319
320
321
322
323
324 public void testTimings() throws SimRuntimeException, NamingException, NetworkException
325 {
326 String signalGroupId = "sgId";
327 Set<String> trafficLightIds = new HashSet<>();
328 String trafficLightId = "08.1";
329 trafficLightIds.add(trafficLightId);
330 Set<SignalGroup> signalGroups = new HashSet<>();
331 for (int cycleTime : new int[] {60, 90})
332 {
333 Duration cycle = Duration.createSI(cycleTime);
334 for (int ftcOffsetTime : new int[] {-100, -10, 0, 10, 100})
335 {
336 Duration ftcOffset = Duration.createSI(ftcOffsetTime);
337 for (int sgOffsetTime : new int[] {-99, -9, 0, 9, 99})
338 {
339 Duration sgOffset = Duration.createSI(sgOffsetTime);
340 for (int preGreenTime : new int[] {0, 3})
341 {
342 Duration preGreen = Duration.createSI(preGreenTime);
343 for (int greenTime : new int[] {5, 15, 100})
344 {
345 Duration green = Duration.createSI(greenTime);
346 for (double yellowTime : new double[] {0, 3.5, 4.5})
347 {
348 Duration yellow = Duration.createSI(yellowTime);
349 double minimumCycleTime = preGreenTime + greenTime + yellowTime;
350 SignalGroup sg =
351 new SignalGroup(signalGroupId, trafficLightIds, sgOffset, preGreen, green, yellow);
352 signalGroups.clear();
353 signalGroups.add(sg);
354 String ftcId = "FTCid";
355 OTSSimulatorInterface simulator = new OTSSimulator();
356 simulator.initialize(Time.ZERO, Duration.ZERO, Duration.createSI(3600), createModelMock());
357 Map<String, TrafficLight> trafficLightMap = new HashMap<String, TrafficLight>();
358 String networkId = "networkID";
359 trafficLightMap.put(trafficLightId,
360 createTrafficLightMock(trafficLightId, networkId, simulator));
361 OTSNetwork network = new OTSNetwork(networkId, true);
362 network.addObject(trafficLightMap.get(trafficLightId));
363
364 FixedTimeController ftc =
365 new FixedTimeController(ftcId, simulator, network, cycle, ftcOffset, signalGroups);
366
367 if (cycleTime < minimumCycleTime)
368 {
369 PrintStream originalError = System.err;
370 boolean exceptionThrown = false;
371 try
372 {
373 while (simulator.getSimulatorTime().si <= 0)
374 {
375 System.setErr(new PrintStream(new ByteArrayOutputStream()));
376 try
377 {
378 simulator.step();
379 }
380 finally
381 {
382 System.setErr(originalError);
383 }
384 }
385 }
386 catch (SimRuntimeException exception)
387 {
388 exceptionThrown = true;
389 assertTrue("exception explains cycle time problem",
390 exception.getCause().getCause().getMessage().contains("Cycle time shorter "));
391 }
392 assertTrue("Too short cycle time should have thrown a SimRuntimeException",
393 exceptionThrown);
394 }
395 else
396 {
397
398
399 for (int second = 0; second <= 300; second++)
400 {
401 Object[] args = new Object[] {simulator, ftc, Boolean.TRUE};
402 simulator.scheduleEventAbs(Time.createSI(second + 0.25), this, this, "checkState",
403 args);
404 simulator.scheduleEventAbs(Time.createSI(second + 0.75), this, this, "checkState",
405 args);
406 }
407 Time stopTime = Time.createSI(300);
408 simulator.runUpTo(stopTime);
409 while (simulator.isRunning())
410 {
411 try
412 {
413 Thread.sleep(1);
414 }
415 catch (InterruptedException exception)
416 {
417 exception.printStackTrace();
418 }
419 }
420 if (simulator.getSimulatorTime().lt(stopTime))
421 {
422
423 checkState(simulator, ftc, Boolean.FALSE);
424 fail("checkState should have thrown an assert error");
425 }
426 }
427 }
428 }
429 }
430 }
431 }
432 }
433 }
434
435
436
437
438
439
440
441 public void checkState(final OTSSimulatorInterface simulator, final FixedTimeController ftc,
442 final boolean stopSimulatorOnError)
443 {
444 double cycleTime = ftc.getCycleTime().si;
445 double time = simulator.getSimulatorTime().si;
446 double mainOffset = ftc.getOffset().si;
447 for (SignalGroup sg : ftc.getSignalGroups())
448 {
449 double phaseOffset = sg.getOffset().si + mainOffset;
450 double phase = time + phaseOffset;
451 while (phase < 0)
452 {
453 phase += cycleTime;
454 }
455 phase %= cycleTime;
456 TrafficLightColor expectedColor = null;
457 if (phase < sg.getPreGreen().si)
458 {
459 expectedColor = TrafficLightColor.PREGREEN;
460 }
461 else if (phase < sg.getPreGreen().plus(sg.getGreen()).si)
462 {
463 expectedColor = TrafficLightColor.GREEN;
464 }
465 else if (phase < sg.getPreGreen().plus(sg.getGreen()).plus(sg.getYellow()).si)
466 {
467 expectedColor = TrafficLightColor.YELLOW;
468 }
469 else
470 {
471 expectedColor = TrafficLightColor.RED;
472 }
473
474 for (TrafficLight tl : sg.getTrafficLights())
475 {
476 if (!expectedColor.equals(tl.getTrafficLightColor()))
477 {
478 if (stopSimulatorOnError)
479 {
480 try
481 {
482 simulator.stop();
483 }
484 catch (SimRuntimeException exception)
485 {
486 exception.printStackTrace();
487 }
488 }
489 else
490 {
491 assertEquals(
492 "Traffic light color mismatch at simulator time " + simulator.getSimulatorTime()
493 + " of signal group " + sg,
494 expectedColor + " which is in phase " + phase + " of cycle time " + cycleTime,
495 tl.getTrafficLightColor());
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 Map<String, TrafficLightColor> currentTrafficLightColors = new HashMap<>();
513
514
515
516
517
518
519
520
521 public TrafficLight createTrafficLightMock(final String id, final String networkId, 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(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 }