package trafficcontrol; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import javax.naming.NamingException; import org.djunits.value.vdouble.scalar.Duration; import org.djunits.value.vdouble.scalar.Time; import org.djutils.immutablecollections.ImmutableSet; import org.mockito.ArgumentMatchers; import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.opentrafficsim.core.dsol.OTSModelInterface; import org.opentrafficsim.core.dsol.OTSSimulator; import org.opentrafficsim.core.dsol.OTSSimulatorInterface; import org.opentrafficsim.core.network.NetworkException; import org.opentrafficsim.core.network.OTSNetwork; import org.opentrafficsim.road.network.lane.object.trafficlight.TrafficLight; import org.opentrafficsim.road.network.lane.object.trafficlight.TrafficLightColor; import org.opentrafficsim.trafficcontrol.FixedTimeController; import org.opentrafficsim.trafficcontrol.FixedTimeController.SignalGroup; import nl.tudelft.simulation.dsol.SimRuntimeException; import nl.tudelft.simulation.dsol.simtime.SimTimeDoubleUnit; /** * Test the fixed time traffic controller class. *

* Copyright (c) 2013-2022 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
* BSD-style license. See OpenTrafficSim License. *

* @version $Revision$, $LastChangedDate$, by $Author$, initial version Feb 21, 2019
* @author Alexander Verbraeck * @author Peter Knoppers * @author Wouter Schakel */ public class TestFixedTimeController { /** * Test the constructors and initializers of the signal group and fixed time controller classes. * @throws SimRuntimeException if that happens uncaught; this test has failed * @throws NamingException if that happens uncaught; this test has failed * @throws NetworkException on exception */ // TODO: @Test public void testConstructors() throws SimRuntimeException, NamingException, NetworkException { String signalGroupId = "sgId"; Set trafficLightIds = new LinkedHashSet<>(); String trafficLightId = "08.1"; trafficLightIds.add(trafficLightId); Duration signalGroupOffset = Duration.instantiateSI(5); Duration preGreen = Duration.instantiateSI(2); Duration green = Duration.instantiateSI(10); Duration yellow = Duration.instantiateSI(3.5); try { new SignalGroup(null, trafficLightIds, signalGroupOffset, preGreen, green, yellow); fail("Null pointer for signalGroupId should have thrown a null pointer exception"); } catch (NullPointerException npe) { // Ignore expected exception } try { new SignalGroup(signalGroupId, null, signalGroupOffset, preGreen, green, yellow); fail("Null pointer for trafficLightIds should have thrown a null pointer exception"); } catch (NullPointerException npe) { // Ignore expected exception } try { new SignalGroup(signalGroupId, trafficLightIds, null, preGreen, green, yellow); fail("Null pointer for signalGroupOffset should have thrown a null pointer exception"); } catch (NullPointerException npe) { // Ignore expected exception } try { new SignalGroup(signalGroupId, trafficLightIds, signalGroupOffset, null, green, yellow); fail("Null pointer for preGreen should have thrown a null pointer exception"); } catch (NullPointerException npe) { // Ignore expected exception } try { new SignalGroup(signalGroupId, trafficLightIds, signalGroupOffset, preGreen, null, yellow); fail("Null pointer for green should have thrown a null pointer exception"); } catch (NullPointerException npe) { // Ignore expected exception } try { new SignalGroup(signalGroupId, trafficLightIds, signalGroupOffset, preGreen, green, null); fail("Null pointer for yellow should have thrown a null pointer exception"); } catch (NullPointerException npe) { // Ignore expected exception } try { new SignalGroup(signalGroupId, new LinkedHashSet(), signalGroupOffset, preGreen, green, yellow); fail("Empty list of traffic light ids should have thrown an illegal argument exception"); } catch (IllegalArgumentException iae) { // Ignore expected exception } // Test the controller that adds default for pre green time SignalGroup sg = new SignalGroup(signalGroupId, trafficLightIds, signalGroupOffset, green, yellow); assertEquals("default for pre green", 0, sg.getPreGreen().si, 0); assertEquals("green", green.si, sg.getGreen().si, 0); assertEquals("yellow", yellow.si, sg.getYellow().si, 0); // Now that we've tested all ways that the constructor should have told us to go to hell, create a signal group sg = new SignalGroup(signalGroupId, trafficLightIds, signalGroupOffset, preGreen, green, yellow); assertEquals("group id", signalGroupId, sg.getId()); assertTrue("toString returns something descriptive", sg.toString().startsWith("SignalGroup [")); String ftcId = "FTCid"; OTSSimulatorInterface simulator = new OTSSimulator("TestFixedTimeController"); simulator.initialize(Time.ZERO, Duration.ZERO, Duration.instantiateSI(3600), createModelMock()); Map trafficLightMap = new LinkedHashMap(); String networkId = "networkID"; trafficLightMap.put(trafficLightId, createTrafficLightMock(trafficLightId, networkId, simulator)); OTSNetwork network = new OTSNetwork(networkId, true, simulator); network.addObject(trafficLightMap.get(trafficLightId)); Duration cycleTime = Duration.instantiateSI(90); Duration offset = Duration.instantiateSI(20); Set signalGroups = new LinkedHashSet<>(); ImmutableSet ids = sg.getTrafficLightIds(); for (String tlId : ids) { assertTrue("returned id is in provided set", trafficLightMap.containsKey(tlId)); } for (String tlId : trafficLightMap.keySet()) { assertTrue("provided id is returned", ids.contains(tlId)); } signalGroups.add(sg); try { new FixedTimeController(null, simulator, network, cycleTime, offset, signalGroups); fail("Null pointer for controller id should have thrown an exception"); } catch (NullPointerException npe) { // Ignore } try { new FixedTimeController(ftcId, null, network, cycleTime, offset, signalGroups); fail("Null pointer for simulator should have thrown an exception"); } catch (NullPointerException npe) { // Ignore } try { new FixedTimeController(ftcId, simulator, null, cycleTime, offset, signalGroups); fail("Null pointer for network should have thrown an exception"); } catch (NullPointerException npe) { // Ignore } try { new FixedTimeController(ftcId, simulator, network, null, offset, signalGroups); fail("Null pointer for cycle time should have thrown an exception"); } catch (NullPointerException npe) { // Ignore } try { new FixedTimeController(ftcId, simulator, network, cycleTime, null, signalGroups); fail("Null pointer for offset should have thrown an exception"); } catch (NullPointerException npe) { // Ignore } try { new FixedTimeController(ftcId, simulator, network, cycleTime, offset, null); fail("Null pointer for signal groups should have thrown an exception"); } catch (NullPointerException npe) { // Ignore } try { new FixedTimeController(ftcId, simulator, network, cycleTime, offset, new LinkedHashSet()); fail("Empty signal groups should have thrown an exception"); } catch (IllegalArgumentException iae) { // Ignore } try { new FixedTimeController(ftcId, simulator, network, Duration.instantiateSI(0), offset, signalGroups); fail("Illegal cycle time should hav thrown an exception"); } catch (IllegalArgumentException iae) { // Ignore } try { new FixedTimeController(ftcId, simulator, network, Duration.instantiateSI(-10), offset, signalGroups); fail("Illegal cycle time should hav thrown an exception"); } catch (IllegalArgumentException iae) { // Ignore } // Not testing check for identical signal groups; yet // Now that we've tested all ways that the constructor should have told us to go to hell, create a controller FixedTimeController ftc = new FixedTimeController(ftcId, simulator, network, cycleTime, offset, signalGroups); assertEquals("FTC id", ftcId, ftc.getId()); assertTrue("toString returns something descriptive", ftc.toString().startsWith("FixedTimeController [")); simulator.runUpTo(new SimTimeDoubleUnit(Time.instantiateSI(1))); while (simulator.isStartingOrRunning()) { try { Thread.sleep(100); } catch (InterruptedException exception) { exception.printStackTrace(); } } for (TrafficLight tl : sg.getTrafficLights()) { assertTrue("acquired traffic light is in the proved set", trafficLightMap.containsKey(tl.getId())); } assertEquals("red time makes up remainder of cycle time", cycleTime.minus(preGreen).minus(green).minus(yellow).si, sg.getRed().si, 0.0001); } /** * Test detection of non-disjoint sets of traffic lights. * @throws NamingException on exception * @throws SimRuntimeException on exception * @throws NetworkException on exception */ // TODO: @Test public void testDisjoint() throws SimRuntimeException, NamingException, NetworkException { String signalGroupId = "sgId1"; Set trafficLightIds1 = new LinkedHashSet<>(); String trafficLightId = "08.1"; trafficLightIds1.add(trafficLightId); Duration signalGroupOffset = Duration.instantiateSI(5); Duration preGreen = Duration.instantiateSI(2); Duration green = Duration.instantiateSI(10); Duration yellow = Duration.instantiateSI(3.5); SignalGroup sg1 = new SignalGroup(signalGroupId, trafficLightIds1, signalGroupOffset, preGreen, green, yellow); String signalGroupId2 = "sgId2"; Set trafficLightIds2 = new LinkedHashSet<>(); trafficLightIds2.add(trafficLightId); SignalGroup sg2 = new SignalGroup(signalGroupId2, trafficLightIds2, signalGroupOffset, preGreen, green, yellow); String ftcId = "FTCid"; OTSSimulatorInterface simulator = new OTSSimulator("TestFixedTimeController"); simulator.initialize(Time.ZERO, Duration.ZERO, Duration.instantiateSI(3600), createModelMock()); Map trafficLightMap = new LinkedHashMap(); String networkId = "networkID"; trafficLightMap.put(trafficLightId, createTrafficLightMock(trafficLightId, networkId, simulator)); OTSNetwork network = new OTSNetwork(networkId, true, simulator); network.addObject(trafficLightMap.get(trafficLightId)); Duration cycleTime = Duration.instantiateSI(90); Duration offset = Duration.instantiateSI(20); Set signalGroups = new LinkedHashSet<>(); signalGroups.add(sg1); signalGroups.add(sg2); try { new FixedTimeController(ftcId, simulator, network, cycleTime, offset, signalGroups); fail("Same traffic light in different signal groups should have thrown an IllegalArgumnentException"); } catch (IllegalArgumentException iae) { // Ignore } } /** * Test timing of fixed time controller. * @throws SimRuntimeException if that happens uncaught; this test has failed * @throws NamingException if that happens uncaught; this test has failed * @throws NetworkException on exception */ // TODO: @Test public void testTimings() throws SimRuntimeException, NamingException, NetworkException { String signalGroupId = "sgId"; Set trafficLightIds = new LinkedHashSet<>(); String trafficLightId = "08.1"; trafficLightIds.add(trafficLightId); Set signalGroups = new LinkedHashSet<>(); for (int cycleTime : new int[] {60, 90}) { Duration cycle = Duration.instantiateSI(cycleTime); for (int ftcOffsetTime : new int[] {-100, -10, 0, 10, 100}) { Duration ftcOffset = Duration.instantiateSI(ftcOffsetTime); for (int sgOffsetTime : new int[] {-99, -9, 0, 9, 99}) { Duration sgOffset = Duration.instantiateSI(sgOffsetTime); for (int preGreenTime : new int[] {0, 3}) { Duration preGreen = Duration.instantiateSI(preGreenTime); for (int greenTime : new int[] {5, 15, 100}) { Duration green = Duration.instantiateSI(greenTime); for (double yellowTime : new double[] {0, 3.5, 4.5}) { Duration yellow = Duration.instantiateSI(yellowTime); double minimumCycleTime = preGreenTime + greenTime + yellowTime; SignalGroup sg = new SignalGroup(signalGroupId, trafficLightIds, sgOffset, preGreen, green, yellow); signalGroups.clear(); signalGroups.add(sg); String ftcId = "FTCid"; OTSSimulatorInterface simulator = new OTSSimulator("TestFixedTimeController"); simulator.initialize(Time.ZERO, Duration.ZERO, Duration.instantiateSI(3600), createModelMock()); Map trafficLightMap = new LinkedHashMap(); String networkId = "networkID"; trafficLightMap.put(trafficLightId, createTrafficLightMock(trafficLightId, networkId, simulator)); OTSNetwork network = new OTSNetwork(networkId, true, simulator); network.addObject(trafficLightMap.get(trafficLightId)); // System.out.println(cycle); FixedTimeController ftc = new FixedTimeController(ftcId, simulator, network, cycle, ftcOffset, signalGroups); // System.out.print(ftc); if (cycleTime < minimumCycleTime) { PrintStream originalError = System.err; boolean exceptionThrown = false; try { while (simulator.getSimulatorTime().si <= 0) { System.setErr(new PrintStream(new ByteArrayOutputStream())); try { simulator.step(); } finally { System.setErr(originalError); } } } catch (SimRuntimeException exception) { exceptionThrown = true; assertTrue("exception explains cycle time problem", exception.getCause().getCause().getMessage().contains("Cycle time shorter ")); } assertTrue("Too short cycle time should have thrown a SimRuntimeException", exceptionThrown); } else { // All transitions are at multiples of 0.5 seconds; check the state at 0.25 and 0.75 in each // second for (int second = 0; second <= 300; second++) { Object[] args = new Object[] {simulator, ftc, Boolean.TRUE}; simulator.scheduleEventAbs(Time.instantiateSI(second + 0.25), this, this, "checkState", args); simulator.scheduleEventAbs(Time.instantiateSI(second + 0.75), this, this, "checkState", args); } Time stopTime = Time.instantiateSI(300); simulator.runUpTo(new SimTimeDoubleUnit(stopTime)); while (simulator.isStartingOrRunning()) { try { Thread.sleep(1); } catch (InterruptedException exception) { exception.printStackTrace(); } } if (simulator.getSimulatorTime().lt(stopTime)) { // something went wrong; call checkState with stopSimulatorOnError set to false checkState(simulator, ftc, Boolean.FALSE); fail("checkState should have thrown an assert error"); } } } } } } } } } /** * Check that the current state of a fixed time traffic light controller matches the design. * @param simulator OTSSimulatorInterface; the simulator * @param ftc FixedTimeController; the fixed time traffic light controller * @param stopSimulatorOnError boolean; if true; stop the simulator on error; if false; execute the failing assert on error */ public void checkState(final OTSSimulatorInterface simulator, final FixedTimeController ftc, final boolean stopSimulatorOnError) { double cycleTime = ftc.getCycleTime().si; double time = simulator.getSimulatorTime().si; double mainOffset = ftc.getOffset().si; for (SignalGroup sg : ftc.getSignalGroups()) { double phaseOffset = sg.getOffset().si + mainOffset; double phase = time + phaseOffset; while (phase < 0) { phase += cycleTime; } phase %= cycleTime; TrafficLightColor expectedColor = null; if (phase < sg.getPreGreen().si) { expectedColor = TrafficLightColor.PREGREEN; } else if (phase < sg.getPreGreen().plus(sg.getGreen()).si) { expectedColor = TrafficLightColor.GREEN; } else if (phase < sg.getPreGreen().plus(sg.getGreen()).plus(sg.getYellow()).si) { expectedColor = TrafficLightColor.YELLOW; } else { expectedColor = TrafficLightColor.RED; } // Verify the color of all traffic lights for (TrafficLight tl : sg.getTrafficLights()) { if (!expectedColor.equals(tl.getTrafficLightColor())) { if (stopSimulatorOnError) { try { simulator.stop(); } catch (SimRuntimeException exception) { exception.printStackTrace(); } } else { assertEquals( "Traffic light color mismatch at simulator time " + simulator.getSimulatorTime() + " of signal group " + sg, expectedColor + " which is in phase " + phase + " of cycle time " + cycleTime, tl.getTrafficLightColor()); } } } } } /** * Create a mocked OTSModelInterface. * @return OTSModelInterface */ public OTSModelInterface createModelMock() { return Mockito.mock(OTSModelInterface.class); } /** Remember current state of all mocked traffic lights. */ private Map currentTrafficLightColors = new LinkedHashMap<>(); /** * Mock a traffic light. * @param id String; value that will be returned by the getId method * @param networkId String; name of network (prepended to id for result of getFullId method) * @param simulator TODO * @return TrafficLight */ public TrafficLight createTrafficLightMock(final String id, final String networkId, final OTSSimulatorInterface simulator) { TrafficLight result = Mockito.mock(TrafficLight.class); Mockito.when(result.getId()).thenReturn(id); Mockito.when(result.getFullId()).thenReturn(networkId + "." + id); Mockito.when(result.getTrafficLightColor()).thenAnswer(new Answer() { @Override public TrafficLightColor answer(final InvocationOnMock invocation) throws Throwable { return TestFixedTimeController.this.currentTrafficLightColors.get(result.getFullId()); } }); Mockito.doAnswer((Answer) invocation -> { TrafficLightColor tlc = invocation.getArgument(0); // System.out.println(simulator.getSimulatorTime() + " changing color of " + result.getFullId() + " from " // + this.currentTrafficLightColors.get(result.getFullId()) + " to " + tlc); this.currentTrafficLightColors.put(result.getFullId(), tlc); return null; }).when(result).setTrafficLightColor(ArgumentMatchers.any(TrafficLightColor.class)); this.currentTrafficLightColors.put(result.getFullId(), TrafficLightColor.BLACK); return result; } }