diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d0aa7aa..e69412f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,45 +1,46 @@ name: SonarQube on: - push: - branches: - - main - - master - - develop pull_request: types: [opened, synchronize, reopened] + push: + branches: [master, develop] + +permissions: + contents: read + +concurrency: + group: sonar-${{ github.ref }} + cancel-in-progress: true jobs: sonarqube: name: SonarQube Scan runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - name: Check out full history + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: fetch-depth: 0 - - name: Set up Python - uses: actions/setup-python@v5 + - name: Install uv + uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 with: - python-version: '3.x' - - - name: Install dependencies (best effort) - continue-on-error: true - run: | - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - if [ -f pyproject.toml ]; then pip install . || true; fi - pip install coverage pytest || true - - - name: Run tests with coverage (best effort) - continue-on-error: true - run: | - if [ -d tests ] || [ -d test ]; then - coverage run -m pytest || true - coverage xml -o coverage.xml || true - fi - - - name: SonarQube Scan - uses: SonarSource/sonarqube-scan-action@v5 + version: "0.12.3" + enable-cache: true + + - name: Install locked test environment + run: uv sync --group test --python 3.11 + + - name: Generate required coverage report + run: uv run --python 3.11 pytest --cov=urbanpy --cov-report=xml + + - name: Scan and wait for the mandatory quality gate + uses: SonarSource/sonarqube-scan-action@22918119ff8e1ca75a623e15c8296b6ea4fbe28f # v8.2.1 env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} + with: + args: >- + -Dsonar.qualitygate.wait=true + -Dsonar.qualitygate.timeout=300 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 419b425..1c7aa45 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,132 +1,136 @@ -# This workflow will: -# 1. Build the docs using Sphinx and any errors in the build process are bubbled up as Github status checks -# 2. Install Python dependencies, run tests and lint with a variety of Python versions -# 3. If commit is tagged with "vX.Y.Z", create a release -# 4. When release is created, upload Package using Twine -# 5. When release is created, build docs using Sphinx and push changes to this project gh-pages branch - -# For more information see: -# 1. https://github.com/ammaraskar/sphinx-action -# 2. https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions -# 3. https://github.com/actions/create-release -# 4. https://github.com/grst/python-ci-versioneer - -name: Test and deploy +name: CI + +on: + pull_request: + push: + branches: [master, develop] + permissions: contents: read -on: [push, pull_request] +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + UV_FROZEN: "true" jobs: - docs: + quality: + name: Quality and coverage runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: ammaraskar/sphinx-action@master - with: - docs-folder: "docs/" + - name: Check out repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Install uv + uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 + with: + version: "0.12.3" + enable-cache: true + + - name: Install Python and locked development environment + run: | + uv python install 3.11 + uv sync --group test --group lint --python 3.11 + + - name: Check source + run: uv run --python 3.11 ruff check urbanpy tests - build: + - name: Run hermetic tests with coverage + run: uv run --python 3.11 pytest --cov=urbanpy --cov-report=term-missing --cov-report=xml + + - name: Upload coverage artifact + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: coverage-xml + path: coverage.xml + if-no-files-found: error + + tests: + name: Python ${{ matrix.python-version }} runs-on: ubuntu-latest strategy: + fail-fast: false matrix: - python-version: ["3.9", "3.10", "3.11"] + python-version: ["3.11", "3.12", "3.13", "3.14"] steps: - - uses: actions/checkout@v3 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - python -m pip install --upgrade pip - python -m pip install flake8 pytest - 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 pytest - run: | - # OSRM routing is too expensive to test on Github Actions. - # please test this function locally. - pytest -k 'not test_osrm_matrix' - - release: - needs: [docs, build] - if: | - (github.event_name == 'push' && startsWith(github.ref, 'refs/tags')) || - (github.event_name == 'release' && contains(github.event.action, 'published')) + - name: Check out repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Install uv + uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 + with: + version: "0.12.3" + enable-cache: true + + - name: Install Python and locked test environment + env: + PYTHON_VERSION: ${{ matrix.python-version }} + run: | + uv python install "$PYTHON_VERSION" + uv sync --group test --python "$PYTHON_VERSION" + + - name: Run hermetic tests + env: + PYTHON_VERSION: ${{ matrix.python-version }} + run: uv run --python "$PYTHON_VERSION" pytest + + package: + name: Package runs-on: ubuntu-latest steps: - - name: Checkout code - uses: actions/checkout@v3 - - name: Create Release - id: create_release - uses: actions/create-release@latest - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is automatic - with: - tag_name: ${{ github.ref }} - release_name: Release ${{ github.ref }} - draft: false - prerelease: false - - deploy: - needs: release + - name: Check out repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Install uv + uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 + with: + version: "0.12.3" + enable-cache: true + + - name: Build distributions + run: uv build + + - name: Validate distributions + run: | + uvx --from twine==7.0.0 twine check dist/* + uvx --from check-wheel-contents==0.6.3 check-wheel-contents dist/*.whl + + security: + name: Dependency audit runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: '3.x' - - name: Autobump version - run: | - export PATH=/home/runner/.local/bin/:$PATH - - VERSION=$( echo $GITHUB_REF | /bin/sed 's#.*/v##' ) - SETUP_PLACEHOLDER='__version__ = "0.dev0"' - SETUP_FILE='setup.py' - DOCS_PLACEHOLDER='__version__' - DOCS_CONF_FILE='docs/source/conf.py' - - grep "$PLACEHOLDER" "$SETUP_FILE" - /bin/sed -i "s/$SETUP_PLACEHOLDER/__version__ = \"${VERSION}\"/g" "$SETUP_FILE" - - grep "$PLACEHOLDER" "$DOCS_CONF_FILE" - /bin/sed -i "s/$DOCS_PLACEHOLDER/${VERSION}/" "$DOCS_CONF_FILE" - shell: bash - - name: Install dependencies - run: | - python -m pip install --upgrade pip - python -m pip install setuptools wheel - - name: Build a binary wheel and a source tarball - run: | - python setup.py sdist bdist_wheel - - name: Publish distribution to PyPi - uses: pypa/gh-action-pypi-publish@master - with: - password: ${{ secrets.PYPI_API_TOKEN }} - - uses: ammaraskar/sphinx-action@master - with: - docs-folder: "docs/" - - name: Commit documentation changes - run: | - git clone https://github.com/EL-BID/urbanpy.git --branch gh-pages --single-branch gh-pages - cp -r docs/build/html/* gh-pages/ - cd gh-pages - git config --local user.email "claudio.rtega2701@gmail.com" - git config --local user.name "Claudio Ortega" - git add . - git commit -m "Update documentation" -a || true - # The above command will fail if no changes were present, so we ignore - # the return code. - - name: Push changes - uses: ad-m/github-push-action@master - with: - branch: gh-pages - directory: gh-pages - github_token: ${{ secrets.GITHUB_TOKEN }} + - name: Check out repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Install uv + uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 + with: + version: "0.12.3" + enable-cache: true + + - name: Export locked runtime dependencies + run: uv export --no-dev --no-hashes --no-emit-project --output-file requirements-audit.txt + + - name: Audit known vulnerabilities + run: uvx --from pip-audit==2.10.1 pip-audit --requirement requirements-audit.txt + + docs: + name: Documentation + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Install uv + uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 + with: + version: "0.12.3" + enable-cache: true + + - name: Install locked documentation environment + run: uv sync --group docs --python 3.11 + + - name: Build documentation + run: uv run --python 3.11 sphinx-build --fail-on-warning --keep-going -b html docs/source docs/_build/html diff --git a/docs/source/conf.py b/docs/source/conf.py index 8d481b9..fd10ae4 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -20,7 +20,7 @@ # sys.path.insert(0, os.path.abspath('../../urbanpy/plotting')) # sys.path.insert(0, os.path.abspath('../../urbanpy/routing')) # sys.path.insert(0, os.path.abspath('../../urbanpy/utils')) -import sphinx_rtd_theme +from urbanpy import __version__ # -- Project information ----------------------------------------------------- @@ -29,7 +29,7 @@ author = "Andres Regal, Claudio Ortega & Antonio Vasquez Brust" # The full version, including alpha/beta/rc tags -release = "__version__" +release = __version__ # -- General configuration --------------------------------------------------- diff --git a/pyproject.toml b/pyproject.toml index 92f90c3..88bbe33 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -112,7 +112,7 @@ include = [ packages = ["urbanpy"] [tool.pytest.ini_options] -addopts = "--strict-config --strict-markers" +addopts = "--strict-config --strict-markers --disable-socket -m 'not live and not docker'" python_files = ["test_*.py", "check_*.py", "example_*.py", "*_test.py", "*_tests.py"] testpaths = ["tests"] markers = [ @@ -138,11 +138,12 @@ line-length = 88 target-version = "py311" [tool.ruff.lint] -select = ["E4", "E7", "E9", "F", "I"] +# Start with correctness rules that the legacy tree already satisfies. The 0.3 +# lint workstream expands this ratchet after its mechanical cleanup PR. +select = ["E9", "F63", "F7", "F82"] [tool.mypy] python_version = "3.11" plugins = ["pydantic.mypy"] show_error_codes = true warn_unused_configs = true - diff --git a/sonar-project.properties b/sonar-project.properties index 16119d2..9ce8e46 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -2,10 +2,12 @@ sonar.projectKey=EL-BID_urbanpy sonar.projectName=urbanpy # Source code -sonar.sources=. +sonar.sources=urbanpy +sonar.tests=tests +sonar.python.coverage.reportPaths=coverage.xml # Common exclusions -sonar.exclusions=**/node_modules/**,**/dist/**,**/build/**,**/target/**,**/*.min.js,**/vendor/**,**/__pycache__/**,**/*.pyc,**/venv/**,**/.venv/**,**/coverage/** +sonar.exclusions=**/__pycache__/**,**/*.pyc # SCM -sonar.scm.provider=git \ No newline at end of file +sonar.scm.provider=git diff --git a/tests/download_test.py b/tests/download_test.py index 0d8308b..2f5b679 100644 --- a/tests/download_test.py +++ b/tests/download_test.py @@ -1,31 +1,108 @@ -import unittest -import sys -import pandas as pd +from datetime import date -sys.path.append("../urbanpy") -import urbanpy as up +import geopandas as gpd +import pytest +import responses +from shapely.geometry import Polygon +from urbanpy import download +from urbanpy.download import download as download_module -class DownloadTest(unittest.TestCase): - country = "peru" - def test_search_hdx(self): - datasets = up.download.search_hdx_dataset(self.country) - self.assertEqual(7, len(datasets)) +def test_nominatim_requires_contact_email(): + with pytest.raises(ValueError, match="provide an email"): + download.nominatim_osm("Lima, Peru") - # def download_hdx_test(self): - # df = up.download.download_hdx_dataset(country, 0) - # test_df = pd.DataFrame([\ - # [-18.339306, -70.382361, 11.318147, 12.099885],\ - # [-18.335694, -70.393750, 11.318147, 12.099885],\ - # [-18.335694, -70.387361, 11.318147, 12.099885],\ - # [-18.335417, -70.394028, 11.318147, 12.099885],\ - # [-18.335139, -70.394306, 11.318147, 12.099885]]\, - # columns=['latitude', 'longitude', 'population_2015', 'population_2020']) +def test_nominatim_parses_captured_geojson(): + payload = { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": {"display_name": "Lima, Peru"}, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-77.1, -12.2], + [-76.9, -12.2], + [-76.9, -12.0], + [-77.1, -12.2], + ] + ], + }, + } + ], + } + with responses.RequestsMock() as captured: + captured.get( + "https://nominatim.openstreetmap.org/search.php", json=payload + ) + result = download.nominatim_osm("Lima, Peru", email="dev@example.org") - # self.assertEqual(df, test_df) + assert list(result["display_name"]) == ["Lima, Peru"] + assert result.crs.to_string() == "EPSG:4326" -if __name__ == "__main__": - unittest.main() +def test_search_hdx_dataset_uses_captured_provider_records(monkeypatch): + records = [ + { + "created": "2024-01-02T00:00:00", + "name": "population_per_2024.csv", + "size": 2**20, + "download_url": "https://example.org/population.csv", + "url": "https://example.org/population.csv", + }, + { + "created": "2024-01-02T00:00:00", + "name": "metadata.pdf", + "size": 100, + "download_url": "https://example.org/metadata.pdf", + "url": "https://example.org/metadata.pdf", + }, + ] + monkeypatch.setattr( + download_module.Dataset, "search_in_hdx", lambda _query: [object()] + ) + monkeypatch.setattr( + download_module.Dataset, "get_all_resources", lambda _datasets: records + ) + + result = download.search_hdx_dataset("Peru") + + assert len(result) == 1 + assert result.iloc[0].to_dict() == { + "created": date(2024, 1, 2), + "name": "population_per_2024.csv", + "population": "Overall population density", + "size_mb": 1.0, + "url": "https://example.org/population.csv", + } + + +def test_osmnx_graph_validates_required_arguments(capsys): + assert download.osmnx_graph("polygon") is None + assert "provide a polygon" in capsys.readouterr().out.lower() + + +def test_overpass_accepts_a_local_polygon_mask(monkeypatch): + class Response: + status_code = 200 + reason = "OK" + + @staticmethod + def json(): + return {"elements": []} + + monkeypatch.setattr( + download_module.requests, "get", lambda *_args, **_kwargs: Response() + ) + monkeypatch.setattr( + download_module, "overpass_to_gdf", lambda *_args: ("gdf", None) + ) + mask = gpd.GeoDataFrame( + geometry=[Polygon([(0, 0), (1, 0), (1, 1), (0, 0)])], crs="EPSG:4326" + ) + + assert download.overpass("node", {"amenity": "school"}, mask) == ("gdf", None) diff --git a/tests/geom_test.py b/tests/geom_test.py index 5a2f052..d336f1f 100644 --- a/tests/geom_test.py +++ b/tests/geom_test.py @@ -1,182 +1,64 @@ -import unittest import geopandas as gpd -import numpy as np -import networkx as nx -from shapely.geometry import Polygon +import pandas as pd +from shapely.geometry import Point, Polygon -import sys +from urbanpy import geom -sys.path.append("../urbanpy") -import urbanpy as up +def _city(): + polygon = Polygon( + [(-77.05, -12.10), (-77.00, -12.10), (-77.00, -12.05), (-77.05, -12.05)] + ) + return gpd.GeoDataFrame(geometry=[polygon], crs="EPSG:4326") -class GeomTest(unittest.TestCase): - # TODO: implement test for overlay_polygons_hexs and osmnx_coefficient_computation - def test_merge_geom_download(self): - """ - Test Merge several GeoDataFrames from OSM download_osm - """ +def test_merge_geom_downloads_unions_local_polygons(): + left = gpd.GeoDataFrame( + geometry=[Polygon([(0, 0), (1, 0), (1, 1), (0, 0)])], crs="EPSG:4326" + ) + right = gpd.GeoDataFrame( + geometry=[Polygon([(1, 0), (2, 0), (2, 1), (1, 0)])], crs="EPSG:4326" + ) - # Example test values - gdf1 = up.download.nominatim_osm(query="Jesús María, Lima, Peru", email="claudio.rtega2701@gmail.com") - gdf2 = up.download.nominatim_osm(query="Lince, Lima, Peru", email="claudio.rtega2701@gmail.com") + merged = geom.merge_geom_downloads([left, right]) - # Merged - merged_geom = up.geom.merge_geom_downloads([gdf1, gdf2]) + assert len(merged) == 1 + assert merged.geometry.iloc[0].covers(left.geometry.iloc[0]) + assert merged.geometry.iloc[0].covers(right.geometry.iloc[0]) - # Difference merged geom with sources - empty_polygon = merged_geom.difference(gdf1).difference(gdf2)[0] - # Test empty polygon - self.assertEqual(Polygon(), empty_polygon) +def test_filter_and_remove_population_points(): + population = pd.DataFrame( + { + "longitude": [-77.03, -77.01, -78.0], + "latitude": [-12.08, -12.06, -13.0], + "population": [10, 20, 30], + } + ) - def test_filter_population(self): - """ - Test filtering of population dataframe with a GeoDataFrame with a polygon geometry. + filtered = geom.filter_population(population, _city()) + remaining = geom.remove_features(filtered, [-77.04, -12.09, -77.02, -12.07]) - * Download population data - * Download city limits - * Filter population data within the city limits + assert list(filtered["population"]) == [10, 20] + assert list(remaining["population"]) == [20] - """ - # Example test values - datasets_df = up.download.search_hdx_dataset("bolivia") - pop_df = up.download.get_hdx_dataset(datasets_df, 0) - polygon_gdf = up.download.nominatim_osm("La Paz, Bolivia", 1, email="claudio.rtega2701@gmail.com") +def test_hexagon_generation_merge_and_downsampling(): + hexagons = geom.gen_hexagons(8, _city()) + points = gpd.GeoDataFrame( + {"population": [3, 7]}, + geometry=[Point(-77.03, -12.08), Point(-77.01, -12.06)], + crs="EPSG:4326", + ) - # Filter pop - filtered_points_gdf = up.geom.filter_population(pop_df, polygon_gdf) + merged = geom.merge_shape_hex( + hexagons, points, {"population": "sum"}, predicate="within" + ) + coarse = geom.resolution_downsampling( + merged.fillna({"population": 0}), "hex", 7, {"population": "sum"} + ) - # Get bounding box to test the result - minx, miny, maxx, maxy = polygon_gdf.geometry.total_bounds - - # Test path length - self.assertEqual( - filtered_points_gdf.shape, - filtered_points_gdf.cx[minx:maxx, miny:maxy].shape, - ) - - def test_remove_features(self): - """ - Test the removal of a set of features based on bounds. - - * Download population data - * Download city limits - * Filter population data within the city limits - - """ - - # Download city limits - polygon_gdf = up.download.nominatim_osm("La Paz, Bolivia", 1, email="claudio.rtega2701@gmail.com") - - # Example test values - bounds = polygon_gdf.geometry.centroid.buffer(0.1).total_bounds - datasets_df = up.download.search_hdx_dataset("bolivia") - pop_df = up.download.get_hdx_dataset(datasets_df, 0) - filtered_points_gdf = up.geom.filter_population(pop_df, polygon_gdf) - - # Remove features from bounding box - features_removed = up.geom.remove_features(filtered_points_gdf, bounds) - - # Bounding box for validation - minx, miny, maxx, maxy = bounds - - # Test path length - self.assertEqual(True, features_removed.cx[minx:maxx, miny:maxy].empty) - - def test_gen_hexagons(self): - """ - Test the generation of H3 hexagons for a given input GeoDataFrame with a polygon or multipolygon geometry. - - * Download city limits - * Generate H3 hexagons to fill the city limits - - """ - - # Download city limits - polygon_gdf = up.download.nominatim_osm("La Paz, Bolivia", 0, email="claudio.rtega2701@gmail.com") - - # Generate hexs - hex_gdf = up.geom.gen_hexagons(resolution=6, city=polygon_gdf) - - # Test the number of hexagons generated - self.assertEqual((51, 2), hex_gdf.shape) - - def test_merge_shape_hex(self): - """ - Test the aggregation of a metric from a smaller shapes (e.g. Points) with a H3 hexagon GeoDataFrame. - - * Download city limits - * Download population data - * Merge data - - """ - - # Download city limits - polygon_gdf = up.download.nominatim_osm("La Paz, Bolivia", 1, email="claudio.rtega2701@gmail.com") - - # Example test values - datasets_df = up.download.search_hdx_dataset("bolivia") - pop_df = up.download.get_hdx_dataset(datasets_df, 0) - filtered_points_gdf = up.geom.filter_population(pop_df, polygon_gdf) - hex_gdf = up.geom.gen_hexagons(resolution=6, city=polygon_gdf) - - # Aggregate pop metric - merged_hex = up.geom.merge_shape_hex( - hex_gdf, filtered_points_gdf, agg={"bol_general_2020": "sum"} - ) - - # Sum aggregated metric with hexagons - population_hexs_sum = merged_hex["bol_general_2020"].sum() - - # Sum metric with spatial filter - spatial_filter = filtered_points_gdf.geometry.intersects( - hex_gdf.geometry.unary_union - ) - population_points_sum = filtered_points_gdf[spatial_filter][ - "bol_general_2020" - ].sum() - - # Delta - delta = population_hexs_sum - population_points_sum - print(delta) - - # Test the diff of aggregated population - self.assertAlmostEqual(0, delta) - - def test_resolution_downsampling(self): - """ - Test downsampling hexagon resolution and aggregating indicated metrics. - - * Download city limits - * Download population data - * Merge data - - """ - # Download city limits - polygon_gdf = up.download.nominatim_osm("La Paz, Bolivia", 0, email="claudio.rtega2701@gmail.com") - - # Example test values - datasets_df = up.download.search_hdx_dataset("bolivia") - pop_df = up.download.get_hdx_dataset(datasets_df, 0) - filtered_points_gdf = up.geom.filter_population(pop_df, polygon_gdf) - hex_gdf = up.geom.gen_hexagons(resolution=6, city=polygon_gdf) - - # Aggregate pop metric - merged_hex = up.geom.merge_shape_hex( - hex_gdf, filtered_points_gdf, agg={"bol_general_2020": "sum"} - ) - - # Downsample data - hex_downsampled = up.geom.resolution_downsampling( - merged_hex, "hex", 5, {"bol_general_2020": "sum"} - ) - - # Test the number of hexagons and indicators generated - self.assertEqual((14, 3), hex_downsampled.shape) - - -if __name__ == "__main__": - unittest.main() + assert not hexagons.empty + assert merged["population"].sum() == 10 + assert coarse["population"].sum() == 10 + assert coarse.crs == hexagons.crs diff --git a/tests/routing_test.py b/tests/routing_test.py index ac9a065..28e2162 100644 --- a/tests/routing_test.py +++ b/tests/routing_test.py @@ -1,16 +1,16 @@ import unittest + import geopandas as gpd import numpy as np +import pytest -import sys - -sys.path.append("../urbanpy") import urbanpy as up class RoutingTest(unittest.TestCase): # TODO implement tests for google distance matrix and ors distance matrix + @pytest.mark.docker def test_osrm_matrix(self): """ Test OSRM distance and duration matrix @@ -99,6 +99,7 @@ def test_osrm_matrix(self): # Close OSRM routing Server up.routing.stop_osrm_server("peru", "south-america", "foot") + @pytest.mark.live def test_nx_route(self): """ Test path finding interface with networkx. @@ -134,12 +135,5 @@ def test_nx_route(self): # Test number of nodes self.assertEqual(up.routing.nx_route(G, source, target, None), -1) - def test_google_matrix(self): - pass - - def test_ors_matrix(self): - pass - - if __name__ == "__main__": unittest.main() diff --git a/tests/utils_test.py b/tests/utils_test.py index a259814..7f03153 100644 --- a/tests/utils_test.py +++ b/tests/utils_test.py @@ -1,10 +1,8 @@ import unittest -import numpy as np -from shapely.geometry import Point, Polygon, LineString -import sys +import numpy as np +from shapely.geometry import LineString, Point, Polygon -sys.path.append("../urbanpy") import urbanpy as up @@ -15,11 +13,11 @@ def test_xy_swap(self): """ p = Point(-77.0, -12.0) - l = LineString([[-77.0, -12.0], [-78.0, -13.0]]) + line = LineString([[-77.0, -12.0], [-78.0, -13.0]]) poly = Polygon([(-77.0, -12.0), (-78.0, -11.0), (-77.0, -11.0)]) swapped_p = up.utils.swap_xy(p) - swapped_l = up.utils.swap_xy(l) + swapped_l = up.utils.swap_xy(line) swapped_poly = up.utils.swap_xy(poly) self.assertEqual(list(swapped_p.coords), [(-12.0, -77.0)])