View Javadoc
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   * Test the fixed time traffic controller class.
38   * <p>
39   * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
40   * BSD-style license. See <a href="http://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
41   * <p>
42   * @version $Revision$, $LastChangedDate$, by $Author$, initial version Feb 21, 2019 <br>
43   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
44   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
45   * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
46   */
47  public class TestFixedTimeController
48  {
49  
50      /**
51       * Test the constructors and initializers of the signal group and fixed time controller classes.
52       * @throws SimRuntimeException if that happens uncaught; this test has failed
53       * @throws NamingException if that happens uncaught; this test has failed
54       * @throws NetworkException
55       */
56      // TODO: @Test
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              // Ignore expected exception
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              // Ignore expected exception
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              // Ignore expected exception
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             // Ignore expected exception
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             // Ignore expected exception
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             // Ignore expected exception
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             // Ignore expected exception
129         }
130         // Test the controller that adds default for pre green time
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         // Now that we've tested all ways that the constructor should have told us to go to hell, create a signal group
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             // Ignore
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             // Ignore
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             // Ignore
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             // Ignore
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             // Ignore
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             // Ignore
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             // Ignore
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             // Ignore
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             // Ignore
242         }
243         // Not testing check for identical signal groups; yet
244         // Now that we've tested all ways that the constructor should have told us to go to hell, create a controller
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      * Test detection of non-disjoint sets of traffic lights.
271      * @throws NamingException
272      * @throws SimRuntimeException
273      * @throws NetworkException
274      */
275     // TODO: @Test
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             // Ignore
314         }
315     }
316 
317     /**
318      * Test timing of fixed time controller.
319      * @throws SimRuntimeException if that happens uncaught; this test has failed
320      * @throws NamingException if that happens uncaught; this test has failed
321      * @throws NetworkException
322      */
323     // TODO: @Test
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                                 // System.out.println(cycle);
364                                 FixedTimeController ftc =
365                                         new FixedTimeController(ftcId, simulator, network, cycle, ftcOffset, signalGroups);
366                                 // System.out.print(ftc);
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                                     // All transitions are at multiples of 0.5 seconds; check the state at 0.25 and 0.75 in each
398                                     // second
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                                         // something went wrong; call checkState with stopSimulatorOnError set to false
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      * Check that the current state of a fixed time traffic light controller matches the design.
437      * @param simulator OTSSimulatorInterface; the simulator
438      * @param ftc FixedTimeController; the fixed time traffic light controller
439      * @param stopSimulatorOnError boolean; if true; stop the simulator on error; if false; execute the failing assert on error
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             // Verify the color of all traffic lights
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      * Create a mocked OTSModelInterface.
504      * @return OTSModelInterface
505      */
506     public OTSModelInterface createModelMock()
507     {
508         return Mockito.mock(OTSModelInterface.class);
509     }
510 
511     /** Remember current state of all mocked traffic lights. */
512     Map<String, TrafficLightColor> currentTrafficLightColors = new HashMap<>();
513 
514     /**
515      * Mock a traffic light.
516      * @param id String; value that will be returned by the getId method
517      * @param networkId String; name of network (prepended to id for result of getFullId method)
518      * @param simulator TODO
519      * @return TrafficLight
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             // System.out.println(simulator.getSimulatorTime() + " changing color of " + result.getFullId() + " from "
538             // + this.currentTrafficLightColors.get(result.getFullId()) + " to " + tlc);
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 }