diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index 5c04afa..0000000 --- a/.coveragerc +++ /dev/null @@ -1,4 +0,0 @@ -[report] -omit = - tests/* - /usr/lib/python3/dist-packages/* \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 44f34bc..7ff0502 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,35 +1,47 @@ -name: Test +name: Test plugin on: pull_request: - branches: [main] - workflow_dispatch: + branches: + - main + +env: + # plugin name/directory where the code for the plugin is stored + PLUGIN_NAME: gtfsgo + # python notation to test running inside plugin + TESTS_RUN_FUNCTION: gtfsgo.test_suite.test_package + # Docker settings + DOCKER_IMAGE: qgis/qgis jobs: - Test: + Test-plugin-gtfsgo: runs-on: ubuntu-latest + strategy: matrix: - qgis_image_tag: [release-3_28] - env: - OS: ubuntu - QGIS_IMAGE_TAG: ${{ matrix.qgis_image_tag }} - steps: - - uses: actions/checkout@v3 + docker_tags: [release-3_28, release-3_34] - - name: Pull QGIS - run: docker pull qgis/qgis:${{ matrix.qgis_image_tag }} + steps: + - name: Checkout + uses: actions/checkout@v2 - name: Export requirements.txt run: | pip3 install poetry poetry export --with dev -f requirements.txt -o requirements.txt - - name: Run tests - run: docker run --rm --net=host --volume .:/app -w=/app -e QGIS_PLUGIN_IN_CI=1 qgis/qgis:${{ matrix.qgis_image_tag }} sh -c "pip3 install -r /app/requirements.txt && xvfb-run -s '+extension GLX -screen 0 1024x768x24' pytest -v --cov --cov-report=xml --cov-report=term" + - name: Docker pull and create qgis-testing-environment + run: | + docker pull "$DOCKER_IMAGE":${{ matrix.docker_tags }} + docker run -d --name qgis-testing-environment -v .:/tests_directory/gtfsgo -e DISPLAY=:99 "$DOCKER_IMAGE":${{ matrix.docker_tags }} + + - name: Docker set up QGIS + run: | + docker exec qgis-testing-environment sh -c "qgis_setup.sh $PLUGIN_NAME" + docker exec qgis-testing-environment sh -c "rm -f /root/.local/share/QGIS/QGIS3/profiles/default/python/plugins/$PLUGIN_NAME" + docker exec qgis-testing-environment sh -c "ln -s /tests_directory/$PLUGIN_NAME /root/.local/share/QGIS/QGIS3/profiles/default/python/plugins/$PLUGIN_NAME" + docker exec qgis-testing-environment sh -c "pip3 install -r /tests_directory/$PLUGIN_NAME/requirements.txt" - - name: Upload coverage reports to Codecov - uses: codecov/codecov-action@v4.0.1 - with: - token: ${{ secrets.CODECOV_TOKEN }} - slug: MIERUNE/GTFS-GO + - name: Docker run plugin tests + run: | + docker exec qgis-testing-environment sh -c "qgis_testrunner.sh $TESTS_RUN_FUNCTION" diff --git a/poetry.lock b/poetry.lock index 0ab6017..586eacf 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,131 +1,5 @@ # This file is automatically @generated by Poetry and should not be changed by hand. -[[package]] -name = "attrs" -version = "22.2.0" -description = "Classes Without Boilerplate" -category = "dev" -optional = false -python-versions = ">=3.6" -files = [ - {file = "attrs-22.2.0-py3-none-any.whl", hash = "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836"}, - {file = "attrs-22.2.0.tar.gz", hash = "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"}, -] - -[package.extras] -cov = ["attrs[tests]", "coverage-enable-subprocess", "coverage[toml] (>=5.3)"] -dev = ["attrs[docs,tests]"] -docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope.interface"] -tests = ["attrs[tests-no-zope]", "zope.interface"] -tests-no-zope = ["cloudpickle", "cloudpickle", "hypothesis", "hypothesis", "mypy (>=0.971,<0.990)", "mypy (>=0.971,<0.990)", "pympler", "pympler", "pytest (>=4.3.0)", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-mypy-plugins", "pytest-xdist[psutil]", "pytest-xdist[psutil]"] - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -category = "dev" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] - -[[package]] -name = "coverage" -version = "7.5.2" -description = "Code coverage measurement for Python" -category = "dev" -optional = false -python-versions = ">=3.8" -files = [ - {file = "coverage-7.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:554c7327bf0fd688050348e22db7c8e163fb7219f3ecdd4732d7ed606b417263"}, - {file = "coverage-7.5.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d0305e02e40c7cfea5d08d6368576537a74c0eea62b77633179748d3519d6705"}, - {file = "coverage-7.5.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:829fb55ad437d757c70d5b1c51cfda9377f31506a0a3f3ac282bc6a387d6a5f1"}, - {file = "coverage-7.5.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:894b1acded706f1407a662d08e026bfd0ff1e59e9bd32062fea9d862564cfb65"}, - {file = "coverage-7.5.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe76d6dee5e4febefa83998b17926df3a04e5089e3d2b1688c74a9157798d7a2"}, - {file = "coverage-7.5.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c7ebf2a37e4f5fea3c1a11e1f47cea7d75d0f2d8ef69635ddbd5c927083211fc"}, - {file = "coverage-7.5.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20e611fc36e1a0fc7bbf957ef9c635c8807d71fbe5643e51b2769b3cc0fb0b51"}, - {file = "coverage-7.5.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7c5c5b7ae2763533152880d5b5b451acbc1089ade2336b710a24b2b0f5239d20"}, - {file = "coverage-7.5.2-cp310-cp310-win32.whl", hash = "sha256:1e4225990a87df898e40ca31c9e830c15c2c53b1d33df592bc8ef314d71f0281"}, - {file = "coverage-7.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:976cd92d9420e6e2aa6ce6a9d61f2b490e07cb468968adf371546b33b829284b"}, - {file = "coverage-7.5.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5997d418c219dcd4dcba64e50671cca849aaf0dac3d7a2eeeb7d651a5bd735b8"}, - {file = "coverage-7.5.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ec27e93bbf5976f0465e8936f02eb5add99bbe4e4e7b233607e4d7622912d68d"}, - {file = "coverage-7.5.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f11f98753800eb1ec872562a398081f6695f91cd01ce39819e36621003ec52a"}, - {file = "coverage-7.5.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e34680049eecb30b6498784c9637c1c74277dcb1db75649a152f8004fbd6646"}, - {file = "coverage-7.5.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e12536446ad4527ac8ed91d8a607813085683bcce27af69e3b31cd72b3c5960"}, - {file = "coverage-7.5.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:3d3f7744b8a8079d69af69d512e5abed4fb473057625588ce126088e50d05493"}, - {file = "coverage-7.5.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:431a3917e32223fcdb90b79fe60185864a9109631ebc05f6c5aa03781a00b513"}, - {file = "coverage-7.5.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a7c6574225f34ce45466f04751d957b5c5e6b69fca9351db017c9249786172ce"}, - {file = "coverage-7.5.2-cp311-cp311-win32.whl", hash = "sha256:2b144d142ec9987276aeff1326edbc0df8ba4afbd7232f0ca10ad57a115e95b6"}, - {file = "coverage-7.5.2-cp311-cp311-win_amd64.whl", hash = "sha256:900532713115ac58bc3491b9d2b52704a05ed408ba0918d57fd72c94bc47fba1"}, - {file = "coverage-7.5.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:9a42970ce74c88bdf144df11c52c5cf4ad610d860de87c0883385a1c9d9fa4ab"}, - {file = "coverage-7.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:26716a1118c6ce2188283b4b60a898c3be29b480acbd0a91446ced4fe4e780d8"}, - {file = "coverage-7.5.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:60b66b0363c5a2a79fba3d1cd7430c25bbd92c923d031cae906bdcb6e054d9a2"}, - {file = "coverage-7.5.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5d22eba19273b2069e4efeff88c897a26bdc64633cbe0357a198f92dca94268"}, - {file = "coverage-7.5.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3bb5b92a0ab3d22dfdbfe845e2fef92717b067bdf41a5b68c7e3e857c0cff1a4"}, - {file = "coverage-7.5.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1aef719b6559b521ae913ddeb38f5048c6d1a3d366865e8b320270b7bc4693c2"}, - {file = "coverage-7.5.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8809c0ea0e8454f756e3bd5c36d04dddf222989216788a25bfd6724bfcee342c"}, - {file = "coverage-7.5.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1acc2e2ef098a1d4bf535758085f508097316d738101a97c3f996bccba963ea5"}, - {file = "coverage-7.5.2-cp312-cp312-win32.whl", hash = "sha256:97de509043d3f0f2b2cd171bdccf408f175c7f7a99d36d566b1ae4dd84107985"}, - {file = "coverage-7.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:8941e35a0e991a7a20a1fa3e3182f82abe357211f2c335a9e6007067c3392fcf"}, - {file = "coverage-7.5.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5662bf0f6fb6757f5c2d6279c541a5af55a39772c2362ed0920b27e3ce0e21f7"}, - {file = "coverage-7.5.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3d9c62cff2ffb4c2a95328488fd7aa96a7a4b34873150650fe76b19c08c9c792"}, - {file = "coverage-7.5.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74eeaa13e8200ad72fca9c5f37395fb310915cec6f1682b21375e84fd9770e84"}, - {file = "coverage-7.5.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f29bf497d51a5077994b265e976d78b09d9d0dff6ca5763dbb4804534a5d380"}, - {file = "coverage-7.5.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f96aa94739593ae0707eda9813ce363a0a0374a810ae0eced383340fc4a1f73"}, - {file = "coverage-7.5.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:51b6cee539168a912b4b3b040e4042b9e2c9a7ad9c8546c09e4eaeff3eacba6b"}, - {file = "coverage-7.5.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:59a75e6aa5c25b50b5a1499f9718f2edff54257f545718c4fb100f48d570ead4"}, - {file = "coverage-7.5.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:29da75ce20cb0a26d60e22658dd3230713c6c05a3465dd8ad040ffc991aea318"}, - {file = "coverage-7.5.2-cp38-cp38-win32.whl", hash = "sha256:23f2f16958b16152b43a39a5ecf4705757ddd284b3b17a77da3a62aef9c057ef"}, - {file = "coverage-7.5.2-cp38-cp38-win_amd64.whl", hash = "sha256:9e41c94035e5cdb362beed681b58a707e8dc29ea446ea1713d92afeded9d1ddd"}, - {file = "coverage-7.5.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:06d96b9b19bbe7f049c2be3c4f9e06737ec6d8ef8933c7c3a4c557ef07936e46"}, - {file = "coverage-7.5.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:878243e1206828908a6b4a9ca7b1aa8bee9eb129bf7186fc381d2646f4524ce9"}, - {file = "coverage-7.5.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:482df956b055d3009d10fce81af6ffab28215d7ed6ad4a15e5c8e67cb7c5251c"}, - {file = "coverage-7.5.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a35c97af60a5492e9e89f8b7153fe24eadfd61cb3a2fb600df1a25b5dab34b7e"}, - {file = "coverage-7.5.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24bb4c7859a3f757a116521d4d3a8a82befad56ea1bdacd17d6aafd113b0071e"}, - {file = "coverage-7.5.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e1046aab24c48c694f0793f669ac49ea68acde6a0798ac5388abe0a5615b5ec8"}, - {file = "coverage-7.5.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:448ec61ea9ea7916d5579939362509145caaecf03161f6f13e366aebb692a631"}, - {file = "coverage-7.5.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4a00bd5ba8f1a4114720bef283cf31583d6cb1c510ce890a6da6c4268f0070b7"}, - {file = "coverage-7.5.2-cp39-cp39-win32.whl", hash = "sha256:9f805481d5eff2a96bac4da1570ef662bf970f9a16580dc2c169c8c3183fa02b"}, - {file = "coverage-7.5.2-cp39-cp39-win_amd64.whl", hash = "sha256:2c79f058e7bec26b5295d53b8c39ecb623448c74ccc8378631f5cb5c16a7e02c"}, - {file = "coverage-7.5.2-pp38.pp39.pp310-none-any.whl", hash = "sha256:40dbb8e7727560fe8ab65efcddfec1ae25f30ef02e2f2e5d78cfb52a66781ec5"}, - {file = "coverage-7.5.2.tar.gz", hash = "sha256:13017a63b0e499c59b5ba94a8542fb62864ba3016127d1e4ef30d354fc2b00e9"}, -] - -[package.dependencies] -tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} - -[package.extras] -toml = ["tomli"] - -[[package]] -name = "exceptiongroup" -version = "1.1.0" -description = "Backport of PEP 654 (exception groups)" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "exceptiongroup-1.1.0-py3-none-any.whl", hash = "sha256:327cbda3da756e2de031a3107b81ab7b3770a602c4d16ca618298c526f4bec1e"}, - {file = "exceptiongroup-1.1.0.tar.gz", hash = "sha256:bcb67d800a4497e1b404c2dd44fca47d3b7a5e5433dbab67f96c1a685cdfdf23"}, -] - -[package.extras] -test = ["pytest (>=6)"] - -[[package]] -name = "iniconfig" -version = "2.0.0" -description = "brain-dead simple config-ini parsing" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, -] - [[package]] name = "numpy" version = "1.26.4" @@ -172,18 +46,6 @@ files = [ {file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"}, ] -[[package]] -name = "packaging" -version = "23.0" -description = "Core utilities for Python packages" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "packaging-23.0-py3-none-any.whl", hash = "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2"}, - {file = "packaging-23.0.tar.gz", hash = "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"}, -] - [[package]] name = "pandas" version = "2.2.2" @@ -258,118 +120,6 @@ sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-d test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] xml = ["lxml (>=4.9.2)"] -[[package]] -name = "pluggy" -version = "1.5.0" -description = "plugin and hook calling mechanisms for python" -category = "dev" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, - {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, -] - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] - -[[package]] -name = "pytest" -version = "7.2.1" -description = "pytest: simple powerful testing with Python" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "pytest-7.2.1-py3-none-any.whl", hash = "sha256:c7c6ca206e93355074ae32f7403e8ea12163b1163c976fee7d4d84027c162be5"}, - {file = "pytest-7.2.1.tar.gz", hash = "sha256:d45e0952f3727241918b8fd0f376f5ff6b301cc0777c6f9a556935c92d8a7d42"}, -] - -[package.dependencies] -attrs = ">=19.2.0" -colorama = {version = "*", markers = "sys_platform == \"win32\""} -exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=0.12,<2.0" -tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} - -[package.extras] -testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] - -[[package]] -name = "pytest-cov" -version = "5.0.0" -description = "Pytest plugin for measuring coverage." -category = "dev" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857"}, - {file = "pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652"}, -] - -[package.dependencies] -coverage = {version = ">=5.2.1", extras = ["toml"]} -pytest = ">=4.6" - -[package.extras] -testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] - -[[package]] -name = "pytest-mock" -version = "3.14.0" -description = "Thin-wrapper around the mock package for easier use with pytest" -category = "dev" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"}, - {file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"}, -] - -[package.dependencies] -pytest = ">=6.2.5" - -[package.extras] -dev = ["pre-commit", "pytest-asyncio", "tox"] - -[[package]] -name = "pytest-qgis" -version = "2.0.0" -description = "A pytest plugin for testing QGIS python plugins" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "pytest-qgis-2.0.0.tar.gz", hash = "sha256:1a801dfa85a339c36a1a3ffac1c4224d5a05e9acd77aa50782a4ea6e50fd38c9"}, - {file = "pytest_qgis-2.0.0-py3-none-any.whl", hash = "sha256:b09f374f593292e09f45dc5a8c8e40b83e8d8d80f32f64a6482d21626b465086"}, -] - -[package.dependencies] -pytest = ">=6.0" - -[[package]] -name = "pytest-qt" -version = "4.4.0" -description = "pytest support for PyQt and PySide applications" -category = "dev" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pytest-qt-4.4.0.tar.gz", hash = "sha256:76896142a940a4285339008d6928a36d4be74afec7e634577e842c9cc5c56844"}, - {file = "pytest_qt-4.4.0-py3-none-any.whl", hash = "sha256:001ed2f8641764b394cf286dc8a4203e40eaf9fff75bf0bfe5103f7f8d0c591d"}, -] - -[package.dependencies] -pluggy = ">=1.1" -pytest = "*" - -[package.extras] -dev = ["pre-commit", "tox"] -doc = ["sphinx", "sphinx-rtd-theme"] - [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -436,18 +186,6 @@ files = [ {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] -[[package]] -name = "tomli" -version = "2.0.1" -description = "A lil' TOML parser" -category = "dev" -optional = false -python-versions = ">=3.7" -files = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, -] - [[package]] name = "tzdata" version = "2024.1" @@ -463,4 +201,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "1390d9a37044c5d0e692ab25f6557e7834afbc0da043f94fe62b19917f61911f" +content-hash = "a4720e0cabe241d8069aff076a50d004fcd1c38a0c9d2726a46269fdcd7709aa" diff --git a/pyproject.toml b/pyproject.toml index 43f0cf1..159c74f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,13 +10,8 @@ python = "^3.9" [tool.poetry.group.dev.dependencies] -pytest = "^7.2.1" ruff = "^0.4.3" -pytest-qt = "^4.4.0" -pytest-cov = "^5.0.0" pandas = "^2.2.2" -pytest-mock = "^3.14.0" -pytest-qgis = "^2.0.0" [build-system] @@ -25,5 +20,5 @@ build-backend = "poetry.core.masonry.api" [tool.ruff] select = ["F", "E", "W", "B", "C90", "N", "I"] -ignore = ["N999", "N802", "F401", "E501"] +ignore = ["N999", "N802", "F401", "E501", "N806", "C901", "N803", "N815", "B018"] target-version = "py39" \ No newline at end of file diff --git a/test_suite.py b/test_suite.py new file mode 100644 index 0000000..c5fd63e --- /dev/null +++ b/test_suite.py @@ -0,0 +1,85 @@ +""" +Test Suite. +""" + +import os +import sys +import tempfile +import unittest + +from osgeo import gdal +from qgis.core import Qgis + +try: + from pip import main as pipmain +except ImportError: + from pip._internal import main as pipmain + +try: + import coverage +except ImportError: + pipmain(["install", "coverage"]) + import coverage + +__author__ = "Alessandro Pasotti" +__revision__ = "$Format:%H$" +__date__ = "30/04/2018" +__copyright__ = "Copyright 2018, North Road" + + +def _run_tests(test_suite, package_name, with_coverage=False): + """Core function to test a test suite.""" + count = test_suite.countTestCases() + print("########") + print("%s tests has been discovered in %s" % (count, package_name)) + print("Python GDAL : %s" % gdal.VersionInfo("VERSION_NUM")) + print("QGIS version : {}".format(Qgis.version())) + print("########") + if with_coverage: + cov = coverage.Coverage( + source=["/processing_r"], + omit=["*/test/*"], + ) + cov.start() + + unittest.TextTestRunner(verbosity=3, stream=sys.stdout).run(test_suite) + + if with_coverage: + cov.stop() + cov.save() + + with tempfile.NamedTemporaryFile(delete=False) as report: + cov.report(file=report) + # Produce HTML reports in the `htmlcov` folder and open index.html + # cov.html_report() + report.close() + + with open(report.name, "r", encoding="utf8") as fin: + print(fin.read()) + + +def test_package(package="gtfsgo"): + """Test package. + This function is called by travis without arguments. + + :param package: The package to test. + :type package: str + """ + test_loader = unittest.defaultTestLoader + try: + test_suite = test_loader.discover(package) + except ImportError: + test_suite = unittest.TestSuite() + _run_tests(test_suite, package) + + +def test_environment(): + """Test package with an environment variable.""" + package = os.environ.get("TESTING_PACKAGE", "gtfsgo") + test_loader = unittest.defaultTestLoader + test_suite = test_loader.discover(package) + _run_tests(test_suite, package) + + +if __name__ == "__main__": + test_package() diff --git a/tests/conftest.py b/tests/conftest.py deleted file mode 100644 index fd7930e..0000000 --- a/tests/conftest.py +++ /dev/null @@ -1,22 +0,0 @@ -from typing import Iterable - -import pytest -from pytest_mock import MockerFixture -from qgis.gui import QgisInterface -from qgis.PyQt.QtCore import QSettings - -from ..__init__ import classFactory - - -@pytest.fixture() -def plugin(qgis_iface: QgisInterface, mocker: MockerFixture) -> Iterable[None]: - # mock - mocker.patch.object(QSettings, "value", return_value="en") - qgis_iface.addPluginToWebMenu = lambda x, y: None # pytest-qgisで未実装 - - _plugin = classFactory(qgis_iface) - _plugin.initGui() - - yield _plugin - - # _plugin.unload() QgisInterface.removePluginMenu()がpytest-qgisで未実装 diff --git a/tests/qgis_interface.py b/tests/qgis_interface.py new file mode 100644 index 0000000..5ffc59f --- /dev/null +++ b/tests/qgis_interface.py @@ -0,0 +1,224 @@ +# coding=utf-8 +"""QGIS plugin implementation. + +.. note:: This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + +.. note:: This source code was copied from the 'postgis viewer' application + with original authors: + Copyright (c) 2010 by Ivan Mincik, ivan.mincik@gista.sk + Copyright (c) 2011 German Carrillo, geotux_tuxman@linuxmail.org + Copyright (c) 2014 Tim Sutton, tim@linfiniti.com + +""" + +__author__ = "tim@linfiniti.com" +__revision__ = "$Format:%H$" +__date__ = "10/01/2011" +__copyright__ = ( + "Copyright (c) 2010 by Ivan Mincik, ivan.mincik@gista.sk and " + "Copyright (c) 2011 German Carrillo, geotux_tuxman@linuxmail.org" + "Copyright (c) 2014 Tim Sutton, tim@linfiniti.com" +) + +from typing import List + +from PyQt5.QtCore import QObject, QSize, pyqtSignal, pyqtSlot +from qgis.core import QgsMapLayer, QgsProject +from qgis.gui import QgsMapCanvas, QgsMessageBar +from qgis.PyQt.QtWidgets import QDockWidget + + +# noinspection PyMethodMayBeStatic,PyPep8Naming +class QgisInterface(QObject): + """Class to expose QGIS objects and functions to plugins. + + This class is here for enabling us to run unit tests only, + so most methods are simply stubs. + """ + + currentLayerChanged = pyqtSignal(QgsMapLayer) + + def __init__(self, canvas: QgsMapCanvas): + """Constructor + :param canvas: + """ + QObject.__init__(self) + self.canvas = canvas + # Set up slots so we can mimic the behaviour of QGIS when layers + # are added. + + # noinspection PyArgumentList + QgsProject.instance().layersAdded.connect(self.addLayers) + # noinspection PyArgumentList + QgsProject.instance().layerWasAdded.connect(self.addLayer) + # noinspection PyArgumentList + QgsProject.instance().removeAll.connect(self.removeAllLayers) + + # For processing module + self.destCrs = None + + self.message_bar = QgsMessageBar() + + def addLayers(self, layers: List[QgsMapLayer]): + """Handle layers being added to the registry so they show up in canvas. + + :param layers: list list of map layers that were added + + .. note:: The QgsInterface api does not include this method, + it is added here as a helper to facilitate testing. + """ + # LOGGER.debug('addLayers called on qgis_interface') + # LOGGER.debug('Number of layers being added: %s' % len(layers)) + # LOGGER.debug('Layer Count Before: %s' % len(self.canvas.layers())) + current_layers = self.canvas.layers() + final_layers = [] + for layer in current_layers: + final_layers.append(layer) + for layer in layers: + final_layers.append(layer) + + self.canvas.setLayers(final_layers) + # LOGGER.debug('Layer Count After: %s' % len(self.canvas.layers())) + + def addLayer(self, layer: QgsMapLayer): + """Handle a layer being added to the registry so it shows up in canvas. + + :param layer: list list of map layers that were added + + .. note: The QgsInterface api does not include this method, it is added + here as a helper to facilitate testing. + + .. note: The addLayer method was deprecated in QGIS 1.8 so you should + not need this method much. + """ + pass # pylint: disable=unnecessary-pass + + @pyqtSlot() + def removeAllLayers(self): # pylint: disable=no-self-use + """Remove layers from the canvas before they get deleted.""" + self.canvas.setLayers([]) + + def newProject(self): # pylint: disable=no-self-use + """Create new project.""" + # noinspection PyArgumentList + QgsProject.instance().clear() + + # ---------------- API Mock for QgsInterface follows ------------------- + + def zoomFull(self): + """Zoom to the map full extent.""" + pass # pylint: disable=unnecessary-pass + + def zoomToPrevious(self): + """Zoom to previous view extent.""" + pass # pylint: disable=unnecessary-pass + + def zoomToNext(self): + """Zoom to next view extent.""" + pass # pylint: disable=unnecessary-pass + + def zoomToActiveLayer(self): + """Zoom to extent of active layer.""" + pass # pylint: disable=unnecessary-pass + + def addVectorLayer(self, path: str, base_name: str, provider_key: str): + """Add a vector layer. + + :param path: Path to layer. + :type path: str + + :param base_name: Base name for layer. + :type base_name: str + + :param provider_key: Provider key e.g. 'ogr' + :type provider_key: str + """ + pass # pylint: disable=unnecessary-pass + + def addRasterLayer(self, path: str, base_name: str): + """Add a raster layer given a raster layer file name + + :param path: Path to layer. + :type path: str + + :param base_name: Base name for layer. + :type base_name: str + """ + pass # pylint: disable=unnecessary-pass + + def activeLayer(self) -> QgsMapLayer: # pylint: disable=no-self-use + """Get pointer to the active layer (layer selected in the legend).""" + # noinspection PyArgumentList + layers = QgsProject.instance().mapLayers() + for item in layers: + return layers[item] + + def addToolBarIcon(self, action): + """Add an icon to the plugins toolbar. + + :param action: Action to add to the toolbar. + :type action: QAction + """ + pass # pylint: disable=unnecessary-pass + + def removeToolBarIcon(self, action): + """Remove an action (icon) from the plugin toolbar. + + :param action: Action to add to the toolbar. + :type action: QAction + """ + pass # pylint: disable=unnecessary-pass + + def addToolBar(self, name): + """Add toolbar with specified name. + + :param name: Name for the toolbar. + :type name: str + """ + pass # pylint: disable=unnecessary-pass + + def mapCanvas(self) -> QgsMapCanvas: + """Return a pointer to the map canvas.""" + return self.canvas + + def mainWindow(self): + """Return a pointer to the main window. + + In case of QGIS it returns an instance of QgisApp. + """ + pass # pylint: disable=unnecessary-pass + + def addDockWidget(self, area, dock_widget: QDockWidget): + """Add a dock widget to the main window. + + :param area: Where in the ui the dock should be placed. + :type area: + + :param dock_widget: A dock widget to add to the UI. + :type dock_widget: QDockWidget + """ + pass # pylint: disable=unnecessary-pass + + def legendInterface(self): + """Get the legend.""" + return self.canvas + + def iconSize(self, dockedToolbar) -> int: + """ + Returns the toolbar icon size. + :param dockedToolbar: If True, the icon size + for toolbars contained within docks is returned. + """ + if dockedToolbar: + return QSize(16, 16) + + return QSize(24, 24) + + def messageBar(self) -> QgsMessageBar: + """ + Return the message bar of the main app + """ + return self.message_bar diff --git a/tests/test_dialog.py b/tests/test_dialog.py new file mode 100644 index 0000000..a5d210c --- /dev/null +++ b/tests/test_dialog.py @@ -0,0 +1,24 @@ +import os +import unittest + +from gtfs_go_dialog import GTFSGoDialog + +from .utilities import get_qgis_app + +QGIS_APP, CANVAS, IFACE, PARENT = get_qgis_app() + + +class TestDialog(unittest.TestCase): + def test_dialog(self): + """Test the dialog.""" + dialog = GTFSGoDialog(IFACE) + + assert dialog.isVisible() is False + dialog.show() + assert dialog.isVisible() is True + dialog.close() + assert dialog.isVisible() is False + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_gtfs_go.py b/tests/test_gtfs_go.py deleted file mode 100644 index 4563349..0000000 --- a/tests/test_gtfs_go.py +++ /dev/null @@ -1,16 +0,0 @@ -from gtfs_go import GTFSGo - - -def test_run(plugin: GTFSGo): - """ダイアログを表示する関数のテスト""" - - # 初期状態でダイアログは未初期化 - assert plugin.dialog is None - - plugin.run() - assert plugin.dialog.isVisible() - - plugin.dialog.close() - - assert not plugin.dialog.isVisible() # ダイアログが閉じられても - assert plugin.dialog is not None # インスタンスは保持される diff --git a/tests/test_gtfs_go_dialog.py b/tests/test_gtfs_go_dialog.py deleted file mode 100644 index 6e3c2b4..0000000 --- a/tests/test_gtfs_go_dialog.py +++ /dev/null @@ -1,16 +0,0 @@ -from qgis.gui import QgisInterface - -from gtfs_go_dialog import GTFSGoDialog - - -def test_init(qgis_iface: QgisInterface): - dialog = GTFSGoDialog(qgis_iface) - assert not dialog.isVisible() - dialog.show() - assert dialog.isVisible() - dialog.close() - assert not dialog.isVisible() - - # init_gui() runs during init - assert dialog.repositoryCombobox.currentText() == "Preset" - assert dialog.comboBox.currentText() == "---Load local ZipFile---" diff --git a/tests/test_gtfs_go_labelling.py b/tests/test_gtfs_go_labelling.py deleted file mode 100644 index 92caf24..0000000 --- a/tests/test_gtfs_go_labelling.py +++ /dev/null @@ -1,9 +0,0 @@ -from gtfs_go_labeling import get_labeling_for_stops - - -def test_get_labeling_for_stops(): - labeling = get_labeling_for_stops() - assert labeling.settings().fieldName == "stop_name" # default value - - labeling = get_labeling_for_stops("testname") - assert labeling.settings().fieldName == "testname" diff --git a/tests/test_gtfs_go_renderer.py b/tests/test_gtfs_go_renderer.py deleted file mode 100644 index f47049b..0000000 --- a/tests/test_gtfs_go_renderer.py +++ /dev/null @@ -1,52 +0,0 @@ -from pytest_mock import MockerFixture -from qgis.core import ( - QgsCategorizedSymbolRenderer, - QgsSingleSymbolRenderer, - QgsVectorLayer, -) - -from gtfs_go_renderer import Renderer - - -def test_renderer_point(): - target_layer = QgsVectorLayer(r"""{ - "type": "Feature", - "geometry": { - "type": "Point", - "coordinates": [0.0, 0.0] - }, - "properties": { - "testname": "Null Island" - } - }""") - renderer = Renderer(target_layer, "testname") - symbol_renderer = renderer.make_renderer() - assert isinstance(symbol_renderer, QgsSingleSymbolRenderer) - - -def test_renderer_polygon(mocker: MockerFixture): - # in QGIS-API there are some classes which crash on test environment - # -> mock methods using them - mocker.patch("gtfs_go_renderer.Renderer._make_categories_by", return_value=[]) - - polygon_layer = QgsVectorLayer(r"""{ - "type": "Feature", - "geometry": { - "type": "Polygon", - "coordinates": [[[ - 0.0, 0.0, - 0.0, 1.0, - 1.0, 1.0, - 1.0, 0.0, - 0.0, 0.0 - ]]] - }, - "properties": { - "testname": "Null Square" - } - }""") - - p_renderer = Renderer(polygon_layer, "testname") - p_symbol_renderer = p_renderer.make_renderer() - # when input is polygon, QgsCategorizedSymbolRenderer is used - assert isinstance(p_symbol_renderer, QgsCategorizedSymbolRenderer) diff --git a/tests/test_metadata.py b/tests/test_metadata.py new file mode 100644 index 0000000..5a6e204 --- /dev/null +++ b/tests/test_metadata.py @@ -0,0 +1,47 @@ +import configparser +import os +import unittest + + +class TestInit(unittest.TestCase): + """Test that the plugin init is usable for QGIS. + reference: https://github.com/felt/qgis-plugin/blob/main/felt/test/test_init.py + """ + + def test_read_init(self): + """Test that the plugin __init__ will validate on plugins.qgis.org.""" + + # You should update this list according to the latest in + # https://github.com/qgis/qgis-django/blob/master/qgis-app/ + # plugins/validator.py + + required_metadata = [ + "name", + "description", + "version", + "qgisMinimumVersion", + "email", + "author", + ] + + file_path = os.path.abspath( + os.path.join(os.path.dirname(os.path.dirname(__file__)), "metadata.txt") + ) + metadata = [] + parser = configparser.ConfigParser() + parser.optionxform = str + parser.read(file_path) + message = 'Cannot find a section named "general" in %s' % file_path + assert parser.has_section("general"), message + metadata.extend(parser.items("general")) + for expectation in required_metadata: + message = 'Cannot find metadata "%s" in metadata source (%s).' % ( + expectation, + file_path, + ) + + self.assertIn(expectation, dict(metadata), message) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_qgis_environment.py b/tests/test_qgis_environment.py new file mode 100644 index 0000000..f32bae8 --- /dev/null +++ b/tests/test_qgis_environment.py @@ -0,0 +1,26 @@ +""" +Tests for QGIS functionality. +""" + +import unittest + +from qgis.core import QgsProviderRegistry + +from .utilities import get_qgis_app + +QGIS_APP = get_qgis_app() + + +class QGISTest(unittest.TestCase): + """Test the QGIS Environment""" + + def test_qgis_environment(self): + """QGIS environment has the expected providers""" + + r = QgsProviderRegistry.instance() + self.assertIn("gdal", r.providerList()) + self.assertIn("ogr", r.providerList()) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_repository_api.py b/tests/test_repository_api.py deleted file mode 100644 index 1ecf8fe..0000000 --- a/tests/test_repository_api.py +++ /dev/null @@ -1,20 +0,0 @@ -from repository.japan_dpf import api - - -def test_get_feeds_valid(): - feeds = api.get_feeds("2024-08-01") - assert len(feeds) > 0 - - feeds = api.get_feeds("2024-08-01", extent="139.7,35.6,139.8,35.7", pref="6") - assert len(feeds) > 0 - - -def test_get_feeds_invalid(): - feeds = api.get_feeds("2024-08-01", pref="48") # 48 is invalid pref - assert len(feeds) == 0 - - feeds = api.get_feeds("2000-08-01") # too old - assert len(feeds) == 0 - - feeds = api.get_feeds("2024-08-01", extent="0,0,0,0") # null island - assert len(feeds) == 0 diff --git a/tests/utilities.py b/tests/utilities.py new file mode 100644 index 0000000..22446c6 --- /dev/null +++ b/tests/utilities.py @@ -0,0 +1,111 @@ +""" +Common functionality used by regression tests. +""" + +import atexit +import logging +import os +import sys + +from qgis.core import QgsApplication +from qgis.gui import QgsMapCanvas +from qgis.PyQt.QtCore import QSize +from qgis.PyQt.QtWidgets import QWidget +from qgis.utils import iface + +from .qgis_interface import QgisInterface + +LOGGER = logging.getLogger("QGIS") +QGIS_APP = None # Static variable used to hold hand to running QGIS app +CANVAS = None +PARENT = None +IFACE = None + + +def get_qgis_app(cleanup=True): + """Start one QGIS application to test against. + + :returns: Handle to QGIS app, canvas, iface and parent. If there are any + errors the tuple members will be returned as None. + :rtype: (QgsApplication, CANVAS, IFACE, PARENT) + + If QGIS is already running the handle to that app will be returned. + """ + + global QGIS_APP, PARENT, IFACE, CANVAS # pylint: disable=W0603 + + if iface: + QGIS_APP = QgsApplication + CANVAS = iface.mapCanvas() + PARENT = iface.mainWindow() + IFACE = iface + return QGIS_APP, CANVAS, IFACE, PARENT + + global QGISAPP # pylint: disable=global-variable-undefined + + try: + QGISAPP # pylint: disable=used-before-assignment + except NameError: + myGuiFlag = True # All test will run qgis in gui mode + + # In python3 we need to convert to a bytes object (or should + # QgsApplication accept a QString instead of const char* ?) + try: + argvb = list(map(os.fsencode, sys.argv)) + except AttributeError: + argvb = sys.argv + + # Note: QGIS_PREFIX_PATH is evaluated in QgsApplication - + # no need to mess with it here. + QGISAPP = QgsApplication(argvb, myGuiFlag) + + QGISAPP.initQgis() + s = QGISAPP.showSettings() + LOGGER.debug(s) + + def debug_log_message(message, tag, level): + """ + Prints a debug message to a log + :param message: message to print + :param tag: log tag + :param level: log message level (severity) + :return: + """ + print("{}({}): {}".format(tag, level, message)) + + QgsApplication.instance().messageLog().messageReceived.connect( + debug_log_message + ) + + if cleanup: + + @atexit.register + def exitQgis(): # pylint: disable=unused-variable + """ + Gracefully closes the QgsApplication instance + """ + try: + # pylint: disable=used-before-assignment + # pylint: disable=redefined-outer-name + QGISAPP.exitQgis() # noqa + QGISAPP = None # noqa + # pylint: enable=used-before-assignment + # pylint: enable=redefined-outer-name + except NameError: + pass + + if PARENT is None: + # noinspection PyPep8Naming + PARENT = QWidget() + + if CANVAS is None: + # noinspection PyPep8Naming + CANVAS = QgsMapCanvas(PARENT) + CANVAS.resize(QSize(400, 400)) + + if IFACE is None: + # QgisInterface is a stub implementation of the QGIS plugin interface + # noinspection PyPep8Naming + IFACE = QgisInterface(CANVAS) + + return QGISAPP, CANVAS, IFACE, PARENT