From a67d66c217e781083ab2ef10ee0addeb0992272c Mon Sep 17 00:00:00 2001 From: JP-Ellis Date: Fri, 22 Mar 2024 11:05:19 +1100 Subject: [PATCH] chore(tests): use long-lived pact broker The initial implementation of the compatibility suite spun up and down the Pact Broker for each scenario, which also resulting in flaky tests in CI. This refactor uses a session pytest fixture which will spin up the broker once, and keep re-using it. Functionality to 'reset' the broker between tests has also been added. Signed-off-by: JP-Ellis --- .../compatibility_suite/test_v1_provider.py | 66 ++++++++++++++++--- tests/v3/compatibility_suite/util/provider.py | 19 ++++++ 2 files changed, 75 insertions(+), 10 deletions(-) diff --git a/tests/v3/compatibility_suite/test_v1_provider.py b/tests/v3/compatibility_suite/test_v1_provider.py index 9b216962d4..c48beca5da 100644 --- a/tests/v3/compatibility_suite/test_v1_provider.py +++ b/tests/v3/compatibility_suite/test_v1_provider.py @@ -9,13 +9,15 @@ import logging import pickle import re +import shutil import signal import subprocess import sys import time +from contextvars import ContextVar from pathlib import Path from threading import Thread -from typing import Any, Generator, NoReturn +from typing import Any, Generator, NoReturn, Union import pytest import requests @@ -34,6 +36,16 @@ logger = logging.getLogger(__name__) +reset_broker_var = ContextVar("reset_broker", default=True) +""" +This context variable is used to determine whether the Pact broker should be +cleaned up. It is used to ensure that the broker is only cleaned up once, even +if a step is run multiple times. + +All scenarios which make use of the Pact broker should set this to `True` at the +start of the scenario. +""" + @pytest.fixture() def verifier() -> Verifier: @@ -41,6 +53,35 @@ def verifier() -> Verifier: return Verifier() +@pytest.fixture(scope="session") +def broker_url(request: pytest.FixtureRequest) -> Generator[URL, Any, None]: + """ + Fixture to run the Pact broker. + + This inspects whether the `--broker-url` option has been given. If it has, + it is assumed that the broker is already running and simply returns the + given URL. + + Otherwise, the Pact broker is started in a container. The URL of the + containerised broker is then returned. + """ + broker_url: Union[str, None] = request.config.getoption("--broker-url") + + # If we have been given a broker URL, there's nothing more to do here and we + # can return early. + if broker_url: + yield URL(broker_url) + return + + with DockerCompose( + Path(__file__).parent / "util", + compose_file_name="pact-broker.yml", + pull=True, + ) as _: + yield URL("http://pactbroker:pactbroker@localhost:9292") + return + + ################################################################################ ## Scenario ################################################################################ @@ -76,6 +117,7 @@ def test_incorrect_request_is_made_to_provider() -> None: ) def test_verifying_a_simple_http_request_via_a_pact_broker() -> None: """Verifying a simple HTTP request via a Pact broker.""" + reset_broker_var.set(True) # noqa: FBT003 @scenario( @@ -84,6 +126,7 @@ def test_verifying_a_simple_http_request_via_a_pact_broker() -> None: ) def test_verifying_a_simple_http_request_via_a_pact_broker_with_publishing() -> None: """Verifying a simple HTTP request via a Pact broker with publishing.""" + reset_broker_var.set(True) # noqa: FBT003 @scenario( @@ -92,6 +135,7 @@ def test_verifying_a_simple_http_request_via_a_pact_broker_with_publishing() -> ) def test_verifying_multiple_pact_files_via_a_pact_broker() -> None: """Verifying multiple Pact files via a Pact broker.""" + reset_broker_var.set(True) # noqa: FBT003 @scenario( @@ -100,6 +144,7 @@ def test_verifying_multiple_pact_files_via_a_pact_broker() -> None: ) def test_incorrect_request_is_made_to_provider_via_a_pact_broker() -> None: """Incorrect request is made to provider via a Pact broker.""" + reset_broker_var.set(True) # noqa: FBT003 @scenario( @@ -475,6 +520,7 @@ def a_pact_file_for_interaction_is_to_be_verified( ) def a_pact_file_for_interaction_is_to_be_verified_from_a_pact_broker( interaction_definitions: dict[int, InteractionDefinition], + broker_url: URL, verifier: Verifier, interaction: int, temp_dir: Path, @@ -491,18 +537,18 @@ def a_pact_file_for_interaction_is_to_be_verified_from_a_pact_broker( defn.add_to_pact(pact, f"interaction {interaction}") pacts_dir = temp_dir / "pacts" + if pacts_dir.exists(): + shutil.rmtree(pacts_dir) pacts_dir.mkdir(exist_ok=True, parents=True) pact.write_file(pacts_dir) - with DockerCompose( - Path(__file__).parent / "util", - compose_file_name="pact-broker.yml", - pull=True, - ) as _: - pact_broker = PactBroker(URL("http://pactbroker:pactbroker@localhost:9292")) - pact_broker.publish(pacts_dir) - verifier.broker_source(pact_broker.url) - yield pact_broker + pact_broker = PactBroker(broker_url) + if reset_broker_var.get(): + pact_broker.reset() + reset_broker_var.set(False) # noqa: FBT003 + pact_broker.publish(pacts_dir) + verifier.broker_source(pact_broker.url) + yield pact_broker @given("publishing of verification results is enabled") diff --git a/tests/v3/compatibility_suite/util/provider.py b/tests/v3/compatibility_suite/util/provider.py index 0f4d4be627..2968152b0d 100644 --- a/tests/v3/compatibility_suite/util/provider.py +++ b/tests/v3/compatibility_suite/util/provider.py @@ -299,6 +299,25 @@ def _install(self) -> None: msg = "pact-broker not found" raise NotImplementedError(msg) + def reset(self) -> None: + """ + Reset the Pact Broker. + + This function will reset the Pact Broker by deleting all pacts and + verification results. + """ + requests.delete( + str( + self.url + / "integrations" + / "provider" + / self.provider + / "consumer" + / self.consumer + ), + timeout=2, + ) + def publish(self, directory: Path | str, version: str | None = None) -> None: """ Publish the interactions to the Pact Broker.