From a684370eb6d0135a5daa21f241d75774da5557ee Mon Sep 17 00:00:00 2001 From: Alexey Zakharenkov Date: Wed, 6 Jul 2022 10:15:10 +0300 Subject: [PATCH] Add tests on adjusting rails geometry; configure GitHub Actions for the tests --- .github/workflows/python-app.yml | 39 + subway_structure.py | 4 +- tests/__init__.py | 0 tests/sample_data.py | 1335 ++++++++++++++++++++++++++++++ tests/test_build_tracks.py | 107 +++ 5 files changed, 1483 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/python-app.yml create mode 100644 tests/__init__.py create mode 100644 tests/sample_data.py create mode 100644 tests/test_build_tracks.py diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml new file mode 100644 index 0000000..343fd4a --- /dev/null +++ b/.github/workflows/python-app.yml @@ -0,0 +1,39 @@ +# This workflow will install Python dependencies, run tests and lint with a single version of Python +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: Python application + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +permissions: + contents: read + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up Python 3.6 + uses: actions/setup-python@v3 + with: + python-version: "3.6" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install flake8 + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + - name: Lint with flake8 + run: | + # stop the build if there are Python syntax errors or undefined names + flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide + flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + - name: Test with unittest + run: | + python -m unittest discover tests diff --git a/subway_structure.py b/subway_structure.py index b67ce01..ece0b5c 100644 --- a/subway_structure.py +++ b/subway_structure.py @@ -1093,7 +1093,7 @@ class Route: first_stop_location = find_segment(self.stops[0].stop, tracks, 0) last_stop_location = find_segment(self.stops[-1].stop, tracks, 0) - if last_stop_location: + if last_stop_location != (None, None): seg2, u2 = last_stop_location if u2 == 0.0: # Make seg2 the segment the last_stop_location is @@ -1104,7 +1104,7 @@ class Route: tracks = tracks[0:seg2 + 2] tracks[-1] = self.stops[-1].stop - if first_stop_location: + if first_stop_location != (None, None): seg1, u1 = first_stop_location if u1 == 1.0: # Make seg1 the segment the first_stop_location is diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/sample_data.py b/tests/sample_data.py new file mode 100644 index 0000000..af89404 --- /dev/null +++ b/tests/sample_data.py @@ -0,0 +1,1335 @@ +sample_networks = { + "Only 2 stations, no rails": { + "xml": """ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +""", + "station_count": 2, + "tracks": [], + "extended_tracks": [ + (0.0, 0.0), + (1.0, 0.0), + ], + "truncated_tracks": [], + }, + + "Only 2 stations connected with rails": { + "xml": """ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +""", + "station_count": 2, + "tracks": [ + (0.0, 0.0), + (1.0, 0.0), + ], + "extended_tracks": [ + (0.0, 0.0), + (1.0, 0.0), + ], + "truncated_tracks": [ + (0.0, 0.0), + (1.0, 0.0), + ], + }, + + "Only 6 stations, no rails": { + "xml": """ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +""", + "station_count": 6, + "tracks": [], + "extended_tracks": [ + (0.0, 0.0), + (1.0, 0.0), + (2.0, 0.0), + (3.0, 0.0), + (4.0, 0.0), + (5.0, 0.0), + ], + "truncated_tracks": [], + }, + + "One rail line connecting all stations": { + "xml": """ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +""", + "station_count": 6, + "tracks": [ + (0.0, 0.0), + (1.0, 0.0), + (2.0, 0.0), + (3.0, 0.0), + (4.0, 0.0), + (5.0, 0.0), + ], + "extended_tracks": [ + (0.0, 0.0), + (1.0, 0.0), + (2.0, 0.0), + (3.0, 0.0), + (4.0, 0.0), + (5.0, 0.0), + ], + "truncated_tracks": [ + (0.0, 0.0), + (1.0, 0.0), + (2.0, 0.0), + (3.0, 0.0), + (4.0, 0.0), + (5.0, 0.0), + ], + }, + + "One rail line connecting all stations except the last": { + "xml": """ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +""", + "station_count": 6, + "tracks": [ + (0.0, 0.0), + (1.0, 0.0), + (2.0, 0.0), + (3.0, 0.0), + (4.0, 0.0), + ], + "extended_tracks": [ + (0.0, 0.0), + (1.0, 0.0), + (2.0, 0.0), + (3.0, 0.0), + (4.0, 0.0), + (5.0, 0.0), + ], + "truncated_tracks": [ + (0.0, 0.0), + (1.0, 0.0), + (2.0, 0.0), + (3.0, 0.0), + (4.0, 0.0), + ], + }, + + "One rail line connecting all stations except the fist": { + "xml": """ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +""", + "station_count": 6, + "tracks": [ + (1.0, 0.0), + (2.0, 0.0), + (3.0, 0.0), + (4.0, 0.0), + (5.0, 0.0), + ], + "extended_tracks": [ + (0.0, 0.0), + (1.0, 0.0), + (2.0, 0.0), + (3.0, 0.0), + (4.0, 0.0), + (5.0, 0.0), + ], + "truncated_tracks": [ + (1.0, 0.0), + (2.0, 0.0), + (3.0, 0.0), + (4.0, 0.0), + (5.0, 0.0), + ], + }, + + "One rail line connecting all stations except the fist and the last": { + "xml": """ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +""", + "station_count": 6, + "tracks": [ + (1.0, 0.0), + (2.0, 0.0), + (3.0, 0.0), + (4.0, 0.0), + ], + "extended_tracks": [ + (0.0, 0.0), + (1.0, 0.0), + (2.0, 0.0), + (3.0, 0.0), + (4.0, 0.0), + (5.0, 0.0), + ], + "truncated_tracks": [ + (1.0, 0.0), + (2.0, 0.0), + (3.0, 0.0), + (4.0, 0.0), + ], + }, + + "One rail line connecting only 2 first stations": { + "xml": """ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +""", + "station_count": 6, + "tracks": [ + (0.0, 0.0), + (1.0, 0.0), + ], + "extended_tracks": [ + (0.0, 0.0), + (1.0, 0.0), + (2.0, 0.0), + (3.0, 0.0), + (4.0, 0.0), + (5.0, 0.0), + ], + "truncated_tracks": [ + (0.0, 0.0), + (1.0, 0.0), + ], + }, + + "One rail line connecting only 2 last stations": { + "xml": """ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +""", + "station_count": 6, + "tracks": [ + (4.0, 0.0), + (5.0, 0.0), + ], + "extended_tracks": [ + (0.0, 0.0), + (1.0, 0.0), + (2.0, 0.0), + (3.0, 0.0), + (4.0, 0.0), + (5.0, 0.0), + ], + "truncated_tracks": [ + (4.0, 0.0), + (5.0, 0.0), + ], + }, + + "One rail connecting all stations and protruding at both ends": { + "xml": """ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +""", + "station_count": 6, + "tracks": [ + (-1.0, 0.0), + (0.0, 0.0), + (1.0, 0.0), + (2.0, 0.0), + (3.0, 0.0), + (4.0, 0.0), + (5.0, 0.0), + (6.0, 0.0), + ], + "extended_tracks": [ + (-1.0, 0.0), + (0.0, 0.0), + (1.0, 0.0), + (2.0, 0.0), + (3.0, 0.0), + (4.0, 0.0), + (5.0, 0.0), + (6.0, 0.0), + ], + "truncated_tracks": [ + (0.0, 0.0), + (1.0, 0.0), + (2.0, 0.0), + (3.0, 0.0), + (4.0, 0.0), + (5.0, 0.0), + ], + }, + + "Several rails with reversed order for backward route, connecting all stations and protruding at both ends": { + "xml": """ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +""", + "station_count": 6, + "tracks": [ + (-1.0, 0.0), + (0.0, 0.0), + (1.0, 0.0), + (2.0, 0.0), + (3.0, 0.0), + (4.0, 0.0), + (5.0, 0.0), + (6.0, 0.0), + ], + "extended_tracks": [ + (-1.0, 0.0), + (0.0, 0.0), + (1.0, 0.0), + (2.0, 0.0), + (3.0, 0.0), + (4.0, 0.0), + (5.0, 0.0), + (6.0, 0.0), + ], + "truncated_tracks": [ + (0.0, 0.0), + (1.0, 0.0), + (2.0, 0.0), + (3.0, 0.0), + (4.0, 0.0), + (5.0, 0.0), + ], + }, + + "One rail laying near all stations requiring station projecting, protruding at both ends": { + "xml": """ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +""", + "station_count": 6, + "tracks": [ + (-1.0, 0.0), + (6.0, 0.0), + ], + "extended_tracks": [ + (-1.0, 0.0), + (6.0, 0.0), + ], + "truncated_tracks": [ + (0.0, 0.0), + (5.0, 0.0), + ], + }, + + "One rail laying near all stations except the first and last": { + "xml": """ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +""", + "station_count": 6, + "tracks": [ + (1.0, 0.0), + (4.0, 0.0), + ], + "extended_tracks": [ + (0.0, 0.0001), + (1.0, 0.0), + (4.0, 0.0), + (5.0, 0.0001), + ], + "truncated_tracks": [ + (1.0, 0.0), + (4.0, 0.0), + ], + }, + + "Circle route without rails": { + "xml": """ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +""", + "station_count": 4, + "tracks": [], + "extended_tracks": [ + (0.0, 0.0), + (0.0, 1.0), + (1.0, 1.0), + (1.0, 0.0), + (0.0, 0.0), + ], + "truncated_tracks": [], + }, + + "Circle route with closed rail line connecting all stations": { + "xml": """ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +""", + "station_count": 4, + "tracks": [ + (0.0, 0.0), + (0.0, 1.0), + (1.0, 1.0), + (1.0, 0.0), + (0.0, 0.0), + ], + "extended_tracks": [ + (0.0, 0.0), + (0.0, 1.0), + (1.0, 1.0), + (1.0, 0.0), + (0.0, 0.0), + ], + "truncated_tracks": [ + (0.0, 0.0), + (0.0, 1.0), + (1.0, 1.0), + (1.0, 0.0), + (0.0, 0.0), + ], + }, +} diff --git a/tests/test_build_tracks.py b/tests/test_build_tracks.py new file mode 100644 index 0000000..997a404 --- /dev/null +++ b/tests/test_build_tracks.py @@ -0,0 +1,107 @@ +""" +To perform tests manually, run this command from the top directory +of the repository: + +> python -m unittest discover tests + +or simply + +> python -m unittest +""" + +import io +import unittest + +from subway_structure import City +from subway_io import load_xml +from tests.sample_data import sample_networks + + +class TestOneRouteTracks(unittest.TestCase): + """Test tracks extending and truncating on one-route networks""" + + STATION_COUNT_INDEX = 4 + CITY_TEMPLATE = [ + 1, # city id + "Null Island", # name + "World", # Country + "Africa", # continent + None, # station count. Would be taken from the sample network data under testing + 1, # subway line count + 0, # light rail line count + 0, # interchanges + "-179, -89, 179, 89", # bbox + ] + + def prepare_city_routes(self, network): + city_data = self.CITY_TEMPLATE.copy() + city_data[self.STATION_COUNT_INDEX] = network["station_count"] + city = City(city_data) + elements = load_xml(io.BytesIO(network["xml"].encode("utf-8"))) + for el in elements: + city.add(el) + city.extract_routes() + city.validate() + + self.assertTrue(city.is_good()) + + route_master = list(city.routes.values())[0] + variants = route_master.routes + + fwd_route = [v for v in variants if v.name == "Forward"][0] + bwd_route = [v for v in variants if v.name == "Backward"][0] + + return fwd_route, bwd_route + + def _test_tracks_extending_for_network(self, network_data): + fwd_route, bwd_route = self.prepare_city_routes(network_data) + + self.assertEqual( + fwd_route.tracks, + network_data["tracks"], + "Wrong tracks", + ) + extended_tracks = fwd_route.get_extended_tracks() + self.assertEqual( + extended_tracks, + network_data["extended_tracks"], + "Wrong tracks after extending", + ) + + self.assertEqual( + bwd_route.tracks, + network_data["tracks"][::-1], + "Wrong backward tracks", + ) + extended_tracks = bwd_route.get_extended_tracks() + self.assertEqual( + extended_tracks, + network_data["extended_tracks"][::-1], + "Wrong backward tracks after extending", + ) + + def _test_tracks_truncating_for_network(self, network_data): + fwd_route, bwd_route = self.prepare_city_routes(network_data) + + truncated_tracks = fwd_route.get_truncated_tracks(fwd_route.tracks) + self.assertEqual( + truncated_tracks, + network_data["truncated_tracks"], + "Wrong tracks after truncating", + ) + truncated_tracks = bwd_route.get_truncated_tracks(bwd_route.tracks) + self.assertEqual( + truncated_tracks, + network_data["truncated_tracks"][::-1], + "Wrong backward tracks after truncating", + ) + + def test_tracks_extending(self): + for network_name, network_data in sample_networks.items(): + with self.subTest(msg=network_name): + self._test_tracks_extending_for_network(network_data) + + def test_tracks_truncating(self): + for network_name, network_data in sample_networks.items(): + with self.subTest(msg=network_name): + self._test_tracks_truncating_for_network(network_data)