def test_shift_one(self) -> None:
        """ Test finding a shift of one """
        # GIVEN
        sync_start = SyncStart(from_id="sg1", to_id="sg2")
        fts = FixedTimeSchedule(greenyellow_intervals=dict(
            sg1=[GreenYellowInterval(start_greenyellow=10, end_greenyellow=40),
                 GreenYellowInterval(start_greenyellow=50, end_greenyellow=70)],
            sg2=[GreenYellowInterval(start_greenyellow=50, end_greenyellow=60),
                 GreenYellowInterval(start_greenyellow=10, end_greenyellow=30)]),
            period=100)

        # WHEN
        matches = find_other_sg_relation_matches(other_relation=sync_start, fts=fts, index_from=0)
        matches2 = find_other_sg_relation_matches(other_relation=sync_start, fts=fts, index_from=1)

        # THEN
        self.assertListEqual(matches, [0, 1])
        self.assertListEqual(matches2, [1, 0])
    def test_successful_validation2(self) -> None:
        """ Test validation for correct fts; we will modify this schedule to violate maximum green and red times """
        # GIVEN
        fts = FixedTimeSchedule(greenyellow_intervals=dict(
            sg1=[
                GreenYellowInterval(start_greenyellow=0, end_greenyellow=50),
                GreenYellowInterval(start_greenyellow=120, end_greenyellow=170)
            ],
            sg2=[
                GreenYellowInterval(start_greenyellow=60, end_greenyellow=110),
                GreenYellowInterval(start_greenyellow=180, end_greenyellow=230)
            ]),
                                period=240)

        intersection = TestFTSValidationOfBounds.get_default_intersection()

        # WHEN
        validate_bounds(intersection=intersection, fts=fts)
    def test_negative(self) -> None:
        """ Test initializing GreenYellowInterval object with negative start_greenyellow or end_greenyellow"""
        for key in TestIntervalInputValidation.get_default_inputs():
            # GIVEN
            input_dict = TestIntervalInputValidation.get_default_inputs()
            input_dict[key] = -1  # should be positive

            with self.assertRaises(AssertionError):
                # WHEN initializing the queue lengths
                GreenYellowInterval(**input_dict)
    def test_not_a_number(self) -> None:
        """ Test initializing GreenYellowInterval object with non-numeric arguments """
        for key in TestIntervalInputValidation.get_default_inputs():
            # GIVEN
            input_dict = TestIntervalInputValidation.get_default_inputs()
            input_dict[key] = "str"  # each argument should be a number

            with self.assertRaises(ValueError):
                # WHEN initializing the queue lengths
                GreenYellowInterval(**input_dict)
    def test_rate_no_greenyellow_interval(self) -> None:
        """ Test providing no GreenYellowInterval for the interval """
        # GIVEN
        input_dict = TestFTSInputValidation.get_default_inputs()
        input_dict["greenyellow_intervals"]["id3"] = [
            GreenYellowInterval(start_greenyellow=1, end_greenyellow=10), "3"
        ]

        with self.assertRaises(ValueError):
            # WHEN initializing the fts
            FixedTimeSchedule(**input_dict)
    def test_no_shift_possible(self) -> None:
        """ Test finding the shifts for a schedule without an unambiguous shift"""
        # GIVEN
        sync_start = SyncStart(from_id="sg1", to_id="sg2")
        fts = FixedTimeSchedule(greenyellow_intervals=dict(
            sg1=[GreenYellowInterval(start_greenyellow=10, end_greenyellow=30),
                 GreenYellowInterval(start_greenyellow=40, end_greenyellow=50),
                 GreenYellowInterval(start_greenyellow=60, end_greenyellow=80)],
            sg2=[GreenYellowInterval(start_greenyellow=10, end_greenyellow=30),
                 GreenYellowInterval(start_greenyellow=40, end_greenyellow=50),
                 GreenYellowInterval(start_greenyellow=60, end_greenyellow=80)]),
            period=100)

        # swap two intervals (we do this after initialization as otherwise we would get a ValueError (not correct order
        #  of greenyellow intervals) when initializing the FixedTimeSchedule
        fts._greenyellow_intervals["sg2"][:2] = reversed(fts._greenyellow_intervals["sg2"][:2])

        # WHEN
        matches = find_other_sg_relation_matches(other_relation=sync_start, fts=fts, index_from=0)
        matches2 = find_other_sg_relation_matches(other_relation=sync_start, fts=fts, index_from=1)
        matches3 = find_other_sg_relation_matches(other_relation=sync_start, fts=fts, index_from=2)

        # THEN
        self.assertListEqual(matches, [0, 1, 0])
        self.assertListEqual(matches2, [1, 0, 0])
        self.assertListEqual(matches3, [0, 0, 1])
    def test_incorrect_offset(self) -> None:
        """
        Test that validation of incorrect offset raises SafetyViolation.
        :return:
        """
        # GIVEN
        offset = Offset(from_id="sg3", to_id="sg4", seconds=20)
        fts_org = FixedTimeSchedule(greenyellow_intervals=dict(
            sg1=[GreenYellowInterval(start_greenyellow=15, end_greenyellow=35)],
            sg2=[GreenYellowInterval(start_greenyellow=45, end_greenyellow=65)],
            sg3=[GreenYellowInterval(start_greenyellow=10, end_greenyellow=40),
                 GreenYellowInterval(start_greenyellow=50, end_greenyellow=70)],
            sg4=[GreenYellowInterval(start_greenyellow=30, end_greenyellow=40),
                 GreenYellowInterval(start_greenyellow=69, end_greenyellow=90)]),
            period=100)
        signalgroup3 = TestFTSOtherSGRelationValidation.get_default_signalgroup(name="sg3")
        signalgroup4 = TestFTSOtherSGRelationValidation.get_default_signalgroup(name="sg4")
        intersection = TestFTSOtherSGRelationValidation.get_default_intersection(
            additional_signalgroups=[signalgroup3, signalgroup4], offsets=[offset])

        for interval_shift in range(2):
            with self.subTest(f"interval_shift={interval_shift}"):
                fts = deepcopy(fts_org)
                fts._greenyellow_intervals["sg4"] = fts._greenyellow_intervals["sg4"][:interval_shift] + \
                    fts._greenyellow_intervals["sg4"][interval_shift:]

                with self.assertRaises(SafetyViolation):
                    # WHEN validating
                    validate_other_sg_relations(intersection=intersection, fts=fts)
    def test_red_interval_too_large(self) -> None:
        """ Test red interval too large """
        # GIVEN
        # red interval of signalgroup 3 is too large
        fts = FixedTimeSchedule(greenyellow_intervals=dict(
            sg1=[
                GreenYellowInterval(start_greenyellow=0, end_greenyellow=50),
                GreenYellowInterval(start_greenyellow=120, end_greenyellow=170)
            ],
            sg2=[
                GreenYellowInterval(start_greenyellow=60, end_greenyellow=110),
                GreenYellowInterval(start_greenyellow=180, end_greenyellow=230)
            ],
            sg3=[
                GreenYellowInterval(start_greenyellow=0, end_greenyellow=50),
                GreenYellowInterval(start_greenyellow=120, end_greenyellow=170)
            ]),
                                period=240)

        signalgroup3 = TestFTSValidationOfBounds.get_default_signalgroup(
            name="sg3", max_red=60)
        intersection = TestFTSValidationOfBounds.get_default_intersection(
            additional_signalgroups=[signalgroup3])

        with self.assertRaises(SafetyViolation):
            # WHEN validating
            validate_bounds(intersection=intersection, fts=fts)
    def test_comparing_different_greenyellow_intervals(self):
        """ Test comparing two different fixed-time schedules (different ids) """
        # GIVEN
        input_dict = TestFTSInputValidation.get_default_inputs()
        input_dict2 = TestFTSInputValidation.get_default_inputs(
        )  # ensure not same references
        input_dict2["greenyellow_intervals"]["sg2"][0] = GreenYellowInterval(
            start_greenyellow=13, end_greenyellow=23)
        fts1 = FixedTimeSchedule(**input_dict)
        fts2 = FixedTimeSchedule(**input_dict2)

        # WHEN
        same = fts1 == fts2

        self.assertEqual(same, False)
 def test_times_exceeding_period_duration(self) -> None:
     """ Test providing no GreenYellowInterval for the interval """
     # GIVEN
     input_dict = TestFTSInputValidation.get_default_inputs()
     for time in ["start_greenyellow", "end_greenyellow"]:
         with self.subTest(f"{time} exceeding period duration"):
             greenyellow_interval_dict = dict(start_greenyellow=1,
                                              end_greenyellow=10)
             greenyellow_interval_dict[time] = input_dict["period"] + 1
             input_dict["greenyellow_intervals"]["id3"] = [
                 GreenYellowInterval(**greenyellow_interval_dict)
             ]
             with self.assertRaises(ValueError):
                 # WHEN initializing the fts
                 FixedTimeSchedule(**input_dict)
    def test_incorrect_greenyellow_trail(self) -> None:
        """
        Test that validation of incorrect greenyellow-trail raises SafetyViolation.
        :return:
        """
        # GIVEN
        min_greenyellow_trail = 20
        max_greenyellow_trail = 30
        greenyellow_lead = GreenyellowLead(from_id="sg3", to_id="sg4",
                                           min_seconds=min_greenyellow_trail, max_seconds=max_greenyellow_trail)
        fts = FixedTimeSchedule(greenyellow_intervals=dict(
            sg1=[GreenYellowInterval(start_greenyellow=15, end_greenyellow=35)],
            sg2=[GreenYellowInterval(start_greenyellow=45, end_greenyellow=65)],
            sg3=[GreenYellowInterval(start_greenyellow=10, end_greenyellow=40),
                 GreenYellowInterval(start_greenyellow=50, end_greenyellow=70)],
            sg4=[GreenYellowInterval(start_greenyellow=30, end_greenyellow=40),
                 GreenYellowInterval(start_greenyellow=69, end_greenyellow=90)]),
            period=100)
        signalgroup3 = TestFTSOtherSGRelationValidation.get_default_signalgroup(name="sg3")
        signalgroup4 = TestFTSOtherSGRelationValidation.get_default_signalgroup(name="sg4")
        intersection = TestFTSOtherSGRelationValidation.get_default_intersection(
            additional_signalgroups=[signalgroup3, signalgroup4], greenyellow_leads=[greenyellow_lead])

        for trail_time in [min_greenyellow_trail - 1, max_greenyellow_trail + 1]:
            for interval_shift in range(2):
                fts_copy = deepcopy(fts)

                # adjust schedule to the specified greenyellow-lead
                for index, greenyellow_interval in enumerate(fts_copy.get_greenyellow_intervals(signalgroup4)):
                    greenyellow_end = (greenyellow_interval.end_greenyellow - trail_time) % fts_copy.period
                    greenyellow_start = (greenyellow_end - 20) % fts_copy.period
                    fts_copy._greenyellow_intervals["sg3"][index] = GreenYellowInterval(
                        start_greenyellow=greenyellow_start, end_greenyellow=greenyellow_end)

                fts_copy._greenyellow_intervals["sg4"] = fts_copy._greenyellow_intervals["sg4"][:interval_shift] + \
                    fts_copy._greenyellow_intervals["sg4"][interval_shift:]

                with self.subTest(f"greenyellow_lead={trail_time}, interval_shift={interval_shift}"):
                    with self.assertRaises(SafetyViolation):
                        # WHEN validating
                        validate_other_sg_relations(intersection=intersection, fts=fts_copy)
    def test_correct_sync_starts(self) -> None:
        """
        Test that validation of correct synchronous start passes.
        :return:
        """
        # GIVEN
        sync_start = SyncStart(from_id="sg3", to_id="sg4")
        fts = FixedTimeSchedule(greenyellow_intervals=dict(
            sg1=[GreenYellowInterval(start_greenyellow=15, end_greenyellow=35)],
            sg2=[GreenYellowInterval(start_greenyellow=45, end_greenyellow=65)],
            sg3=[GreenYellowInterval(start_greenyellow=10, end_greenyellow=40),
                 GreenYellowInterval(start_greenyellow=50, end_greenyellow=70)],
            sg4=[GreenYellowInterval(start_greenyellow=10, end_greenyellow=30),
                 GreenYellowInterval(start_greenyellow=50, end_greenyellow=60)]),
            period=100)
        signalgroup3 = TestFTSOtherSGRelationValidation.get_default_signalgroup(name="sg3")
        signalgroup4 = TestFTSOtherSGRelationValidation.get_default_signalgroup(name="sg4")
        intersection = TestFTSOtherSGRelationValidation.get_default_intersection(
            additional_signalgroups=[signalgroup3, signalgroup4], sync_starts=[sync_start])

        # WHEN validating
        validate_other_sg_relations(intersection=intersection, fts=fts)
    def test_no_shift_possible(self) -> None:
        """ Test finding no shift is possible for a schedule without an unambiguous shift"""
        # GIVEN
        sync_start = SyncStart(from_id="sg1", to_id="sg2")
        fts = FixedTimeSchedule(greenyellow_intervals=dict(
            sg1=[GreenYellowInterval(start_greenyellow=10, end_greenyellow=30),
                 GreenYellowInterval(start_greenyellow=40, end_greenyellow=50),
                 GreenYellowInterval(start_greenyellow=60, end_greenyellow=80)],
            sg2=[GreenYellowInterval(start_greenyellow=10, end_greenyellow=30),
                 GreenYellowInterval(start_greenyellow=40, end_greenyellow=50),
                 GreenYellowInterval(start_greenyellow=60, end_greenyellow=80)]),
            period=100)

        # Swap two intervals (we do this after initialization as otherwise we would get a ValueError (not correct order
        #  of greenyellow intervals)
        fts._greenyellow_intervals["sg2"][:2] = reversed(fts._greenyellow_intervals["sg2"][:2])

        # WHEN
        shift = get_other_sg_relation_shift(other_relation=sync_start, fts=fts)

        # THEN
        self.assertEqual(shift, None)