|
14 | 14 | package org.onebusaway.gtfs_merge; |
15 | 15 |
|
16 | 16 | import static org.junit.jupiter.api.Assertions.assertEquals; |
| 17 | +import static org.junit.jupiter.api.Assertions.assertNotNull; |
17 | 18 | import static org.junit.jupiter.api.Assertions.assertTrue; |
18 | 19 |
|
19 | 20 | import java.io.File; |
20 | 21 | import java.io.IOException; |
21 | 22 | import java.util.ArrayList; |
| 23 | +import java.util.HashMap; |
| 24 | +import java.util.HashSet; |
22 | 25 | import java.util.Iterator; |
23 | 26 | import java.util.List; |
| 27 | +import java.util.Map; |
| 28 | +import java.util.Set; |
24 | 29 | import org.junit.jupiter.api.AfterEach; |
25 | 30 | import org.junit.jupiter.api.BeforeEach; |
26 | 31 | import org.junit.jupiter.api.Test; |
@@ -628,6 +633,107 @@ public void testLocationTypeMismatch_PlatformFeedFirstStationFeedSecond() throws |
628 | 633 | assertTrue(foundStopTime, "expected at least one merged stop_time"); |
629 | 634 | } |
630 | 635 |
|
| 636 | + /** |
| 637 | + * Reproduces issue 142: after a raw service_id collision forces a rename, the renamed id must not |
| 638 | + * collide with an id that already exists in the merged output feed (or in another, |
| 639 | + * not-yet-processed key from the same source feed). Round-trips the merged feed through a |
| 640 | + * writer/reader cycle to make sure the on-disk result is unambiguous. |
| 641 | + */ |
| 642 | + @Test |
| 643 | + public void testServiceIdRenameAvoidsCollisionWithExistingRawId() throws IOException { |
| 644 | + // "fresh" input (lowest priority, processed second): a single, previously-unmerged |
| 645 | + // service calendar whose raw id "T0" collides with an id in the higher-priority input. |
| 646 | + _oldGtfs.putAgencies(1); |
| 647 | + _oldGtfs.putRoutes(1); |
| 648 | + _oldGtfs.putStops(3); |
| 649 | + _oldGtfs.putCalendars(1, "mask=1111100", "service_id=T0"); |
| 650 | + _oldGtfs.putCalendarDates("T0=20120704"); |
| 651 | + _oldGtfs.putTrips(1, "r0", "T0", "trip_id=fresh-trip"); |
| 652 | + _oldGtfs.putStopTimes("fresh-trip", "s0,s1,s2"); |
| 653 | + |
| 654 | + // "already merged" input (highest priority, processed first): already contains both the |
| 655 | + // raw id "T0" and a previously-renamed raw id "a-T0" as two logically distinct calendars. |
| 656 | + _newGtfs.putAgencies(1); |
| 657 | + _newGtfs.putRoutes(1); |
| 658 | + _newGtfs.putStops(3); |
| 659 | + _newGtfs.putCalendars(2, "mask=0000011,1010101", "service_id=T0,a-T0"); |
| 660 | + _newGtfs.putCalendarDates("T0=20120705", "a-T0=20120706"); |
| 661 | + _newGtfs.putTrips(2, "r0,r0", "T0,a-T0", "trip_id=target-t0-trip,target-a-t0-trip"); |
| 662 | + _newGtfs.putStopTimes("target-t0-trip,target-a-t0-trip", "s0,s1,s2"); |
| 663 | + |
| 664 | + ServiceCalendarMergeStrategy strategy = new ServiceCalendarMergeStrategy(); |
| 665 | + strategy.setDuplicateDetectionStrategy(EDuplicateDetectionStrategy.NONE); |
| 666 | + strategy.setDuplicateRenamingStrategy(EDuplicateRenamingStrategy.CONTEXT); |
| 667 | + _merger.setServiceCalendarStrategy(strategy); |
| 668 | + |
| 669 | + GtfsRelationalDao dao = merge(); |
| 670 | + |
| 671 | + Set<String> serviceIds = new HashSet<>(); |
| 672 | + Map<String, String> serviceIdByCalendarMask = new HashMap<>(); |
| 673 | + for (ServiceCalendar calendar : dao.getAllCalendars()) { |
| 674 | + String serviceId = calendar.getServiceId().getId(); |
| 675 | + serviceIds.add(serviceId); |
| 676 | + serviceIdByCalendarMask.put(getCalendarMask(calendar), serviceId); |
| 677 | + } |
| 678 | + assertEquals(3, dao.getAllCalendars().size(), "expected three logical calendars"); |
| 679 | + assertEquals(3, serviceIds.size(), "expected three distinct raw service ids"); |
| 680 | + assertEquals(3, serviceIdByCalendarMask.size(), "expected three distinct calendar masks"); |
| 681 | + assertEquals( |
| 682 | + "T0", |
| 683 | + serviceIdByCalendarMask.get("0000011"), |
| 684 | + "higher-priority T0 calendar must remain unchanged"); |
| 685 | + assertEquals( |
| 686 | + "a-T0", |
| 687 | + serviceIdByCalendarMask.get("1010101"), |
| 688 | + "higher-priority a-T0 calendar must remain unchanged"); |
| 689 | + |
| 690 | + String freshId = serviceIdByCalendarMask.get("1111100"); |
| 691 | + assertNotNull(freshId, "expected the fresh calendar to receive a third, unused id"); |
| 692 | + assertTrue(!freshId.equals("T0") && !freshId.equals("a-T0")); |
| 693 | + |
| 694 | + Map<String, String> serviceIdByTripId = new HashMap<>(); |
| 695 | + for (Trip trip : dao.getAllTrips()) { |
| 696 | + serviceIdByTripId.put(trip.getId().getId(), trip.getServiceId().getId()); |
| 697 | + } |
| 698 | + assertEquals(3, dao.getAllTrips().size(), "expected one trip for each logical calendar"); |
| 699 | + assertEquals(freshId, serviceIdByTripId.get("fresh-trip")); |
| 700 | + assertEquals("T0", serviceIdByTripId.get("target-t0-trip")); |
| 701 | + assertEquals("a-T0", serviceIdByTripId.get("target-a-t0-trip")); |
| 702 | + |
| 703 | + Map<String, String> serviceIdByExceptionDate = new HashMap<>(); |
| 704 | + for (ServiceCalendarDate date : dao.getAllCalendarDates()) { |
| 705 | + serviceIdByExceptionDate.put(date.getDate().getAsString(), date.getServiceId().getId()); |
| 706 | + } |
| 707 | + assertEquals( |
| 708 | + 3, dao.getAllCalendarDates().size(), "expected one date for each logical calendar"); |
| 709 | + assertEquals(3, serviceIdByExceptionDate.size(), "expected three distinct exception dates"); |
| 710 | + assertEquals(freshId, serviceIdByExceptionDate.get("20120704")); |
| 711 | + assertEquals("T0", serviceIdByExceptionDate.get("20120705")); |
| 712 | + assertEquals("a-T0", serviceIdByExceptionDate.get("20120706")); |
| 713 | + |
| 714 | + // no ambiguity in the round-tripped output: exactly one calendar per raw service id |
| 715 | + for (String id : serviceIds) { |
| 716 | + int count = 0; |
| 717 | + for (ServiceCalendar calendar : dao.getAllCalendars()) { |
| 718 | + if (id.equals(calendar.getServiceId().getId())) { |
| 719 | + count++; |
| 720 | + } |
| 721 | + } |
| 722 | + assertEquals(1, count, "expected exactly one calendar for service id=" + id); |
| 723 | + } |
| 724 | + } |
| 725 | + |
| 726 | + private String getCalendarMask(ServiceCalendar calendar) { |
| 727 | + return "" |
| 728 | + + calendar.getMonday() |
| 729 | + + calendar.getTuesday() |
| 730 | + + calendar.getWednesday() |
| 731 | + + calendar.getThursday() |
| 732 | + + calendar.getFriday() |
| 733 | + + calendar.getSaturday() |
| 734 | + + calendar.getSunday(); |
| 735 | + } |
| 736 | + |
631 | 737 | private GtfsRelationalDao merge() throws IOException { |
632 | 738 | List<File> paths = new ArrayList<>(); |
633 | 739 | paths.add(_oldGtfs.getPath()); |
|
0 commit comments