diff --git a/src/ext-test/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSourceTest.java b/src/ext-test/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSourceTest.java index cfe080c47dc..0f05d0d49b3 100644 --- a/src/ext-test/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSourceTest.java +++ b/src/ext-test/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSourceTest.java @@ -1,34 +1,17 @@ package org.opentripplanner.ext.siri; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; -import java.time.Duration; -import java.time.LocalDate; -import java.time.ZoneId; import java.util.List; import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.IntStream; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.opentripplanner.DateTimeHelper; -import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; -import org.opentripplanner.model.StopTime; -import org.opentripplanner.model.calendar.CalendarServiceData; -import org.opentripplanner.transit.model._data.TransitModelForTest; -import org.opentripplanner.transit.model.framework.Deduplicator; -import org.opentripplanner.transit.model.framework.FeedScopedId; -import org.opentripplanner.transit.model.network.Route; -import org.opentripplanner.transit.model.network.TripPattern; -import org.opentripplanner.transit.model.site.RegularStop; -import org.opentripplanner.transit.model.site.Station; -import org.opentripplanner.transit.model.site.StopLocation; +import org.opentripplanner.model.PickDrop; import org.opentripplanner.transit.model.timetable.RealTimeState; import org.opentripplanner.transit.model.timetable.Trip; import org.opentripplanner.transit.model.timetable.TripOnServiceDate; import org.opentripplanner.transit.model.timetable.TripTimes; import org.opentripplanner.transit.model.timetable.TripTimesFactory; -import org.opentripplanner.transit.model.timetable.TripTimesStringBuilder; import org.opentripplanner.transit.service.DefaultTransitService; import org.opentripplanner.transit.service.StopModel; import org.opentripplanner.transit.service.TransitModel; @@ -36,13 +19,13 @@ import org.opentripplanner.updater.TimetableSnapshotSourceParameters; import org.opentripplanner.updater.spi.UpdateError; import org.opentripplanner.updater.spi.UpdateResult; -import uk.org.siri.siri20.EstimatedTimetableDeliveryStructure; +import org.opentripplanner.updater.trip.RealtimeTestEnvironment; class SiriTimetableSnapshotSourceTest { @Test void testCancelTrip() { - var env = new RealtimeTestEnvironment(); + var env = RealtimeTestEnvironment.siri(); assertEquals(RealTimeState.SCHEDULED, env.getTripTimesForTrip(env.trip1).getRealTimeState()); @@ -59,7 +42,7 @@ void testCancelTrip() { @Test void testAddJourney() { - var env = new RealtimeTestEnvironment(); + var env = RealtimeTestEnvironment.siri(); var updates = new SiriEtBuilder(env.getDateTimeHelper()) .withEstimatedVehicleJourneyCode("newJourney") @@ -82,7 +65,7 @@ void testAddJourney() { @Test void testReplaceJourney() { - var env = new RealtimeTestEnvironment(); + var env = RealtimeTestEnvironment.siri(); var updates = new SiriEtBuilder(env.getDateTimeHelper()) .withEstimatedVehicleJourneyCode("newJourney") @@ -115,7 +98,7 @@ void testReplaceJourney() { */ @Test void testUpdateJourneyWithDatedVehicleJourneyRef() { - var env = new RealtimeTestEnvironment(); + var env = RealtimeTestEnvironment.siri(); var updates = updatedJourneyBuilder(env) .withDatedVehicleJourneyRef(env.trip1.getId().getId()) @@ -134,11 +117,13 @@ void testUpdateJourneyWithDatedVehicleJourneyRef() { */ @Test void testUpdateJourneyWithFramedVehicleJourneyRef() { - var env = new RealtimeTestEnvironment(); + var env = RealtimeTestEnvironment.siri(); var updates = updatedJourneyBuilder(env) .withFramedVehicleJourneyRef(builder -> - builder.withServiceDate(env.serviceDate).withVehicleJourneyRef(env.trip1.getId().getId()) + builder + .withServiceDate(RealtimeTestEnvironment.SERVICE_DATE) + .withVehicleJourneyRef(env.trip1.getId().getId()) ) .buildEstimatedTimetableDeliveries(); var result = env.applyEstimatedTimetable(updates); @@ -151,7 +136,7 @@ void testUpdateJourneyWithFramedVehicleJourneyRef() { */ @Test void testUpdateJourneyWithoutJourneyRef() { - var env = new RealtimeTestEnvironment(); + var env = RealtimeTestEnvironment.siri(); var updates = updatedJourneyBuilder(env).buildEstimatedTimetableDeliveries(); var result = env.applyEstimatedTimetable(updates); @@ -164,7 +149,7 @@ void testUpdateJourneyWithoutJourneyRef() { */ @Test void testUpdateJourneyWithFuzzyMatching() { - var env = new RealtimeTestEnvironment(); + var env = RealtimeTestEnvironment.siri(); var updates = updatedJourneyBuilder(env).buildEstimatedTimetableDeliveries(); var result = env.applyEstimatedTimetableWithFuzzyMatcher(updates); @@ -178,11 +163,11 @@ void testUpdateJourneyWithFuzzyMatching() { */ @Test void testUpdateJourneyWithFuzzyMatchingAndMissingAimedDepartureTime() { - var env = new RealtimeTestEnvironment(); + var env = RealtimeTestEnvironment.siri(); var updates = new SiriEtBuilder(env.getDateTimeHelper()) .withFramedVehicleJourneyRef(builder -> - builder.withServiceDate(env.serviceDate).withVehicleJourneyRef("XXX") + builder.withServiceDate(RealtimeTestEnvironment.SERVICE_DATE).withVehicleJourneyRef("XXX") ) .withEstimatedCalls(builder -> builder @@ -203,7 +188,7 @@ void testUpdateJourneyWithFuzzyMatchingAndMissingAimedDepartureTime() { */ @Test void testChangeQuay() { - var env = new RealtimeTestEnvironment(); + var env = RealtimeTestEnvironment.siri(); var updates = new SiriEtBuilder(env.getDateTimeHelper()) .withDatedVehicleJourneyRef(env.trip1.getId().getId()) @@ -226,7 +211,7 @@ void testChangeQuay() { @Test void testCancelStop() { - var env = new RealtimeTestEnvironment(); + var env = RealtimeTestEnvironment.siri(); var updates = new SiriEtBuilder(env.getDateTimeHelper()) .withDatedVehicleJourneyRef(env.trip2.getId().getId()) @@ -254,7 +239,7 @@ void testCancelStop() { @Test @Disabled("Not supported yet") void testAddStop() { - var env = new RealtimeTestEnvironment(); + var env = RealtimeTestEnvironment.siri(); var updates = new SiriEtBuilder(env.getDateTimeHelper()) .withDatedVehicleJourneyRef(env.trip1.getId().getId()) @@ -287,7 +272,7 @@ void testAddStop() { @Test void testNotMonitored() { - var env = new RealtimeTestEnvironment(); + var env = RealtimeTestEnvironment.siri(); var updates = new SiriEtBuilder(env.getDateTimeHelper()) .withMonitored(false) @@ -300,7 +285,7 @@ void testNotMonitored() { @Test void testReplaceJourneyWithoutEstimatedVehicleJourneyCode() { - var env = new RealtimeTestEnvironment(); + var env = RealtimeTestEnvironment.siri(); var updates = new SiriEtBuilder(env.getDateTimeHelper()) .withDatedVehicleJourneyRef("newJourney") @@ -325,7 +310,7 @@ void testReplaceJourneyWithoutEstimatedVehicleJourneyCode() { @Test void testNegativeHopTime() { - var env = new RealtimeTestEnvironment(); + var env = RealtimeTestEnvironment.siri(); var updates = new SiriEtBuilder(env.getDateTimeHelper()) .withDatedVehicleJourneyRef(env.trip1.getId().getId()) @@ -345,7 +330,7 @@ void testNegativeHopTime() { @Test void testNegativeDwellTime() { - var env = new RealtimeTestEnvironment(); + var env = RealtimeTestEnvironment.siri(); var updates = new SiriEtBuilder(env.getDateTimeHelper()) .withDatedVehicleJourneyRef(env.trip2.getId().getId()) @@ -370,7 +355,7 @@ void testNegativeDwellTime() { @Test @Disabled("Not supported yet") void testExtraUnknownStop() { - var env = new RealtimeTestEnvironment(); + var env = RealtimeTestEnvironment.siri(); var updates = new SiriEtBuilder(env.getDateTimeHelper()) .withDatedVehicleJourneyRef(env.trip1.getId().getId()) @@ -413,231 +398,4 @@ private static void assertTripUpdated(RealtimeTestEnvironment env) { env.getRealtimeTimetable(env.trip1) ); } - - private static class RealtimeTestEnvironment { - - public static final FeedScopedId SERVICE_ID = TransitModelForTest.id("SERVICE_ID"); - private final TransitModelForTest testModel = TransitModelForTest.of(); - public final ZoneId timeZone = ZoneId.of(TransitModelForTest.TIME_ZONE_ID); - public final Station stationA = testModel.station("A").build(); - public final Station stationB = testModel.station("B").build(); - public final Station stationC = testModel.station("C").build(); - public final Station stationD = testModel.station("D").build(); - public final RegularStop stopA1 = testModel.stop("A1").withParentStation(stationA).build(); - public final RegularStop stopB1 = testModel.stop("B1").withParentStation(stationB).build(); - public final RegularStop stopB2 = testModel.stop("B2").withParentStation(stationB).build(); - public final RegularStop stopC1 = testModel.stop("C1").withParentStation(stationC).build(); - public final RegularStop stopD1 = testModel.stop("D1").withParentStation(stationD).build(); - public final StopModel stopModel = testModel - .stopModelBuilder() - .withRegularStop(stopA1) - .withRegularStop(stopB1) - .withRegularStop(stopB2) - .withRegularStop(stopC1) - .withRegularStop(stopD1) - .build(); - - public final LocalDate serviceDate = LocalDate.of(2024, 5, 8); - public TransitModel transitModel; - public SiriTimetableSnapshotSource snapshotSource; - - public final FeedScopedId operator1Id = TransitModelForTest.id("TestOperator1"); - public final FeedScopedId route1Id = TransitModelForTest.id("TestRoute1"); - public Trip trip1; - public Trip trip2; - - public final DateTimeHelper dateTimeHelper = new DateTimeHelper(timeZone, serviceDate); - - public RealtimeTestEnvironment() { - transitModel = new TransitModel(stopModel, new Deduplicator()); - transitModel.initTimeZone(timeZone); - transitModel.addAgency(TransitModelForTest.AGENCY); - - Route route1 = TransitModelForTest.route(route1Id).build(); - - trip1 = - createTrip( - "TestTrip1", - route1, - List.of(new Stop(stopA1, 10, 11), new Stop(stopB1, 20, 21)) - ); - trip2 = - createTrip( - "TestTrip2", - route1, - List.of(new Stop(stopA1, 60, 61), new Stop(stopB1, 70, 71), new Stop(stopC1, 80, 81)) - ); - - CalendarServiceData calendarServiceData = new CalendarServiceData(); - calendarServiceData.putServiceDatesForServiceId( - SERVICE_ID, - List.of(serviceDate.minusDays(1), serviceDate, serviceDate.plusDays(1)) - ); - transitModel.getServiceCodes().put(SERVICE_ID, 0); - transitModel.updateCalendarServiceData(true, calendarServiceData, DataImportIssueStore.NOOP); - - transitModel.index(); - - var parameters = new TimetableSnapshotSourceParameters(Duration.ZERO, false); - snapshotSource = new SiriTimetableSnapshotSource(parameters, transitModel); - } - - private record Stop(RegularStop stop, int arrivalTime, int departureTime) {} - - private Trip createTrip(String id, Route route, List stops) { - var trip = Trip.of(id(id)).withRoute(route).withServiceId(SERVICE_ID).build(); - - var tripOnServiceDate = TripOnServiceDate - .of(trip.getId()) - .withTrip(trip) - .withServiceDate(serviceDate) - .build(); - - transitModel.addTripOnServiceDate(tripOnServiceDate.getId(), tripOnServiceDate); - - var stopTimes = IntStream - .range(0, stops.size()) - .mapToObj(i -> { - var stop = stops.get(i); - return createStopTime(trip, i, stop.stop(), stop.arrivalTime(), stop.departureTime()); - }) - .collect(Collectors.toList()); - - TripTimes tripTimes = TripTimesFactory.tripTimes(trip, stopTimes, null); - - final TripPattern pattern = TransitModelForTest - .tripPattern(id + "Pattern", route) - .withStopPattern(TransitModelForTest.stopPattern(stops.stream().map(Stop::stop).toList())) - .build(); - pattern.add(tripTimes); - - transitModel.addTripPattern(pattern.getId(), pattern); - - return trip; - } - - public FeedScopedId id(String id) { - return TransitModelForTest.id(id); - } - - /** - * Returns a new fresh TransitService - */ - public TransitService getTransitService() { - return new DefaultTransitService(transitModel); - } - - public EntityResolver getEntityResolver() { - return new EntityResolver(getTransitService(), getFeedId()); - } - - public TripPattern getPatternForTrip(FeedScopedId tripId) { - return getPatternForTrip(tripId, serviceDate); - } - - public TripPattern getPatternForTrip(FeedScopedId tripId, LocalDate serviceDate) { - var transitService = getTransitService(); - var trip = transitService.getTripOnServiceDateById(tripId); - return transitService.getPatternForTrip(trip.getTrip(), serviceDate); - } - - /** - * Find the current TripTimes for a trip id on the default serviceDate - */ - public TripTimes getTripTimesForTrip(Trip trip) { - return getTripTimesForTrip(trip.getId(), serviceDate); - } - - public String getRealtimeTimetable(String tripId) { - return getRealtimeTimetable(id(tripId), serviceDate); - } - - public String getRealtimeTimetable(Trip trip) { - return getRealtimeTimetable(trip.getId(), serviceDate); - } - - public String getRealtimeTimetable(FeedScopedId tripId, LocalDate serviceDate) { - var tt = getTripTimesForTrip(tripId, serviceDate); - var pattern = getPatternForTrip(tripId); - - return TripTimesStringBuilder.encodeTripTimes(tt, pattern); - } - - public String getScheduledTimetable(String tripId) { - return getScheduledTimetable(id(tripId)); - } - - public String getScheduledTimetable(FeedScopedId tripId) { - var pattern = getPatternForTrip(tripId); - var tt = pattern.getScheduledTimetable().getTripTimes(tripId); - - return TripTimesStringBuilder.encodeTripTimes(tt, pattern); - } - - /** - * Find the current TripTimes for a trip id on the default serviceDate - */ - public TripTimes getTripTimesForTrip(String id) { - return getTripTimesForTrip(id(id), serviceDate); - } - - /** - * Find the current TripTimes for a trip id on a serviceDate - */ - public TripTimes getTripTimesForTrip(FeedScopedId tripId, LocalDate serviceDate) { - var transitService = getTransitService(); - var trip = transitService.getTripOnServiceDateById(tripId).getTrip(); - var pattern = transitService.getPatternForTrip(trip, serviceDate); - var timetable = transitService.getTimetableForTripPattern(pattern, serviceDate); - return timetable.getTripTimes(trip); - } - - public DateTimeHelper getDateTimeHelper() { - return dateTimeHelper; - } - - private StopTime createStopTime( - Trip trip, - int stopSequence, - StopLocation stop, - int arrivalTime, - int departureTime - ) { - var st = new StopTime(); - st.setTrip(trip); - st.setStopSequence(stopSequence); - st.setStop(stop); - st.setArrivalTime(arrivalTime); - st.setDepartureTime(departureTime); - return st; - } - - public String getFeedId() { - return TransitModelForTest.FEED_ID; - } - - public UpdateResult applyEstimatedTimetable(List updates) { - return applyEstimatedTimetable(updates, null); - } - - public UpdateResult applyEstimatedTimetableWithFuzzyMatcher( - List updates - ) { - SiriFuzzyTripMatcher siriFuzzyTripMatcher = new SiriFuzzyTripMatcher(getTransitService()); - return applyEstimatedTimetable(updates, siriFuzzyTripMatcher); - } - - private UpdateResult applyEstimatedTimetable( - List updates, - SiriFuzzyTripMatcher siriFuzzyTripMatcher - ) { - return this.snapshotSource.applyEstimatedTimetable( - siriFuzzyTripMatcher, - getEntityResolver(), - getFeedId(), - false, - updates - ); - } - } } diff --git a/src/ext/java/org/opentripplanner/ext/siri/SiriFuzzyTripMatcher.java b/src/ext/java/org/opentripplanner/ext/siri/SiriFuzzyTripMatcher.java index a0b56dec58a..cc76ad3ac4f 100644 --- a/src/ext/java/org/opentripplanner/ext/siri/SiriFuzzyTripMatcher.java +++ b/src/ext/java/org/opentripplanner/ext/siri/SiriFuzzyTripMatcher.java @@ -66,9 +66,9 @@ public static SiriFuzzyTripMatcher of(TransitService transitService) { } /** - * Constructor with package access for tests only. + * Constructor with public access for tests only. */ - SiriFuzzyTripMatcher(TransitService transitService) { + public SiriFuzzyTripMatcher(TransitService transitService) { this.transitService = transitService; initCache(this.transitService); } diff --git a/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java b/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java index 7bf1a826c1c..7ff66f0b90e 100644 --- a/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java +++ b/src/main/java/org/opentripplanner/transit/model/timetable/TripTimes.java @@ -12,7 +12,7 @@ /** * A TripTimes represents the arrival and departure times for a single trip in a timetable. It is - * one of the core class used for transit routing. This interface allow different kind of trip + * one of the core class used for transit routing. This interface allows different kind of trip * to implement their own trip times. Scheduled/planned trips should be immutable, real-time * trip times should allow updates and more info, frequency-based trips can use a more compact * implementation, and Flex may expose part of the trip as a "scheduled/regular" stop-to-stop diff --git a/src/test/java/org/opentripplanner/updater/trip/RealtimeTestEnvironment.java b/src/test/java/org/opentripplanner/updater/trip/RealtimeTestEnvironment.java new file mode 100644 index 00000000000..1b385df153b --- /dev/null +++ b/src/test/java/org/opentripplanner/updater/trip/RealtimeTestEnvironment.java @@ -0,0 +1,338 @@ +package org.opentripplanner.updater.trip; + +import com.google.transit.realtime.GtfsRealtime; +import java.time.Duration; +import java.time.LocalDate; +import java.time.ZoneId; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import org.opentripplanner.DateTimeHelper; +import org.opentripplanner.ext.siri.EntityResolver; +import org.opentripplanner.ext.siri.SiriFuzzyTripMatcher; +import org.opentripplanner.ext.siri.SiriTimetableSnapshotSource; +import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore; +import org.opentripplanner.model.StopTime; +import org.opentripplanner.model.TimetableSnapshot; +import org.opentripplanner.model.calendar.CalendarServiceData; +import org.opentripplanner.transit.model._data.TransitModelForTest; +import org.opentripplanner.transit.model.framework.Deduplicator; +import org.opentripplanner.transit.model.framework.FeedScopedId; +import org.opentripplanner.transit.model.network.Route; +import org.opentripplanner.transit.model.network.TripPattern; +import org.opentripplanner.transit.model.site.RegularStop; +import org.opentripplanner.transit.model.site.Station; +import org.opentripplanner.transit.model.site.StopLocation; +import org.opentripplanner.transit.model.timetable.Trip; +import org.opentripplanner.transit.model.timetable.TripOnServiceDate; +import org.opentripplanner.transit.model.timetable.TripTimes; +import org.opentripplanner.transit.model.timetable.TripTimesFactory; +import org.opentripplanner.transit.model.timetable.TripTimesStringBuilder; +import org.opentripplanner.transit.service.DefaultTransitService; +import org.opentripplanner.transit.service.StopModel; +import org.opentripplanner.transit.service.TransitModel; +import org.opentripplanner.transit.service.TransitService; +import org.opentripplanner.updater.TimetableSnapshotSourceParameters; +import org.opentripplanner.updater.spi.UpdateResult; +import uk.org.siri.siri20.EstimatedTimetableDeliveryStructure; + +/** + * This class exists so that you can share the data building logic for GTFS and Siri tests. + * Since it's not possible to add a Siri and GTFS updater to the transit model at the same time, + * they each have their own test environment. + *

+ * It is however a goal to change that and then these two can be combined. + */ +public final class RealtimeTestEnvironment { + + private static final TimetableSnapshotSourceParameters PARAMETERS = new TimetableSnapshotSourceParameters( + Duration.ZERO, + false + ); + public static final LocalDate SERVICE_DATE = LocalDate.of(2024, 5, 8); + public static final FeedScopedId SERVICE_ID = TransitModelForTest.id("CAL_1"); + private final TransitModelForTest testModel = TransitModelForTest.of(); + public final ZoneId timeZone = ZoneId.of(TransitModelForTest.TIME_ZONE_ID); + public final Station stationA = testModel.station("A").build(); + public final Station stationB = testModel.station("B").build(); + public final Station stationC = testModel.station("C").build(); + public final Station stationD = testModel.station("D").build(); + public final RegularStop stopA1 = testModel.stop("A1").withParentStation(stationA).build(); + public final RegularStop stopB1 = testModel.stop("B1").withParentStation(stationB).build(); + public final RegularStop stopB2 = testModel.stop("B2").withParentStation(stationB).build(); + public final RegularStop stopC1 = testModel.stop("C1").withParentStation(stationC).build(); + public final RegularStop stopD1 = testModel.stop("D1").withParentStation(stationD).build(); + public final StopModel stopModel = testModel + .stopModelBuilder() + .withRegularStop(stopA1) + .withRegularStop(stopB1) + .withRegularStop(stopB2) + .withRegularStop(stopC1) + .withRegularStop(stopD1) + .build(); + public final FeedScopedId operator1Id = TransitModelForTest.id("TestOperator1"); + public final FeedScopedId route1Id = TransitModelForTest.id("TestRoute1"); + public final Trip trip1; + public final Trip trip2; + public final TransitModel transitModel; + private final SiriTimetableSnapshotSource siriSource; + private final TimetableSnapshotSource gtfsSource; + private final DateTimeHelper dateTimeHelper; + + private enum SourceType { + GTFS_RT, + SIRI, + } + + /** + * Siri and GTFS-RT cannot be run at the same time, so you need to decide. + */ + public static RealtimeTestEnvironment siri() { + return new RealtimeTestEnvironment(SourceType.SIRI); + } + + /** + * Siri and GTFS-RT cannot be run at the same time, so you need to decide. + */ + public static RealtimeTestEnvironment gtfs() { + return new RealtimeTestEnvironment(SourceType.GTFS_RT); + } + + private RealtimeTestEnvironment(SourceType sourceType) { + transitModel = new TransitModel(stopModel, new Deduplicator()); + transitModel.initTimeZone(timeZone); + transitModel.addAgency(TransitModelForTest.AGENCY); + + Route route1 = TransitModelForTest.route(route1Id).build(); + + trip1 = + createTrip("TestTrip1", route1, List.of(new Stop(stopA1, 10, 11), new Stop(stopB1, 20, 21))); + trip2 = + createTrip( + "TestTrip2", + route1, + List.of(new Stop(stopA1, 60, 61), new Stop(stopB1, 70, 71), new Stop(stopC1, 80, 81)) + ); + + CalendarServiceData calendarServiceData = new CalendarServiceData(); + calendarServiceData.putServiceDatesForServiceId( + SERVICE_ID, + List.of(SERVICE_DATE.minusDays(1), SERVICE_DATE, SERVICE_DATE.plusDays(1)) + ); + transitModel.getServiceCodes().put(SERVICE_ID, 0); + transitModel.updateCalendarServiceData(true, calendarServiceData, DataImportIssueStore.NOOP); + + transitModel.index(); + + // SIRI and GTFS-RT cannot be registered with the transit model at the same time + // we are actively refactoring to remove this restriction + // for the time being you cannot run a SIRI and GTFS-RT test at the same time + if (sourceType == SourceType.SIRI) { + siriSource = new SiriTimetableSnapshotSource(PARAMETERS, transitModel); + gtfsSource = null; + } else { + gtfsSource = new TimetableSnapshotSource(PARAMETERS, transitModel); + siriSource = null; + } + dateTimeHelper = new DateTimeHelper(timeZone, RealtimeTestEnvironment.SERVICE_DATE); + } + + public static FeedScopedId id(String id) { + return TransitModelForTest.id(id); + } + + /** + * Returns a new fresh TransitService + */ + public TransitService getTransitService() { + return new DefaultTransitService(transitModel); + } + + /** + * Find the current TripTimes for a trip id on a serviceDate + */ + public TripTimes getTripTimesForTrip(FeedScopedId tripId, LocalDate serviceDate) { + var transitService = getTransitService(); + var trip = transitService.getTripOnServiceDateById(tripId).getTrip(); + var pattern = transitService.getPatternForTrip(trip, serviceDate); + var timetable = transitService.getTimetableForTripPattern(pattern, serviceDate); + return timetable.getTripTimes(trip); + } + + public String getFeedId() { + return TransitModelForTest.FEED_ID; + } + + public EntityResolver getEntityResolver() { + return new EntityResolver(getTransitService(), getFeedId()); + } + + public TripPattern getPatternForTrip(FeedScopedId tripId) { + return getPatternForTrip(tripId, RealtimeTestEnvironment.SERVICE_DATE); + } + + public TripPattern getPatternForTrip(FeedScopedId tripId, LocalDate serviceDate) { + var transitService = getTransitService(); + var trip = transitService.getTripOnServiceDateById(tripId); + return transitService.getPatternForTrip(trip.getTrip(), serviceDate); + } + + /** + * Find the current TripTimes for a trip id on the default serviceDate + */ + public TripTimes getTripTimesForTrip(Trip trip) { + return getTripTimesForTrip(trip.getId(), SERVICE_DATE); + } + + /** + * Find the current TripTimes for a trip id on the default serviceDate + */ + public TripTimes getTripTimesForTrip(String id) { + return getTripTimesForTrip(id(id), SERVICE_DATE); + } + + public DateTimeHelper getDateTimeHelper() { + return dateTimeHelper; + } + + public TripPattern getPatternForTrip(Trip trip) { + return transitModel.getTransitModelIndex().getPatternForTrip().get(trip); + } + + public TimetableSnapshot getTimetableSnapshot() { + if (siriSource != null) { + return siriSource.getTimetableSnapshot(); + } else { + return gtfsSource.getTimetableSnapshot(); + } + } + + public String getRealtimeTimetable(String tripId) { + return getRealtimeTimetable(id(tripId), SERVICE_DATE); + } + + public String getRealtimeTimetable(Trip trip) { + return getRealtimeTimetable(trip.getId(), SERVICE_DATE); + } + + public String getRealtimeTimetable(FeedScopedId tripId, LocalDate serviceDate) { + var tt = getTripTimesForTrip(tripId, serviceDate); + var pattern = getPatternForTrip(tripId); + + return TripTimesStringBuilder.encodeTripTimes(tt, pattern); + } + + public String getScheduledTimetable(String tripId) { + return getScheduledTimetable(id(tripId)); + } + + public String getScheduledTimetable(FeedScopedId tripId) { + var pattern = getPatternForTrip(tripId); + var tt = pattern.getScheduledTimetable().getTripTimes(tripId); + + return TripTimesStringBuilder.encodeTripTimes(tt, pattern); + } + + // SIRI updates + + public UpdateResult applyEstimatedTimetableWithFuzzyMatcher( + List updates + ) { + SiriFuzzyTripMatcher siriFuzzyTripMatcher = new SiriFuzzyTripMatcher(getTransitService()); + return applyEstimatedTimetable(updates, siriFuzzyTripMatcher); + } + + public UpdateResult applyEstimatedTimetable(List updates) { + return siriSource.applyEstimatedTimetable( + null, + getEntityResolver(), + getFeedId(), + false, + updates + ); + } + + // GTFS-RT updates + + public UpdateResult applyTripUpdate(GtfsRealtime.TripUpdate update) { + return applyTripUpdates(List.of(update)); + } + + public UpdateResult applyTripUpdates(List updates) { + Objects.requireNonNull(gtfsSource, "Test environment is configured for SIRI only"); + return gtfsSource.applyTripUpdates( + null, + BackwardsDelayPropagationType.REQUIRED_NO_DATA, + true, + updates, + getFeedId() + ); + } + + // private methods + + private UpdateResult applyEstimatedTimetable( + List updates, + SiriFuzzyTripMatcher siriFuzzyTripMatcher + ) { + Objects.requireNonNull(siriSource, "Test environment is configured for GTFS-RT only"); + return siriSource.applyEstimatedTimetable( + siriFuzzyTripMatcher, + getEntityResolver(), + getFeedId(), + false, + updates + ); + } + + private Trip createTrip(String id, Route route, List stops) { + var trip = Trip.of(id(id)).withRoute(route).withServiceId(SERVICE_ID).build(); + + var tripOnServiceDate = TripOnServiceDate + .of(trip.getId()) + .withTrip(trip) + .withServiceDate(SERVICE_DATE) + .build(); + + transitModel.addTripOnServiceDate(tripOnServiceDate.getId(), tripOnServiceDate); + + var stopTimes = IntStream + .range(0, stops.size()) + .mapToObj(i -> { + var stop = stops.get(i); + return createStopTime(trip, i, stop.stop(), stop.arrivalTime(), stop.departureTime()); + }) + .collect(Collectors.toList()); + + TripTimes tripTimes = TripTimesFactory.tripTimes(trip, stopTimes, null); + + final TripPattern pattern = TransitModelForTest + .tripPattern(id + "Pattern", route) + .withStopPattern(TransitModelForTest.stopPattern(stops.stream().map(Stop::stop).toList())) + .build(); + pattern.add(tripTimes); + + transitModel.addTripPattern(pattern.getId(), pattern); + + return trip; + } + + private StopTime createStopTime( + Trip trip, + int stopSequence, + StopLocation stop, + int arrivalTime, + int departureTime + ) { + var st = new StopTime(); + st.setTrip(trip); + st.setStopSequence(stopSequence); + st.setStop(stop); + st.setArrivalTime(arrivalTime); + st.setDepartureTime(departureTime); + return st; + } + + protected record Stop(RegularStop stop, int arrivalTime, int departureTime) {} +} diff --git a/src/test/java/org/opentripplanner/updater/trip/TimetableSnapshotSourceTest.java b/src/test/java/org/opentripplanner/updater/trip/TimetableSnapshotSourceTest.java index eeb7368091c..36ba293e508 100644 --- a/src/test/java/org/opentripplanner/updater/trip/TimetableSnapshotSourceTest.java +++ b/src/test/java/org/opentripplanner/updater/trip/TimetableSnapshotSourceTest.java @@ -1,6 +1,7 @@ package org.opentripplanner.updater.trip; import static com.google.transit.realtime.GtfsRealtime.TripDescriptor.ScheduleRelationship.ADDED; +import static com.google.transit.realtime.GtfsRealtime.TripDescriptor.ScheduleRelationship.CANCELED; import static com.google.transit.realtime.GtfsRealtime.TripDescriptor.ScheduleRelationship.SCHEDULED; import static com.google.transit.realtime.GtfsRealtime.TripUpdate.StopTimeUpdate.ScheduleRelationship.SKIPPED; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -9,12 +10,10 @@ import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.opentripplanner.updater.spi.UpdateError.UpdateErrorType.NO_SERVICE_ON_DATE; import static org.opentripplanner.updater.trip.BackwardsDelayPropagationType.REQUIRED_NO_DATA; import static org.opentripplanner.updater.trip.TimetableSnapshotSourceTest.SameAssert.NotSame; import static org.opentripplanner.updater.trip.TimetableSnapshotSourceTest.SameAssert.Same; -import com.google.protobuf.InvalidProtocolBufferException; import com.google.transit.realtime.GtfsRealtime.TripDescriptor; import com.google.transit.realtime.GtfsRealtime.TripDescriptor.ScheduleRelationship; import com.google.transit.realtime.GtfsRealtime.TripUpdate; @@ -35,6 +34,7 @@ import org.junit.jupiter.params.provider.MethodSource; import org.opentripplanner.ConstantsForTests; import org.opentripplanner.TestOtpModel; +import org.opentripplanner._support.time.ZoneIds; import org.opentripplanner.framework.i18n.NonLocalizedString; import org.opentripplanner.framework.time.ServiceDateUtils; import org.opentripplanner.model.PickDrop; @@ -54,12 +54,18 @@ public class TimetableSnapshotSourceTest { private static final LocalDate SERVICE_DATE = LocalDate.parse("2009-02-01"); + private static final TripUpdate CANCELLATION = new TripUpdateBuilder( + "1.1", + SERVICE_DATE, + CANCELED, + ZoneIds.NEW_YORK + ) + .build(); private TransitModel transitModel; private final GtfsRealtimeFuzzyTripMatcher TRIP_MATCHER_NOOP = null; - private final boolean fullDataset = false; - private byte[] cancellation; + private final boolean FULL_DATASET = false; private String feedId; @BeforeEach @@ -68,28 +74,17 @@ public void setUp() { transitModel = model.transitModel(); feedId = transitModel.getFeedIds().stream().findFirst().get(); - - final TripDescriptor.Builder tripDescriptorBuilder = TripDescriptor.newBuilder(); - - tripDescriptorBuilder.setTripId("1.1"); - tripDescriptorBuilder.setScheduleRelationship(TripDescriptor.ScheduleRelationship.CANCELED); - - final TripUpdate.Builder tripUpdateBuilder = TripUpdate.newBuilder(); - - tripUpdateBuilder.setTrip(tripDescriptorBuilder); - - cancellation = tripUpdateBuilder.build().toByteArray(); } @Test - public void testGetSnapshot() throws InvalidProtocolBufferException { + public void testGetSnapshot() { var updater = defaultUpdater(); updater.applyTripUpdates( TRIP_MATCHER_NOOP, REQUIRED_NO_DATA, - fullDataset, - List.of(TripUpdate.parseFrom(cancellation)), + FULL_DATASET, + List.of(CANCELLATION), feedId ); @@ -99,8 +94,7 @@ public void testGetSnapshot() throws InvalidProtocolBufferException { } @Test - public void testGetSnapshotWithMaxSnapshotFrequencyCleared() - throws InvalidProtocolBufferException { + public void testGetSnapshotWithMaxSnapshotFrequencyCleared() { var updater = new TimetableSnapshotSource( TimetableSnapshotSourceParameters.DEFAULT.withMaxSnapshotFrequency(Duration.ofMillis(-1)), transitModel @@ -111,8 +105,8 @@ public void testGetSnapshotWithMaxSnapshotFrequencyCleared() updater.applyTripUpdates( TRIP_MATCHER_NOOP, REQUIRED_NO_DATA, - fullDataset, - List.of(TripUpdate.parseFrom(cancellation)), + FULL_DATASET, + List.of(CANCELLATION), feedId ); @@ -121,79 +115,6 @@ public void testGetSnapshotWithMaxSnapshotFrequencyCleared() assertNotSame(snapshot, newSnapshot); } - @Test - public void testHandleCanceledTrip() throws InvalidProtocolBufferException { - final FeedScopedId tripId = new FeedScopedId(feedId, "1.1"); - final FeedScopedId tripId2 = new FeedScopedId(feedId, "1.2"); - final Trip trip = transitModel.getTransitModelIndex().getTripForId().get(tripId); - final TripPattern pattern = transitModel.getTransitModelIndex().getPatternForTrip().get(trip); - final int tripIndex = pattern.getScheduledTimetable().getTripIndex(tripId); - final int tripIndex2 = pattern.getScheduledTimetable().getTripIndex(tripId2); - - var updater = defaultUpdater(); - - updater.applyTripUpdates( - TRIP_MATCHER_NOOP, - REQUIRED_NO_DATA, - fullDataset, - List.of(TripUpdate.parseFrom(cancellation)), - feedId - ); - - final TimetableSnapshot snapshot = updater.getTimetableSnapshot(); - final Timetable forToday = snapshot.resolve(pattern, SERVICE_DATE); - final Timetable schedule = snapshot.resolve(pattern, null); - assertNotSame(forToday, schedule); - assertNotSame(forToday.getTripTimes(tripIndex), schedule.getTripTimes(tripIndex)); - assertSame(forToday.getTripTimes(tripIndex2), schedule.getTripTimes(tripIndex2)); - - final TripTimes tripTimes = forToday.getTripTimes(tripIndex); - - assertEquals(RealTimeState.CANCELED, tripTimes.getRealTimeState()); - } - - @Test - public void testHandleDeletedTrip() throws InvalidProtocolBufferException { - final FeedScopedId tripId = new FeedScopedId(feedId, "1.1"); - final FeedScopedId tripId2 = new FeedScopedId(feedId, "1.2"); - final Trip trip = transitModel.getTransitModelIndex().getTripForId().get(tripId); - final TripPattern pattern = transitModel.getTransitModelIndex().getPatternForTrip().get(trip); - final int tripIndex = pattern.getScheduledTimetable().getTripIndex(tripId); - final int tripIndex2 = pattern.getScheduledTimetable().getTripIndex(tripId2); - - var updater = defaultUpdater(); - - final TripDescriptor.Builder tripDescriptorBuilder = TripDescriptor.newBuilder(); - - tripDescriptorBuilder.setTripId("1.1"); - tripDescriptorBuilder.setScheduleRelationship(ScheduleRelationship.DELETED); - - final TripUpdate.Builder tripUpdateBuilder = TripUpdate.newBuilder(); - - tripUpdateBuilder.setTrip(tripDescriptorBuilder); - - var deletion = tripUpdateBuilder.build().toByteArray(); - - updater.applyTripUpdates( - TRIP_MATCHER_NOOP, - REQUIRED_NO_DATA, - fullDataset, - List.of(TripUpdate.parseFrom(deletion)), - feedId - ); - - final TimetableSnapshot snapshot = updater.getTimetableSnapshot(); - final Timetable forToday = snapshot.resolve(pattern, SERVICE_DATE); - final Timetable schedule = snapshot.resolve(pattern, null); - assertNotSame(forToday, schedule); - assertNotSame(forToday.getTripTimes(tripIndex), schedule.getTripTimes(tripIndex)); - assertSame(forToday.getTripTimes(tripIndex2), schedule.getTripTimes(tripIndex2)); - - final TripTimes tripTimes = forToday.getTripTimes(tripIndex); - - assertEquals(RealTimeState.DELETED, tripTimes.getRealTimeState()); - } - /** * This test just asserts that invalid trip ids don't throw an exception and are ignored instead */ @@ -220,7 +141,7 @@ public void invalidTripId() { var result = updater.applyTripUpdates( TRIP_MATCHER_NOOP, REQUIRED_NO_DATA, - fullDataset, + FULL_DATASET, List.of(tripUpdate), feedId ); @@ -342,7 +263,7 @@ public void testHandleModifiedTrip() { updater.applyTripUpdates( TRIP_MATCHER_NOOP, REQUIRED_NO_DATA, - fullDataset, + FULL_DATASET, List.of(tripUpdate), feedId ); @@ -434,54 +355,6 @@ public void testHandleModifiedTrip() { @Nested class Scheduled { - @Test - public void delayed() { - final FeedScopedId tripId = new FeedScopedId(feedId, "1.1"); - final FeedScopedId tripId2 = new FeedScopedId(feedId, "1.2"); - final Trip trip = transitModel.getTransitModelIndex().getTripForId().get(tripId); - final TripPattern pattern = transitModel.getTransitModelIndex().getPatternForTrip().get(trip); - final int tripIndex = pattern.getScheduledTimetable().getTripIndex(tripId); - final int tripIndex2 = pattern.getScheduledTimetable().getTripIndex(tripId2); - - var tripUpdateBuilder = new TripUpdateBuilder( - tripId.getId(), - SERVICE_DATE, - ScheduleRelationship.SCHEDULED, - transitModel.getTimeZone() - ); - - int stopSequence = 2; - int delay = 1; - tripUpdateBuilder.addDelayedStopTime(stopSequence, delay); - - final TripUpdate tripUpdate = tripUpdateBuilder.build(); - - var updater = defaultUpdater(); - - updater.applyTripUpdates( - TRIP_MATCHER_NOOP, - REQUIRED_NO_DATA, - fullDataset, - List.of(tripUpdate), - feedId - ); - - final TimetableSnapshot snapshot = updater.getTimetableSnapshot(); - final Timetable forToday = snapshot.resolve(pattern, SERVICE_DATE); - final Timetable schedule = snapshot.resolve(pattern, null); - assertNotSame(forToday, schedule); - assertNotSame(forToday.getTripTimes(tripIndex), schedule.getTripTimes(tripIndex)); - assertSame(forToday.getTripTimes(tripIndex2), schedule.getTripTimes(tripIndex2)); - assertEquals(1, forToday.getTripTimes(tripIndex).getArrivalDelay(1)); - assertEquals(1, forToday.getTripTimes(tripIndex).getDepartureDelay(1)); - - assertEquals(RealTimeState.SCHEDULED, schedule.getTripTimes(tripIndex).getRealTimeState()); - assertEquals(RealTimeState.UPDATED, forToday.getTripTimes(tripIndex).getRealTimeState()); - - assertEquals(RealTimeState.SCHEDULED, schedule.getTripTimes(tripIndex2).getRealTimeState()); - assertEquals(RealTimeState.SCHEDULED, forToday.getTripTimes(tripIndex2).getRealTimeState()); - } - @Test public void scheduled() { // GIVEN @@ -506,7 +379,7 @@ public void scheduled() { updater.applyTripUpdates( TRIP_MATCHER_NOOP, REQUIRED_NO_DATA, - fullDataset, + FULL_DATASET, List.of(tripUpdate), feedId ); @@ -560,49 +433,6 @@ public void scheduled() { assertEquals(90, originalTripTimesForToday.getDepartureDelay(2)); } - /** - * This test just asserts that trip with start date that is outside the service period doesn't - * throw an exception and is ignored instead. - */ - @Test - public void invalidTripDate() { - // GIVEN - - String scheduledTripId = "1.1"; - - var serviceDateOutsideService = SERVICE_DATE.minusYears(10); - var builder = new TripUpdateBuilder( - scheduledTripId, - serviceDateOutsideService, - SCHEDULED, - transitModel.getTimeZone() - ) - .addDelayedStopTime(1, 0) - .addDelayedStopTime(2, 60, 80) - .addDelayedStopTime(3, 90, 90); - - var tripUpdate = builder.build(); - - var updater = defaultUpdater(); - - // WHEN - var result = updater.applyTripUpdates( - TRIP_MATCHER_NOOP, - REQUIRED_NO_DATA, - fullDataset, - List.of(tripUpdate), - feedId - ); - - // THEN - final TimetableSnapshot snapshot = updater.getTimetableSnapshot(); - assertTrue(snapshot.isEmpty()); - assertFalse(snapshot.isDirty()); - assertEquals(1, result.failed()); - var errors = result.failures(); - assertEquals(1, errors.get(NO_SERVICE_ON_DATE).size()); - } - @Test public void scheduledTripWithSkippedAndNoData() { // GIVEN @@ -627,7 +457,7 @@ public void scheduledTripWithSkippedAndNoData() { updater.applyTripUpdates( TRIP_MATCHER_NOOP, REQUIRED_NO_DATA, - fullDataset, + FULL_DATASET, List.of(tripUpdate), feedId ); @@ -733,108 +563,6 @@ public void scheduledTripWithSkippedAndNoData() { assertTrue(newTripTimes.isNoDataStop(2)); } } - - @Test - public void scheduledTripWithSkippedAndScheduled() { - // GIVEN - - String scheduledTripId = "1.1"; - - var builder = new TripUpdateBuilder( - scheduledTripId, - SERVICE_DATE, - SCHEDULED, - transitModel.getTimeZone() - ) - .addDelayedStopTime(1, 0) - .addSkippedStop(2) - .addDelayedStopTime(3, 90); - - var tripUpdate = builder.build(); - - var updater = defaultUpdater(); - - // WHEN - updater.applyTripUpdates( - TRIP_MATCHER_NOOP, - REQUIRED_NO_DATA, - fullDataset, - List.of(tripUpdate), - feedId - ); - - // THEN - final TimetableSnapshot snapshot = updater.getTimetableSnapshot(); - - // Original trip pattern - { - final FeedScopedId tripId = new FeedScopedId(feedId, scheduledTripId); - final Trip trip = transitModel.getTransitModelIndex().getTripForId().get(tripId); - final TripPattern originalTripPattern = transitModel - .getTransitModelIndex() - .getPatternForTrip() - .get(trip); - - final Timetable originalTimetableForToday = snapshot.resolve( - originalTripPattern, - SERVICE_DATE - ); - final Timetable originalTimetableScheduled = snapshot.resolve(originalTripPattern, null); - - assertNotSame(originalTimetableForToday, originalTimetableScheduled); - - final int originalTripIndexForToday = originalTimetableForToday.getTripIndex(tripId); - final TripTimes originalTripTimesForToday = originalTimetableForToday.getTripTimes( - originalTripIndexForToday - ); - assertTrue( - originalTripTimesForToday.isDeleted(), - "Original trip times should be deleted in time table for service date" - ); - // original trip should be canceled - assertEquals(RealTimeState.DELETED, originalTripTimesForToday.getRealTimeState()); - } - - // New trip pattern - { - final TripPattern newTripPattern = snapshot.getRealtimeAddedTripPattern( - new FeedScopedId(feedId, scheduledTripId), - SERVICE_DATE - ); - - final Timetable newTimetableForToday = snapshot.resolve(newTripPattern, SERVICE_DATE); - final Timetable newTimetableScheduled = snapshot.resolve(newTripPattern, null); - - assertNotSame(newTimetableForToday, newTimetableScheduled); - - assertTrue(newTripPattern.canBoard(0)); - assertFalse(newTripPattern.canBoard(1)); - assertTrue(newTripPattern.canBoard(2)); - - final int newTimetableForTodayModifiedTripIndex = newTimetableForToday.getTripIndex( - scheduledTripId - ); - - var newTripTimes = newTimetableForToday.getTripTimes(newTimetableForTodayModifiedTripIndex); - assertEquals(RealTimeState.UPDATED, newTripTimes.getRealTimeState()); - - assertEquals( - -1, - newTimetableScheduled.getTripIndex(scheduledTripId), - "New trip should not be found in scheduled time table" - ); - - assertEquals(0, newTripTimes.getArrivalDelay(0)); - assertEquals(0, newTripTimes.getDepartureDelay(0)); - assertEquals(45, newTripTimes.getArrivalDelay(1)); - assertEquals(45, newTripTimes.getDepartureDelay(1)); - assertEquals(90, newTripTimes.getArrivalDelay(2)); - assertEquals(90, newTripTimes.getDepartureDelay(2)); - assertFalse(newTripTimes.isCancelledStop(0)); - assertTrue(newTripTimes.isCancelledStop(1)); - assertFalse(newTripTimes.isNoDataStop(2)); - } - } } @Nested @@ -861,7 +589,7 @@ public void addedTrip() { updater.applyTripUpdates( TRIP_MATCHER_NOOP, REQUIRED_NO_DATA, - fullDataset, + FULL_DATASET, List.of(tripUpdate), feedId ); @@ -930,7 +658,7 @@ public void addedTripWithNewRoute() { var result = updater.applyTripUpdates( TRIP_MATCHER_NOOP, REQUIRED_NO_DATA, - fullDataset, + FULL_DATASET, List.of(tripUpdate), feedId ); @@ -981,7 +709,7 @@ public void addedWithUnknownStop() { var result = updater.applyTripUpdates( TRIP_MATCHER_NOOP, REQUIRED_NO_DATA, - fullDataset, + FULL_DATASET, List.of(tripUpdate), feedId ); @@ -1023,7 +751,7 @@ public void repeatedlyAddedTripWithNewRoute() { updater.applyTripUpdates( TRIP_MATCHER_NOOP, REQUIRED_NO_DATA, - fullDataset, + FULL_DATASET, List.of(tripUpdate), feedId ); @@ -1034,7 +762,7 @@ public void repeatedlyAddedTripWithNewRoute() { updater.applyTripUpdates( TRIP_MATCHER_NOOP, REQUIRED_NO_DATA, - fullDataset, + FULL_DATASET, List.of(tripUpdate), feedId ); @@ -1127,7 +855,7 @@ public void testPurgeExpiredData( updater.applyTripUpdates( TRIP_MATCHER_NOOP, REQUIRED_NO_DATA, - fullDataset, + FULL_DATASET, List.of(tripUpdateYesterday), feedId ); @@ -1141,7 +869,7 @@ public void testPurgeExpiredData( updater.applyTripUpdates( TRIP_MATCHER_NOOP, REQUIRED_NO_DATA, - fullDataset, + FULL_DATASET, List.of(tripUpdateToday), feedId ); diff --git a/src/test/java/org/opentripplanner/updater/trip/moduletests/cancellation/CancellationDeletionTest.java b/src/test/java/org/opentripplanner/updater/trip/moduletests/cancellation/CancellationDeletionTest.java new file mode 100644 index 00000000000..8af13ac286f --- /dev/null +++ b/src/test/java/org/opentripplanner/updater/trip/moduletests/cancellation/CancellationDeletionTest.java @@ -0,0 +1,61 @@ +package org.opentripplanner.updater.trip.moduletests.cancellation; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.google.transit.realtime.GtfsRealtime.TripDescriptor.ScheduleRelationship; +import java.util.List; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.opentripplanner.model.Timetable; +import org.opentripplanner.model.TimetableSnapshot; +import org.opentripplanner.transit.model.timetable.RealTimeState; +import org.opentripplanner.updater.trip.RealtimeTestEnvironment; +import org.opentripplanner.updater.trip.TripUpdateBuilder; + +/** + * Cancellations and deletions should end up in the internal data model and make trips unavailable + * for routing. + */ +public class CancellationDeletionTest { + + static List cases() { + return List.of( + Arguments.of(ScheduleRelationship.CANCELED, RealTimeState.CANCELED), + Arguments.of(ScheduleRelationship.DELETED, RealTimeState.DELETED) + ); + } + + @ParameterizedTest + @MethodSource("cases") + public void cancelledTrip(ScheduleRelationship relationship, RealTimeState state) { + var env = RealtimeTestEnvironment.gtfs(); + var pattern1 = env.getPatternForTrip(env.trip1); + + final int tripIndex1 = pattern1.getScheduledTimetable().getTripIndex(env.trip1.getId()); + + var update = new TripUpdateBuilder( + env.trip1.getId().getId(), + RealtimeTestEnvironment.SERVICE_DATE, + relationship, + env.timeZone + ) + .build(); + var result = env.applyTripUpdate(update); + + assertEquals(1, result.successful()); + + final TimetableSnapshot snapshot = env.getTimetableSnapshot(); + final Timetable forToday = snapshot.resolve(pattern1, RealtimeTestEnvironment.SERVICE_DATE); + final Timetable schedule = snapshot.resolve(pattern1, null); + assertNotSame(forToday, schedule); + assertNotSame(forToday.getTripTimes(tripIndex1), schedule.getTripTimes(tripIndex1)); + + var tripTimes = forToday.getTripTimes(tripIndex1); + + assertEquals(state, tripTimes.getRealTimeState()); + assertTrue(tripTimes.isCanceledOrDeleted()); + } +} diff --git a/src/test/java/org/opentripplanner/updater/trip/moduletests/delay/DelayedTest.java b/src/test/java/org/opentripplanner/updater/trip/moduletests/delay/DelayedTest.java new file mode 100644 index 00000000000..4cfe1e5500d --- /dev/null +++ b/src/test/java/org/opentripplanner/updater/trip/moduletests/delay/DelayedTest.java @@ -0,0 +1,88 @@ +package org.opentripplanner.updater.trip.moduletests.delay; + +import static com.google.transit.realtime.GtfsRealtime.TripDescriptor.ScheduleRelationship.SCHEDULED; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertSame; + +import org.junit.jupiter.api.Test; +import org.opentripplanner.model.Timetable; +import org.opentripplanner.model.TimetableSnapshot; +import org.opentripplanner.transit.model.timetable.RealTimeState; +import org.opentripplanner.updater.trip.RealtimeTestEnvironment; +import org.opentripplanner.updater.trip.TripUpdateBuilder; + +/** + * Delays should be applied to the first trip but should leave the second trip untouched. + */ +public class DelayedTest { + + private static final int DELAY = 1; + private static final int STOP_SEQUENCE = 1; + + @Test + public void delayed() { + var env = RealtimeTestEnvironment.gtfs(); + + var tripUpdate = new TripUpdateBuilder( + env.trip1.getId().getId(), + RealtimeTestEnvironment.SERVICE_DATE, + SCHEDULED, + env.timeZone + ) + .addDelayedStopTime(STOP_SEQUENCE, DELAY) + .build(); + + var result = env.applyTripUpdate(tripUpdate); + + assertEquals(1, result.successful()); + + // trip1 should be modified + { + var pattern1 = env.getPatternForTrip(env.trip1); + final int trip1Index = pattern1.getScheduledTimetable().getTripIndex(env.trip1.getId()); + + final TimetableSnapshot snapshot = env.getTimetableSnapshot(); + final Timetable trip1Realtime = snapshot.resolve( + pattern1, + RealtimeTestEnvironment.SERVICE_DATE + ); + final Timetable trip1Scheduled = snapshot.resolve(pattern1, null); + + assertNotSame(trip1Realtime, trip1Scheduled); + assertNotSame( + trip1Realtime.getTripTimes(trip1Index), + trip1Scheduled.getTripTimes(trip1Index) + ); + assertEquals(1, trip1Realtime.getTripTimes(trip1Index).getArrivalDelay(STOP_SEQUENCE)); + assertEquals(1, trip1Realtime.getTripTimes(trip1Index).getDepartureDelay(STOP_SEQUENCE)); + + assertEquals( + RealTimeState.SCHEDULED, + trip1Scheduled.getTripTimes(trip1Index).getRealTimeState() + ); + assertEquals( + RealTimeState.UPDATED, + trip1Realtime.getTripTimes(trip1Index).getRealTimeState() + ); + } + + // trip2 should keep the scheduled information + { + var pattern = env.getPatternForTrip(env.trip2); + final int tripIndex = pattern.getScheduledTimetable().getTripIndex(env.trip2.getId()); + + final TimetableSnapshot snapshot = env.getTimetableSnapshot(); + final Timetable realtime = snapshot.resolve(pattern, RealtimeTestEnvironment.SERVICE_DATE); + final Timetable scheduled = snapshot.resolve(pattern, null); + + assertSame(realtime, scheduled); + assertSame(realtime.getTripTimes(tripIndex), scheduled.getTripTimes(tripIndex)); + assertEquals(0, realtime.getTripTimes(tripIndex).getArrivalDelay(STOP_SEQUENCE)); + assertEquals(0, realtime.getTripTimes(tripIndex).getDepartureDelay(STOP_SEQUENCE)); + + assertEquals(RealTimeState.SCHEDULED, scheduled.getTripTimes(tripIndex).getRealTimeState()); + assertEquals(RealTimeState.SCHEDULED, realtime.getTripTimes(tripIndex).getRealTimeState()); + } + } +} diff --git a/src/test/java/org/opentripplanner/updater/trip/moduletests/delay/SkippedTest.java b/src/test/java/org/opentripplanner/updater/trip/moduletests/delay/SkippedTest.java new file mode 100644 index 00000000000..d97704549ee --- /dev/null +++ b/src/test/java/org/opentripplanner/updater/trip/moduletests/delay/SkippedTest.java @@ -0,0 +1,119 @@ +package org.opentripplanner.updater.trip.moduletests.delay; + +import static com.google.transit.realtime.GtfsRealtime.TripDescriptor.ScheduleRelationship.SCHEDULED; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; +import org.opentripplanner.model.Timetable; +import org.opentripplanner.model.TimetableSnapshot; +import org.opentripplanner.transit.model.framework.FeedScopedId; +import org.opentripplanner.transit.model.network.TripPattern; +import org.opentripplanner.transit.model.timetable.RealTimeState; +import org.opentripplanner.transit.model.timetable.Trip; +import org.opentripplanner.transit.model.timetable.TripTimes; +import org.opentripplanner.updater.trip.RealtimeTestEnvironment; +import org.opentripplanner.updater.trip.TripUpdateBuilder; + +/** + * A mixture of delayed and skipped stops should result in both delayed and cancelled stops. + */ +public class SkippedTest { + + @Test + public void scheduledTripWithSkippedAndScheduled() { + var env = RealtimeTestEnvironment.gtfs(); + String scheduledTripId = env.trip2.getId().getId(); + + var tripUpdate = new TripUpdateBuilder( + scheduledTripId, + RealtimeTestEnvironment.SERVICE_DATE, + SCHEDULED, + env.timeZone + ) + .addDelayedStopTime(0, 0) + .addSkippedStop(1) + .addDelayedStopTime(2, 90) + .build(); + + var result = env.applyTripUpdate(tripUpdate); + + assertEquals(1, result.successful()); + + final TimetableSnapshot snapshot = env.getTimetableSnapshot(); + + // Original trip pattern + { + final FeedScopedId tripId = env.trip2.getId(); + final Trip trip = env.transitModel.getTransitModelIndex().getTripForId().get(tripId); + final TripPattern originalTripPattern = env.transitModel + .getTransitModelIndex() + .getPatternForTrip() + .get(trip); + + final Timetable originalTimetableForToday = snapshot.resolve( + originalTripPattern, + RealtimeTestEnvironment.SERVICE_DATE + ); + final Timetable originalTimetableScheduled = snapshot.resolve(originalTripPattern, null); + + assertNotSame(originalTimetableForToday, originalTimetableScheduled); + + final int originalTripIndexForToday = originalTimetableForToday.getTripIndex(tripId); + final TripTimes originalTripTimesForToday = originalTimetableForToday.getTripTimes( + originalTripIndexForToday + ); + assertTrue( + originalTripTimesForToday.isDeleted(), + "Original trip times should be deleted in time table for service date" + ); + // original trip should be canceled + assertEquals(RealTimeState.DELETED, originalTripTimesForToday.getRealTimeState()); + } + + // New trip pattern + { + final TripPattern newTripPattern = snapshot.getRealtimeAddedTripPattern( + env.trip2.getId(), + RealtimeTestEnvironment.SERVICE_DATE + ); + + final Timetable newTimetableForToday = snapshot.resolve( + newTripPattern, + RealtimeTestEnvironment.SERVICE_DATE + ); + final Timetable newTimetableScheduled = snapshot.resolve(newTripPattern, null); + + assertNotSame(newTimetableForToday, newTimetableScheduled); + + assertTrue(newTripPattern.canBoard(0)); + assertFalse(newTripPattern.canBoard(1)); + assertTrue(newTripPattern.canBoard(2)); + + final int newTimetableForTodayModifiedTripIndex = newTimetableForToday.getTripIndex( + scheduledTripId + ); + + var newTripTimes = newTimetableForToday.getTripTimes(newTimetableForTodayModifiedTripIndex); + assertEquals(RealTimeState.UPDATED, newTripTimes.getRealTimeState()); + + assertEquals( + -1, + newTimetableScheduled.getTripIndex(scheduledTripId), + "New trip should not be found in scheduled time table" + ); + + assertEquals(0, newTripTimes.getArrivalDelay(0)); + assertEquals(0, newTripTimes.getDepartureDelay(0)); + assertEquals(42, newTripTimes.getArrivalDelay(1)); + assertEquals(47, newTripTimes.getDepartureDelay(1)); + assertEquals(90, newTripTimes.getArrivalDelay(2)); + assertEquals(90, newTripTimes.getDepartureDelay(2)); + assertFalse(newTripTimes.isCancelledStop(0)); + assertTrue(newTripTimes.isCancelledStop(1)); + assertFalse(newTripTimes.isNoDataStop(2)); + } + } +} diff --git a/src/test/java/org/opentripplanner/updater/trip/moduletests/rejection/InvalidInputTest.java b/src/test/java/org/opentripplanner/updater/trip/moduletests/rejection/InvalidInputTest.java new file mode 100644 index 00000000000..00831333943 --- /dev/null +++ b/src/test/java/org/opentripplanner/updater/trip/moduletests/rejection/InvalidInputTest.java @@ -0,0 +1,46 @@ +package org.opentripplanner.updater.trip.moduletests.rejection; + +import static com.google.transit.realtime.GtfsRealtime.TripDescriptor.ScheduleRelationship.SCHEDULED; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.opentripplanner.updater.spi.UpdateError.UpdateErrorType.NO_SERVICE_ON_DATE; +import static org.opentripplanner.updater.trip.RealtimeTestEnvironment.SERVICE_DATE; + +import java.time.LocalDate; +import java.util.List; +import java.util.Set; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.opentripplanner.updater.trip.RealtimeTestEnvironment; +import org.opentripplanner.updater.trip.TripUpdateBuilder; + +/** + * A trip with start date that is outside the service period shouldn't throw an exception and is + * ignored instead. + */ +public class InvalidInputTest { + + public static List cases() { + return List.of(SERVICE_DATE.minusYears(10), SERVICE_DATE.plusYears(10)); + } + + @ParameterizedTest + @MethodSource("cases") + public void invalidTripDate(LocalDate date) { + var env = RealtimeTestEnvironment.gtfs(); + + var update = new TripUpdateBuilder(env.trip1.getId().getId(), date, SCHEDULED, env.timeZone) + .addDelayedStopTime(1, 0) + .addDelayedStopTime(2, 60, 80) + .addDelayedStopTime(3, 90, 90) + .build(); + + var result = env.applyTripUpdate(update); + + var snapshot = env.getTimetableSnapshot(); + assertTrue(snapshot.isEmpty()); + assertEquals(1, result.failed()); + var errors = result.failures().keySet(); + assertEquals(Set.of(NO_SERVICE_ON_DATE), errors); + } +}