diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 3b7b6436e..3e35476c9 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1,7 +1,13 @@ +# Copyright 2022 Canonical Ltd. +# See LICENSE file for licensing details. name: Tests + on: - workflow_call: pull_request: + schedule: + - cron: '53 0 * * *' # Daily at 00:53 UTC + # Triggered on push to branch "main" by .github/workflows/release.yaml + workflow_call: jobs: lint: @@ -10,10 +16,11 @@ jobs: steps: - name: Checkout uses: actions/checkout@v3 - - name: Install dependencies - run: python -m pip install tox + - name: Install tox + # TODO: Consider replacing with custom image on self-hosted runner OR pinning version + run: python3 -m pip install tox - name: Run linters - run: tox -e lint + run: tox run -e lint unit-test: name: Unit tests @@ -21,10 +28,11 @@ jobs: steps: - name: Checkout uses: actions/checkout@v3 - - name: Install dependencies - run: python -m pip install tox + - name: Install tox + # TODO: Consider replacing with custom image on self-hosted runner OR pinning version + run: python3 -m pip install tox - name: Run tests - run: tox -e unit + run: tox run -e unit lib-check: name: Check libraries @@ -40,150 +48,57 @@ jobs: credentials: "${{ secrets.CHARMHUB_TOKEN }}" github-token: "${{ secrets.GITHUB_TOKEN }}" - integration-standalone: - name: Integration tests for standalone charm - needs: - - lib-check - - lint - - unit-test - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Setup operator environment - # This part sets the model name to "testing" (used in test run and logdump) - uses: charmed-kubernetes/actions-operator@main - with: - provider: microk8s - # This is needed until https://bugs.launchpad.net/juju/+bug/1977582 is fixed. - bootstrap-options: "--agent-version 2.9.29" - - name: Run pgbouncer standalone integration tests - run: tox -e standalone-integration - - name: Dump logs - uses: canonical/charm-logdump-action@main - if: failure() - with: - app: pgbouncer-k8s - - integration-backend: - name: Integration tests for backend relation - needs: - - lib-check - - lint - - unit-test - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Setup operator environment - # This part sets the model name to "testing" (used in test run and logdump) - uses: charmed-kubernetes/actions-operator@main - with: - provider: microk8s - # This is needed until https://bugs.launchpad.net/juju/+bug/1977582 is fixed. - bootstrap-options: "--agent-version 2.9.29" - - name: Run backend integration tests - run: tox -e backend-integration - - name: Dump logs - uses: canonical/charm-logdump-action@main - if: failure() - with: - app: pgbouncer-k8s - - integration-legacy-client-relations: - name: Integration tests for legacy client relations - needs: - - lib-check - - lint - - unit-test - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Setup operator environment - # This part sets the model name to "testing" (used in test run and logdump) - uses: charmed-kubernetes/actions-operator@main - with: - provider: microk8s - # This is needed until https://bugs.launchpad.net/juju/+bug/1977582 is fixed. - bootstrap-options: "--agent-version 2.9.29" - - name: Run legacy integration tests - run: tox -e legacy-client-relation-integration - - name: Dump logs - uses: canonical/charm-logdump-action@main - if: failure() - with: - app: pgbouncer-k8s - - integration-client-relations: - name: Integration tests for updated client relations - needs: - - lib-check - - lint - - unit-test - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Setup operator environment - # This part sets the model name to "testing" (used in test run and logdump) - uses: charmed-kubernetes/actions-operator@main - with: - provider: microk8s - # This is needed until https://bugs.launchpad.net/juju/+bug/1977582 is fixed. - bootstrap-options: "--agent-version 2.9.29" - - name: Run client integration tests - run: tox -e client-relation-integration - - name: Dump logs - uses: canonical/charm-logdump-action@main - if: failure() - with: - app: pgbouncer-k8s + build: + name: Build charms + uses: canonical/data-platform-workflows/.github/workflows/build_charms_with_cache.yaml@v1 - integration-scaling: - name: Integration tests for scaling pgbouncer + integration-test: + strategy: + fail-fast: false + matrix: + tox-environments: + - standalone-integration + - backend-integration + - legacy-client-relation-integration + - client-relation-integration + - scaling-integration + - tls-integration + name: ${{ matrix.tox-environments }} needs: - lib-check - lint - unit-test + - build runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v3 - name: Setup operator environment - # This part sets the model name to "testing" (used in test run and logdump) + # TODO: Replace with custom image on self-hosted runner uses: charmed-kubernetes/actions-operator@main with: provider: microk8s - # This is needed until https://bugs.launchpad.net/juju/+bug/1977582 is fixed. + # This is needed until https://bugs.launchpad.net/juju/+bug/1977582 is fixed bootstrap-options: "--agent-version 2.9.29" - - name: Run scaling integration tests - run: tox -e scaling-integration - - name: Dump logs - uses: canonical/charm-logdump-action@main - if: failure() - with: - app: pgbouncer-k8s - - integration-test-microk8s-tls: - name: Integration tests for TLS - needs: - - lib-check - - lint - - unit-test - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Setup operator environment - # This part sets the model name to "testing" (used in test run and logdump) - uses: charmed-kubernetes/actions-operator@main + - name: Download packed charm(s) + uses: actions/download-artifact@v3 with: - provider: microk8s - # This is needed until https://bugs.launchpad.net/juju/+bug/1977582 is fixed. - bootstrap-options: "--agent-version 2.9.29" - - name: Run TLS integration tests - run: tox -e tls-integration + name: ${{ needs.build.outputs.artifact-name }} + - name: Select tests + id: select-tests + run: | + if [ "${{ github.event_name }}" == "schedule" ] + then + echo Running unstable and stable tests + echo "mark_expression=" >> $GITHUB_OUTPUT + else + echo Skipping unstable tests + echo "mark_expression=not unstable" >> $GITHUB_OUTPUT + fi + - name: Run integration tests + run: tox run -e ${{ matrix.tox-environments }} -- -m '${{ steps.select-tests.outputs.mark_expression }}' + env: + CI_PACKED_CHARMS: ${{ needs.build.outputs.charms }} - name: Dump logs uses: canonical/charm-logdump-action@main if: failure() diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index baf5a76e4..c9e20e9a9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -31,8 +31,8 @@ juju add-model dev juju model-config logging-config="=INFO;unit=DEBUG" # initialise an environment using tox -tox --notest -e unit -source .tox/unit/bin/activate +tox devenv -e integration +source venv/bin/activate # export kubernetes config to lightkube, for integration testing. microk8s config > ~/.kube/config @@ -43,12 +43,10 @@ microk8s config > ~/.kube/config Use the following tox commands to run tests: ```bash -tox -e fmt # update your code according to linting rules -tox -e lint # code style -tox -e unit # unit tests -tox -e smoke-integration # runs a small subset of integration tests that quickly verifies the charm is mostly working as intended. -tox -e dev-integration # Tag integration tests with `@pytest.mark.dev' to select tests to run using this command. -tox # runs 'fmt', 'lint', and 'unit' environments +tox run -e format # update your code according to linting rules +tox run -e lint # code style +tox run -e unit # unit tests +tox # runs 'fmt', 'lint', and 'unit' environments ``` Integration tests for individual functionality can be found in tox.ini diff --git a/pyproject.toml b/pyproject.toml index af8ef41d8..ca571c914 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,16 +12,7 @@ show_missing = true minversion = "6.0" log_cli_level = "INFO" asyncio_mode = "auto" -markers = [ - "smoke: quick integration tests that can be run to quickly verify that the charm builds and deploys ok.", - "dev: a quick development marker to run selected tests", - "standalone: integration tests for standalone charm function", - "backend: integration tests for backend relation to postgresql", - "scaling: integration tests for pgbouncer scaling", - "legacy_relation: integration tests that test legacy client relations", - "client_relation: integration tests that test modern client relations", - "tls_tests: integration tests that test TLS", -] +markers = ["unstable"] # Formatting tools configuration [tool.black] diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py new file mode 100644 index 000000000..db3bfe1a6 --- /dev/null +++ b/tests/integration/__init__.py @@ -0,0 +1,2 @@ +# Copyright 2023 Canonical Ltd. +# See LICENSE file for licensing details. diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py new file mode 100644 index 000000000..da132958d --- /dev/null +++ b/tests/integration/conftest.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +# Copyright 2022 Canonical Ltd. +# See LICENSE file for licensing details. + +import json +import os +from pathlib import Path + +import pytest +from pytest_operator.plugin import OpsTest + + +@pytest.fixture(scope="module") +def ops_test(ops_test: OpsTest) -> OpsTest: + if os.environ.get("CI") == "true": + # Running in GitHub Actions; skip build step + # (GitHub Actions uses a separate, cached build step. See .github/workflows/ci.yaml) + packed_charms = json.loads(os.environ["CI_PACKED_CHARMS"]) + + async def build_charm(charm_path, bases_index: int = None) -> Path: + for charm in packed_charms: + if Path(charm_path) == Path(charm["directory_path"]): + if bases_index is None or bases_index == charm["bases_index"]: + return charm["file_path"] + raise ValueError(f"Unable to find .charm file for {bases_index=} at {charm_path=}") + + ops_test.build_charm = build_charm + return ops_test diff --git a/tests/integration/helpers/__init__.py b/tests/integration/helpers/__init__.py new file mode 100644 index 000000000..db3bfe1a6 --- /dev/null +++ b/tests/integration/helpers/__init__.py @@ -0,0 +1,2 @@ +# Copyright 2023 Canonical Ltd. +# See LICENSE file for licensing details. diff --git a/tests/integration/relations/__init__.py b/tests/integration/relations/__init__.py new file mode 100644 index 000000000..db3bfe1a6 --- /dev/null +++ b/tests/integration/relations/__init__.py @@ -0,0 +1,2 @@ +# Copyright 2023 Canonical Ltd. +# See LICENSE file for licensing details. diff --git a/tests/integration/relations/pgbouncer_provider/__init__.py b/tests/integration/relations/pgbouncer_provider/__init__.py new file mode 100644 index 000000000..db3bfe1a6 --- /dev/null +++ b/tests/integration/relations/pgbouncer_provider/__init__.py @@ -0,0 +1,2 @@ +# Copyright 2023 Canonical Ltd. +# See LICENSE file for licensing details. diff --git a/tests/integration/relations/pgbouncer_provider/test_pgbouncer_provider.py b/tests/integration/relations/pgbouncer_provider/test_pgbouncer_provider.py index 7b9df4956..92684fb0e 100644 --- a/tests/integration/relations/pgbouncer_provider/test_pgbouncer_provider.py +++ b/tests/integration/relations/pgbouncer_provider/test_pgbouncer_provider.py @@ -11,7 +11,8 @@ from pytest_operator.plugin import OpsTest from constants import BACKEND_RELATION_NAME -from tests.integration.helpers.helpers import ( + +from ...helpers.helpers import ( get_app_relation_databag, get_backend_relation, get_backend_user_pass, @@ -20,11 +21,11 @@ scale_application, wait_for_relation_joined_between, ) -from tests.integration.helpers.postgresql_helpers import ( +from ...helpers.postgresql_helpers import ( check_database_creation, check_database_users_existence, ) -from tests.integration.relations.pgbouncer_provider.helpers import ( +from .helpers import ( build_connection_string, check_new_relation, get_application_relation_data, @@ -51,9 +52,7 @@ SECONDARY_APPLICATION_SECOND_DBNAME = "secondary_application_second_database" -@pytest.mark.smoke @pytest.mark.abort_on_fail -@pytest.mark.client_relation async def test_database_relation_with_charm_libraries( ops_test: OpsTest, application_charm, pgb_charm ): @@ -103,7 +102,6 @@ async def test_database_relation_with_charm_libraries( ) -@pytest.mark.client_relation async def test_database_usage(ops_test: OpsTest): """Check we can update and delete things.""" update_query = ( @@ -123,7 +121,6 @@ async def test_database_usage(ops_test: OpsTest): assert "some data" in json.loads(run_update_query["results"])[0] -@pytest.mark.client_relation async def test_database_version(ops_test: OpsTest): """Check version is accurate.""" version_query = "SELECT version();" @@ -143,7 +140,6 @@ async def test_database_version(ops_test: OpsTest): assert version in json.loads(run_version_query["results"])[0][0] -@pytest.mark.client_relation async def test_readonly_reads(ops_test: OpsTest): """Check we can read things in readonly.""" select_query = "SELECT data FROM test;" @@ -159,7 +155,6 @@ async def test_readonly_reads(ops_test: OpsTest): assert "some data" in json.loads(run_select_query_readonly["results"])[0] -@pytest.mark.client_relation async def test_cant_write_in_readonly(ops_test: OpsTest): """Check we can't write in readonly.""" drop_query = "DROP TABLE test;" @@ -175,7 +170,6 @@ async def test_cant_write_in_readonly(ops_test: OpsTest): assert run_drop_query_readonly["Code"] == "1" -@pytest.mark.client_relation async def test_database_admin_permissions(ops_test: OpsTest): """Test admin permissions.""" create_database_query = "CREATE DATABASE another_database;" @@ -201,7 +195,6 @@ async def test_database_admin_permissions(ops_test: OpsTest): assert "no results to fetch" in json.loads(run_create_user_query["results"]) -@pytest.mark.client_relation async def test_no_read_only_endpoint_in_standalone_cluster(ops_test: OpsTest): """Test that there is no read-only endpoint in a standalone cluster.""" unit = ops_test.model.applications[CLIENT_APP_NAME].units[0] @@ -225,7 +218,6 @@ async def test_no_read_only_endpoint_in_standalone_cluster(ops_test: OpsTest): ), f"read-only-endpoints in pgb databag: {databag}" -@pytest.mark.client_relation async def test_read_only_endpoint_in_scaled_up_cluster(ops_test: OpsTest): """Test that there is read-only endpoint in a scaled up cluster.""" await scale_application(ops_test, PGB, 2) @@ -244,7 +236,6 @@ async def test_read_only_endpoint_in_scaled_up_cluster(ops_test: OpsTest): assert read_only_endpoints, f"read-only-endpoints not in pgb databag: {databag}" -@pytest.mark.client_relation async def test_each_relation_has_unique_credentials(ops_test: OpsTest, application_charm): """Test that two different applications connect to the database with different credentials.""" all_app_names = [SECONDARY_CLIENT_APP_NAME] + APP_NAMES @@ -294,7 +285,6 @@ async def test_each_relation_has_unique_credentials(ops_test: OpsTest, applicati assert app_connstr != secondary_app_connstr -@pytest.mark.client_relation async def test_an_application_can_request_multiple_databases(ops_test: OpsTest): """Test that an application can request additional databases using the same interface.""" # Relate the charms using another relation and wait for them exchanging some connection data. @@ -314,8 +304,6 @@ async def test_an_application_can_request_multiple_databases(ops_test: OpsTest): assert first_database_connection_string != second_database_connection_string -@pytest.mark.smoke -@pytest.mark.client_relation async def test_legacy_relation_compatibility(ops_test: OpsTest): finos = "finos-waltz-k8s" await ops_test.model.deploy(finos, application_name=finos, channel="edge"), @@ -342,7 +330,6 @@ async def test_legacy_relation_compatibility(ops_test: OpsTest): ) -@pytest.mark.client_relation async def test_multiple_pgb_can_connect_to_one_backend(ops_test: OpsTest, pgb_charm): pgb_secondary = f"{PGB}-secondary" await ops_test.model.deploy( @@ -384,8 +371,6 @@ async def test_multiple_pgb_can_connect_to_one_backend(ops_test: OpsTest, pgb_ch ) -@pytest.mark.smoke -@pytest.mark.client_relation async def test_scaling(ops_test: OpsTest): """Check these relations all work when scaling pgbouncer.""" await scale_application(ops_test, PGB, 1) @@ -409,7 +394,6 @@ async def test_scaling(ops_test: OpsTest): ) -@pytest.mark.client_relation async def test_relation_broken(ops_test: OpsTest): """Test that the user is removed when the relation is broken.""" async with ops_test.fast_forward(): @@ -438,7 +422,6 @@ async def test_relation_broken(ops_test: OpsTest): assert "first-database_readonly" not in cfg["databases"].keys() -@pytest.mark.client_relation async def test_relation_with_data_integrator(ops_test: OpsTest): """Test that the charm can be related to the data integrator without extra user roles.""" config = {"database-name": "test-database"} diff --git a/tests/integration/relations/test_backend_database.py b/tests/integration/relations/test_backend_database.py index 6728f0921..53bbfcb8d 100644 --- a/tests/integration/relations/test_backend_database.py +++ b/tests/integration/relations/test_backend_database.py @@ -10,7 +10,7 @@ from pytest_operator.plugin import OpsTest from tenacity import RetryError, Retrying, stop_after_delay, wait_fixed -from tests.integration.helpers.helpers import ( +from ..helpers.helpers import ( get_app_relation_databag, get_backend_relation, get_backend_user_pass, @@ -20,7 +20,7 @@ wait_for_relation_joined_between, wait_for_relation_removed_between, ) -from tests.integration.helpers.postgresql_helpers import ( +from ..helpers.postgresql_helpers import ( check_database_users_existence, enable_connections_logging, get_postgres_primary, @@ -37,7 +37,6 @@ RELATION = "backend-database" -@pytest.mark.backend @pytest.mark.abort_on_fail async def test_relate_pgbouncer_to_postgres(ops_test: OpsTest): """Test that the pgbouncer and postgres charms can relate to one another.""" @@ -107,7 +106,6 @@ async def test_relate_pgbouncer_to_postgres(ops_test: OpsTest): logging.info(cfg.render()) -@pytest.mark.backend async def test_tls_encrypted_connection_to_postgres(ops_test: OpsTest): async with ops_test.fast_forward(): # Relate PgBouncer to PostgreSQL. @@ -150,7 +148,6 @@ async def test_tls_encrypted_connection_to_postgres(ops_test: OpsTest): ), "TLS is not being used on connections to PostgreSQL" -@pytest.mark.backend async def test_pgbouncer_stable_when_deleting_postgres(ops_test: OpsTest): async with ops_test.fast_forward(): await scale_application(ops_test, PGB, 3) diff --git a/tests/integration/relations/test_db.py b/tests/integration/relations/test_db.py index b34705147..ffd2a5c4c 100644 --- a/tests/integration/relations/test_db.py +++ b/tests/integration/relations/test_db.py @@ -5,11 +5,10 @@ import logging from pathlib import Path -import pytest import yaml from pytest_operator.plugin import OpsTest -from tests.integration.helpers.helpers import ( +from ..helpers.helpers import ( get_app_relation_databag, get_backend_user_pass, get_cfg, @@ -17,7 +16,7 @@ wait_for_relation_joined_between, wait_for_relation_removed_between, ) -from tests.integration.helpers.postgresql_helpers import ( +from ..helpers.postgresql_helpers import ( check_database_creation, check_database_users_existence, ) @@ -32,7 +31,6 @@ logger = logging.getLogger(__name__) -@pytest.mark.legacy_relation async def test_create_db_legacy_relation(ops_test: OpsTest): """Test that the pgbouncer and postgres charms can relate to one another.""" # Build, deploy, and relate charms. @@ -165,7 +163,6 @@ async def test_create_db_legacy_relation(ops_test: OpsTest): assert "waltz_standby" not in cfg["databases"].keys() -@pytest.mark.legacy_relation async def test_relation_with_openldap(ops_test: OpsTest): """Test the relation with OpenLDAP charm.""" await ops_test.model.deploy( diff --git a/tests/integration/relations/test_db_admin.py b/tests/integration/relations/test_db_admin.py index 7b8f8f470..034747008 100644 --- a/tests/integration/relations/test_db_admin.py +++ b/tests/integration/relations/test_db_admin.py @@ -5,16 +5,15 @@ import logging from pathlib import Path -import pytest import yaml from pytest_operator.plugin import OpsTest -from tests.integration.helpers.helpers import ( +from ..helpers.helpers import ( get_backend_user_pass, get_legacy_relation_username, wait_for_relation_joined_between, ) -from tests.integration.helpers.postgresql_helpers import ( +from ..helpers.postgresql_helpers import ( check_database_creation, check_database_users_existence, get_unit_address, @@ -30,7 +29,6 @@ PG = "postgresql-k8s" -@pytest.mark.legacy_relation async def test_create_db_admin_legacy_relation(ops_test: OpsTest): # Build, deploy, and relate charms. charm = await ops_test.build_charm(".") diff --git a/tests/integration/relations/test_peers.py b/tests/integration/relations/test_peers.py index ee9c6f4af..7f3a29006 100644 --- a/tests/integration/relations/test_peers.py +++ b/tests/integration/relations/test_peers.py @@ -9,7 +9,7 @@ import yaml from pytest_operator.plugin import OpsTest -from tests.integration.helpers.helpers import ( +from ..helpers.helpers import ( scale_application, wait_for_relation_joined_between, wait_for_relation_removed_between, @@ -24,7 +24,6 @@ FINOS_WALTZ = "finos-waltz" -@pytest.mark.scaling @pytest.mark.abort_on_fail # TODO order marks aren't behaving async def test_deploy_at_scale(ops_test): @@ -40,7 +39,6 @@ async def test_deploy_at_scale(ops_test): ), -@pytest.mark.scaling @pytest.mark.abort_on_fail async def test_scaled_relations(ops_test: OpsTest): """Test that the pgbouncer and postgres charms can relate to one another.""" @@ -80,7 +78,6 @@ async def test_scaled_relations(ops_test: OpsTest): ) -@pytest.mark.scaling async def test_scaling(ops_test: OpsTest): """Test data is replicated to new units after a scale up.""" # Ensure the initial number of units in the application. @@ -104,7 +101,6 @@ async def test_scaling(ops_test: OpsTest): ) -@pytest.mark.scaling async def test_exit_relations(ops_test: OpsTest): """Test that we can exit relations with multiple units without breaking anything.""" async with ops_test.fast_forward(): diff --git a/tests/integration/test_charm.py b/tests/integration/test_charm.py index 59ac6e8ed..715efec42 100644 --- a/tests/integration/test_charm.py +++ b/tests/integration/test_charm.py @@ -9,7 +9,7 @@ import yaml from pytest_operator.plugin import OpsTest -from tests.integration.helpers.helpers import get_cfg, run_command_on_unit +from .helpers.helpers import get_cfg, run_command_on_unit logger = logging.getLogger(__name__) @@ -17,7 +17,6 @@ PGB = METADATA["name"] -@pytest.mark.standalone @pytest.mark.abort_on_fail async def test_build_and_deploy(ops_test: OpsTest): """Build and deploy pgbouncer charm.""" @@ -34,7 +33,6 @@ async def test_build_and_deploy(ops_test: OpsTest): await ops_test.model.wait_for_idle(apps=[PGB], status="blocked", timeout=1000) -@pytest.mark.standalone async def test_config_updates(ops_test: OpsTest): """Test updating charm config updates pgbouncer config & relation data.""" pgbouncer_app = ops_test.model.applications[PGB] @@ -49,7 +47,6 @@ async def test_config_updates(ops_test: OpsTest): assert cfg["pgbouncer"]["listen_port"] == port -@pytest.mark.standalone async def test_multiple_pebble_services(ops_test: OpsTest): """Test we have the correct pebble services.""" unit = ops_test.model.applications[PGB].units[0] diff --git a/tests/integration/test_tls.py b/tests/integration/test_tls.py index 3ba024d33..13ea44f58 100644 --- a/tests/integration/test_tls.py +++ b/tests/integration/test_tls.py @@ -4,7 +4,7 @@ import pytest as pytest from pytest_operator.plugin import OpsTest -from tests.integration.helpers.helpers import ( +from .helpers.helpers import ( CLIENT_APP_NAME, PGB, PGB_METADATA, @@ -22,7 +22,6 @@ @pytest.mark.abort_on_fail -@pytest.mark.tls_tests async def test_build_and_deploy(ops_test: OpsTest): """Build and deploy pgbouncer charm.""" wait_for_apps = False @@ -90,7 +89,6 @@ async def test_build_and_deploy(ops_test: OpsTest): await ops_test.model.wait_for_idle(status="active", timeout=1000) -@pytest.mark.tls_tests async def test_scale_up_pgb(ops_test: OpsTest) -> None: """Scale up PGB while TLS is enabled. @@ -106,7 +104,6 @@ async def test_scale_up_pgb(ops_test: OpsTest) -> None: assert len(ops_test.model.applications[pgb_app].units) == num_units + 1 -@pytest.mark.tls_tests async def test_scale_down_pgb(ops_test: OpsTest) -> None: """Scale down PGB while TLS is enabled. @@ -122,7 +119,6 @@ async def test_scale_down_pgb(ops_test: OpsTest) -> None: assert len(ops_test.model.applications[pgb_app].units) == num_units - 1 -@pytest.mark.tls_tests async def test_remove_tls(ops_test: OpsTest) -> None: """Removes the TLS relation and check through the test app the it is off. @@ -136,7 +132,6 @@ async def test_remove_tls(ops_test: OpsTest) -> None: assert await check_tls(ops_test, client_relation.id, False) -@pytest.mark.tls_tests async def test_add_tls(ops_test: OpsTest) -> None: """Rejoins the TLS relation and check through the test app the it is on. @@ -148,7 +143,6 @@ async def test_add_tls(ops_test: OpsTest) -> None: assert await check_tls(ops_test, client_relation.id, True) -@pytest.mark.tls_tests async def test_mattermost_db(ops_test: OpsTest) -> None: """Deploy Mattermost to test the 'db' relation. diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py new file mode 100644 index 000000000..db3bfe1a6 --- /dev/null +++ b/tests/unit/__init__.py @@ -0,0 +1,2 @@ +# Copyright 2023 Canonical Ltd. +# See LICENSE file for licensing details. diff --git a/tests/unit/relations/__init__.py b/tests/unit/relations/__init__.py new file mode 100644 index 000000000..db3bfe1a6 --- /dev/null +++ b/tests/unit/relations/__init__.py @@ -0,0 +1,2 @@ +# Copyright 2023 Canonical Ltd. +# See LICENSE file for licensing details. diff --git a/tests/unit/relations/test_db.py b/tests/unit/relations/test_db.py index 76595781f..9bbea73cb 100644 --- a/tests/unit/relations/test_db.py +++ b/tests/unit/relations/test_db.py @@ -4,6 +4,11 @@ import unittest from unittest.mock import MagicMock, PropertyMock, patch +from charms.pgbouncer_k8s.v0.pgb import ( + DEFAULT_CONFIG, + PgbConfig, + parse_dict_to_kv_string, +) from ops.testing import Harness from charm import PgBouncerK8sCharm @@ -13,11 +18,6 @@ DB_RELATION_NAME, PEER_RELATION_NAME, ) -from lib.charms.pgbouncer_k8s.v0.pgb import ( - DEFAULT_CONFIG, - PgbConfig, - parse_dict_to_kv_string, -) TEST_UNIT = { "master": "host=master port=1 dbname=testdatabase", @@ -220,17 +220,16 @@ def test_update_connection_info(self, _update_databags, _get_external_app, _get_ ) @patch("relations.db.DbProvides._check_backend", return_value=True) - @patch("relations.db.DbProvides.get_databags", return_value=[{}]) + @patch("relations.db.DbProvides.get_databags") @patch( "relations.backend_database.BackendDatabaseRequires.postgres_databag", new_callable=PropertyMock, - return_value={}, ) @patch( "relations.backend_database.BackendDatabaseRequires.get_read_only_endpoints", return_value=[], ) - @patch("charm.PgBouncerK8sCharm.read_pgb_config", return_value=PgbConfig(DEFAULT_CONFIG)) + @patch("charm.PgBouncerK8sCharm.read_pgb_config") @patch("charm.PgBouncerK8sCharm.render_pgb_config") def test_update_postgres_endpoints( self, @@ -242,9 +241,10 @@ def test_update_postgres_endpoints( _check_backend, ): database = "test_db" - _get_databags.return_value[0] = {"database": database} + _get_databags.return_value = [{"database": database}] _pg_databag.return_value = {"endpoints": "ip:port"} - cfg = _read_cfg.return_value + cfg = PgbConfig(DEFAULT_CONFIG) + _read_cfg.side_effect = [cfg, PgbConfig(DEFAULT_CONFIG)] relation = MagicMock() reload_pgbouncer = False @@ -252,8 +252,11 @@ def test_update_postgres_endpoints( self.db_relation.update_postgres_endpoints(relation, reload_pgbouncer=reload_pgbouncer) assert database in cfg["databases"].keys() assert f"{database}_standby" not in cfg["databases"].keys() + _render_cfg.assert_called_with(cfg, reload_pgbouncer=reload_pgbouncer) _read_only_endpoint.return_value = ["readonly:endpoint"] + _read_cfg.side_effect = [cfg, PgbConfig(DEFAULT_CONFIG)] + self.db_relation.update_postgres_endpoints(relation, reload_pgbouncer=reload_pgbouncer) assert database in cfg["databases"].keys() assert f"{database}_standby" in cfg["databases"].keys() diff --git a/tests/unit/relations/test_peers.py b/tests/unit/relations/test_peers.py index 2cf1ca4b9..1b41e1052 100644 --- a/tests/unit/relations/test_peers.py +++ b/tests/unit/relations/test_peers.py @@ -4,11 +4,11 @@ import unittest from unittest.mock import MagicMock, PropertyMock, patch +from charms.pgbouncer_k8s.v0.pgb import DEFAULT_CONFIG, PgbConfig from ops.testing import Harness from charm import PgBouncerK8sCharm from constants import BACKEND_RELATION_NAME -from lib.charms.pgbouncer_k8s.v0.pgb import DEFAULT_CONFIG, PgbConfig from relations.peers import AUTH_FILE_DATABAG_KEY, CFG_FILE_DATABAG_KEY diff --git a/tests/unit/relations/test_pgbouncer_provider.py b/tests/unit/relations/test_pgbouncer_provider.py index f19d5845f..a78b70ec5 100644 --- a/tests/unit/relations/test_pgbouncer_provider.py +++ b/tests/unit/relations/test_pgbouncer_provider.py @@ -4,11 +4,11 @@ import unittest from unittest.mock import MagicMock, PropertyMock, patch +from charms.pgbouncer_k8s.v0.pgb import DEFAULT_CONFIG, PgbConfig from ops.testing import Harness from charm import PgBouncerK8sCharm from constants import BACKEND_RELATION_NAME, CLIENT_RELATION_NAME, PEER_RELATION_NAME -from lib.charms.pgbouncer_k8s.v0.pgb import DEFAULT_CONFIG, PgbConfig class TestPgbouncerProvider(unittest.TestCase): diff --git a/tests/unit/test_pgb.py b/tests/unit/test_pgb.py index 5f66fc958..b444aeac7 100644 --- a/tests/unit/test_pgb.py +++ b/tests/unit/test_pgb.py @@ -5,9 +5,8 @@ import unittest import pytest - -from lib.charms.pgbouncer_k8s.v0 import pgb -from lib.charms.pgbouncer_k8s.v0.pgb import DEFAULT_CONFIG, PgbConfig +from charms.pgbouncer_k8s.v0 import pgb +from charms.pgbouncer_k8s.v0.pgb import DEFAULT_CONFIG, PgbConfig DATA_DIR = "tests/unit/data" TEST_VALID_INI = f"{DATA_DIR}/test.ini" diff --git a/tox.ini b/tox.ini index 78b0a5232..0f1ad9f82 100644 --- a/tox.ini +++ b/tox.ini @@ -2,27 +2,27 @@ # See LICENSE file for licensing details. [tox] -skipsdist=True +no_package = True skip_missing_interpreters = True -envlist = fmt, lint, unit +env_list = lint, unit [vars] -src_path = {toxinidir}/src/ -tst_path = {toxinidir}/tests/ -lib_path = {toxinidir}/lib/charms/pgbouncer_k8s -all_path = {[vars]src_path} {[vars]tst_path} {[vars]lib_path} +src_path = {tox_root}/src +tests_path = {tox_root}/tests +lib_path = {tox_root}/lib/charms/pgbouncer_k8s +all_path = {[vars]src_path} {[vars]tests_path} {[vars]lib_path} [testenv] -setenv = - PYTHONPATH = {toxinidir}:{toxinidir}/lib:{[vars]src_path} - PYTHONBREAKPOINT=ipdb.set_trace - PY_COLORS=1 -passenv = - PYTHONPATH - CHARM_BUILD_DIR - MODEL_SETTINGS +set_env = + PYTHONPATH = {tox_root}/lib:{[vars]src_path} + PYTHONBREAKPOINT=ipdb.set_trace + PY_COLORS=1 +pass_env = + PYTHONPATH + CHARM_BUILD_DIR + MODEL_SETTINGS -[testenv:fmt] +[testenv:format] description = Apply coding style standards to code deps = black @@ -44,9 +44,9 @@ deps = isort codespell commands = - codespell {toxinidir} --skip {toxinidir}/.git --skip {toxinidir}/.tox \ - --skip {toxinidir}/build --skip {toxinidir}/lib --skip {toxinidir}/venv \ - --skip {toxinidir}/.mypy_cache --skip {toxinidir}/LICENSE + codespell {tox_root} --skip {tox_root}/.git --skip {tox_root}/.tox \ + --skip {tox_root}/build --skip {tox_root}/lib --skip {tox_root}/venv \ + --skip {tox_root}/.mypy_cache --skip {tox_root}/LICENSE # pflake8 wrapper supports config from pyproject.toml pflake8 {[vars]all_path} isort --check-only --diff {[vars]all_path} @@ -59,53 +59,34 @@ deps = pytest-asyncio psycopg2-binary coverage[toml] - -r{toxinidir}/requirements.txt + -r {tox_root}/requirements.txt commands = coverage run --source={[vars]src_path},{[vars]lib_path} \ - -m pytest --ignore={[vars]tst_path}integration -v --tb native -s {posargs} + -m pytest -v --tb native -s {posargs} {[vars]tests_path}/unit coverage report -[testenv:dev-integration] -description = Run integration tests marked dev -deps = - pytest - juju~=2.9.0 # Latest juju 2 - lightkube==0.10.0 - psycopg2-binary - pytest-operator>0.17.0 - -r{toxinidir}/requirements.txt -commands = - pytest -v --tb native --ignore={[vars]tst_path}unit --log-cli-level=INFO -s {posargs} \ - --durations=0 --asyncio-mode=auto -m dev - -[testenv:smoke-integration] -description = Run smoke integration tests to verify basic charm function works -deps = - pytest - juju~=2.9.0 # Latest juju 2 - lightkube==0.10.0 - psycopg2-binary - pytest-operator>0.17.0 - -r{toxinidir}/requirements.txt -commands = - pytest -v --tb native --ignore={[vars]tst_path}unit --log-cli-level=INFO -s {posargs} \ - --durations=0 --asyncio-mode=auto -m smoke - [testenv:standalone-integration] description = Run integration tests for standalone charm function +pass_env = + {[testenv]pass_env} + CI + CI_PACKED_CHARMS deps = pytest juju~=2.9.0 # Latest juju 2 lightkube==0.10.0 psycopg2-binary pytest-operator>0.17.0 - -r{toxinidir}/requirements.txt + -r {tox_root}/requirements.txt commands = - pytest -v --tb native --ignore={[vars]tst_path}unit --log-cli-level=INFO -s {posargs} \ - --durations=0 --asyncio-mode=auto -m standalone + pytest -v --tb native --log-cli-level=INFO -s --durations=0 {posargs} {[vars]tests_path}/integration/test_charm.py [testenv:backend-integration] description = Run integration tests for backend relation +pass_env = + {[testenv]pass_env} + CI + CI_PACKED_CHARMS deps = pytest juju~=2.9.0 # Latest juju 2 @@ -113,13 +94,16 @@ deps = psycopg2-binary pytest-operator>0.17.0 requests - -r{toxinidir}/requirements.txt + -r {tox_root}/requirements.txt commands = - pytest -v --tb native --ignore={[vars]tst_path}unit --log-cli-level=INFO -s {posargs} \ - --durations=0 --asyncio-mode=auto -m backend + pytest -v --tb native --log-cli-level=INFO -s --durations=0 {posargs} {[vars]tests_path}/integration/relations/test_backend_database.py [testenv:client-relation-integration] description = Run integration tests for modern client relations +pass_env = + {[testenv]pass_env} + CI + CI_PACKED_CHARMS deps = pytest juju~=2.9.0 # Latest juju 2 @@ -127,13 +111,33 @@ deps = psycopg2-binary pytest-operator requests - -r{toxinidir}/requirements.txt + -r {tox_root}/requirements.txt commands = - pytest -v --tb native --ignore={[vars]tst_path}unit --log-cli-level=INFO -s {posargs} \ - --durations=0 --asyncio-mode=auto -m client_relation + pytest -v --tb native --log-cli-level=INFO -s --durations=0 {posargs} {[vars]tests_path}/integration/relations/pgbouncer_provider/test_pgbouncer_provider.py [testenv:legacy-client-relation-integration] description = Run integration tests for legacy client relations +pass_env = + {[testenv]pass_env} + CI + CI_PACKED_CHARMS +deps = + pytest + juju~=2.9.0 # Latest juju 2 + lightkube==0.10.0 + psycopg2-binary + pytest-operator + requests + -r {tox_root}/requirements.txt +commands = + pytest -v --tb native --log-cli-level=INFO -s --durations=0 {posargs} {[vars]tests_path}/integration/relations/test_db.py + +[testenv:legacy-client-relation-integration-admin] +description = Run integration tests for legacy client admin relations +pass_env = + {[testenv]pass_env} + CI + CI_PACKED_CHARMS deps = pytest juju~=2.9.0 # Latest juju 2 @@ -141,33 +145,38 @@ deps = psycopg2-binary pytest-operator requests - -r{toxinidir}/requirements.txt + -r {tox_root}/requirements.txt commands = - pytest -v --tb native --ignore={[vars]tst_path}unit --log-cli-level=INFO -s {posargs} \ - --durations=0 --asyncio-mode=auto -m legacy_relation + pytest -v --tb native --log-cli-level=INFO -s --durations=0 {posargs} {[vars]tests_path}/integration/relations/test_db_admin.py [testenv:scaling-integration] description = Run integration tests for scaling +pass_env = + {[testenv]pass_env} + CI + CI_PACKED_CHARMS deps = pytest juju~=2.9.0 # Latest juju 2 lightkube==0.10.0 psycopg2-binary pytest-operator - -r{toxinidir}/requirements.txt + -r {tox_root}/requirements.txt commands = - pytest -v --tb native --ignore={[vars]tst_path}unit --log-cli-level=INFO -s {posargs} \ - --durations=0 --asyncio-mode=auto -m scaling + pytest -v --tb native --log-cli-level=INFO -s --durations=0 {posargs} {[vars]tests_path}/integration/relations/test_peers.py [testenv:tls-integration] description = Run TLS integration tests +pass_env = + {[testenv]pass_env} + CI + CI_PACKED_CHARMS deps = pytest juju~=2.9.0 # Latest juju 2 lightkube==0.10.0 psycopg2-binary pytest-operator - -r{toxinidir}/requirements.txt + -r {tox_root}/requirements.txt commands = - pytest -v --tb native --ignore={[vars]tst_path}unit --log-cli-level=INFO -s {posargs} \ - --durations=0 --asyncio-mode=auto -m tls_tests + pytest -v --tb native --log-cli-level=INFO -s --durations=0 {posargs} {[vars]tests_path}/integration/test_tls.py