From 9e2d2b5c9724db16aa7df7029e7f4725143683ce Mon Sep 17 00:00:00 2001 From: Nikhil172913832 Date: Wed, 12 Nov 2025 11:29:04 +0530 Subject: [PATCH] fix: use threading instead of subprocess for Windows compatibility (#639) --- tests/compatibility_suite/test_v1_provider.py | 105 ----------- tests/compatibility_suite/test_v2_provider.py | 18 -- .../test_v3_http_matching.py | 29 --- .../test_v3_message_producer.py | 73 -------- tests/compatibility_suite/test_v3_provider.py | 10 - .../test_v4_message_provider.py | 10 - tests/compatibility_suite/test_v4_provider.py | 10 - tests/test_match.py | 173 +++++++----------- 8 files changed, 70 insertions(+), 358 deletions(-) diff --git a/tests/compatibility_suite/test_v1_provider.py b/tests/compatibility_suite/test_v1_provider.py index 15821e601..050618ecc 100644 --- a/tests/compatibility_suite/test_v1_provider.py +++ b/tests/compatibility_suite/test_v1_provider.py @@ -5,7 +5,6 @@ from __future__ import annotations import logging -import sys import pytest from pytest_bdd import given, parsers, scenario @@ -45,10 +44,6 @@ ################################################################################ -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @scenario( "definition/features/V1/http_provider.feature", "Verifying a simple HTTP request", @@ -57,10 +52,6 @@ def test_verifying_a_simple_http_request() -> None: """Verifying a simple HTTP request.""" -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @scenario( "definition/features/V1/http_provider.feature", "Verifying multiple Pact files", @@ -69,10 +60,6 @@ def test_verifying_multiple_pact_files() -> None: """Verifying multiple Pact files.""" -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @scenario( "definition/features/V1/http_provider.feature", "Incorrect request is made to provider", @@ -81,10 +68,6 @@ def test_incorrect_request_is_made_to_provider() -> None: """Incorrect request is made to provider.""" -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @pytest.mark.container @scenario( "definition/features/V1/http_provider.feature", @@ -94,10 +77,6 @@ def test_verifying_a_simple_http_request_via_a_pact_broker() -> None: """Verifying a simple HTTP request via a Pact broker.""" -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @pytest.mark.container @scenario( "definition/features/V1/http_provider.feature", @@ -107,10 +86,6 @@ def test_verifying_a_simple_http_request_via_a_pact_broker_with_publishing() -> """Verifying a simple HTTP request via a Pact broker with publishing.""" -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @pytest.mark.container @scenario( "definition/features/V1/http_provider.feature", @@ -120,10 +95,6 @@ def test_verifying_multiple_pact_files_via_a_pact_broker() -> None: """Verifying multiple Pact files via a Pact broker.""" -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @pytest.mark.container @scenario( "definition/features/V1/http_provider.feature", @@ -133,10 +104,6 @@ def test_incorrect_request_is_made_to_provider_via_a_pact_broker() -> None: """Incorrect request is made to provider via a Pact broker.""" -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @scenario( "definition/features/V1/http_provider.feature", "Verifying an interaction with a defined provider state", @@ -145,10 +112,6 @@ def test_verifying_an_interaction_with_a_defined_provider_state() -> None: """Verifying an interaction with a defined provider state.""" -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @scenario( "definition/features/V1/http_provider.feature", "Verifying an interaction with no defined provider state", @@ -157,10 +120,6 @@ def test_verifying_an_interaction_with_no_defined_provider_state() -> None: """Verifying an interaction with no defined provider state.""" -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @scenario( "definition/features/V1/http_provider.feature", "Verifying an interaction where the provider state callback fails", @@ -169,10 +128,6 @@ def test_verifying_an_interaction_where_the_provider_state_callback_fails() -> N """Verifying an interaction where the provider state callback fails.""" -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @scenario( "definition/features/V1/http_provider.feature", "Verifying an interaction where a provider state callback is not configured", @@ -181,10 +136,6 @@ def test_verifying_an_interaction_where_no_provider_state_callback_configured() """Verifying an interaction where a provider state callback is not configured.""" -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @scenario( "definition/features/V1/http_provider.feature", "Verifying a HTTP request with a request filter configured", @@ -193,10 +144,6 @@ def test_verifying_a_http_request_with_a_request_filter_configured() -> None: """Verifying a HTTP request with a request filter configured.""" -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @scenario( "definition/features/V1/http_provider.feature", "Verifies the response status code", @@ -205,10 +152,6 @@ def test_verifies_the_response_status_code() -> None: """Verifies the response status code.""" -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @scenario( "definition/features/V1/http_provider.feature", "Verifies the response headers", @@ -217,10 +160,6 @@ def test_verifies_the_response_headers() -> None: """Verifies the response headers.""" -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @scenario( "definition/features/V1/http_provider.feature", "Response with plain text body (positive case)", @@ -229,10 +168,6 @@ def test_response_with_plain_text_body_positive_case() -> None: """Response with plain text body (positive case).""" -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @scenario( "definition/features/V1/http_provider.feature", "Response with plain text body (negative case)", @@ -241,10 +176,6 @@ def test_response_with_plain_text_body_negative_case() -> None: """Response with plain text body (negative case).""" -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @scenario( "definition/features/V1/http_provider.feature", "Response with JSON body (positive case)", @@ -253,10 +184,6 @@ def test_response_with_json_body_positive_case() -> None: """Response with JSON body (positive case).""" -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @scenario( "definition/features/V1/http_provider.feature", "Response with JSON body (negative case)", @@ -265,10 +192,6 @@ def test_response_with_json_body_negative_case() -> None: """Response with JSON body (negative case).""" -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @scenario( "definition/features/V1/http_provider.feature", "Response with XML body (positive case)", @@ -277,10 +200,6 @@ def test_response_with_xml_body_positive_case() -> None: """Response with XML body (positive case).""" -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @scenario( "definition/features/V1/http_provider.feature", "Response with XML body (negative case)", @@ -289,10 +208,6 @@ def test_response_with_xml_body_negative_case() -> None: """Response with XML body (negative case).""" -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @scenario( "definition/features/V1/http_provider.feature", "Response with binary body (positive case)", @@ -301,10 +216,6 @@ def test_response_with_binary_body_positive_case() -> None: """Response with binary body (positive case).""" -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @scenario( "definition/features/V1/http_provider.feature", "Response with binary body (negative case)", @@ -313,10 +224,6 @@ def test_response_with_binary_body_negative_case() -> None: """Response with binary body (negative case).""" -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @scenario( "definition/features/V1/http_provider.feature", "Response with form post body (positive case)", @@ -325,10 +232,6 @@ def test_response_with_form_post_body_positive_case() -> None: """Response with form post body (positive case).""" -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @scenario( "definition/features/V1/http_provider.feature", "Response with form post body (negative case)", @@ -337,10 +240,6 @@ def test_response_with_form_post_body_negative_case() -> None: """Response with form post body (negative case).""" -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @scenario( "definition/features/V1/http_provider.feature", "Response with multipart body (positive case)", @@ -349,10 +248,6 @@ def test_response_with_multipart_body_positive_case() -> None: """Response with multipart body (positive case).""" -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @scenario( "definition/features/V1/http_provider.feature", "Response with multipart body (negative case)", diff --git a/tests/compatibility_suite/test_v2_provider.py b/tests/compatibility_suite/test_v2_provider.py index 18d5d7acf..61170e89f 100644 --- a/tests/compatibility_suite/test_v2_provider.py +++ b/tests/compatibility_suite/test_v2_provider.py @@ -5,9 +5,7 @@ from __future__ import annotations import logging -import sys -import pytest from pytest_bdd import given, parsers, scenario from tests.compatibility_suite.util import parse_horizontal_table @@ -30,10 +28,6 @@ ################################################################################ -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @scenario( "definition/features/V2/http_provider.feature", "Supports matching rules for the response headers (positive case)", @@ -44,10 +38,6 @@ def test_supports_matching_rules_for_the_response_headers_positive_case() -> Non """ -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @scenario( "definition/features/V2/http_provider.feature", "Supports matching rules for the response headers (negative case)", @@ -58,10 +48,6 @@ def test_supports_matching_rules_for_the_response_headers_negative_case() -> Non """ -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @scenario( "definition/features/V2/http_provider.feature", "Verifies the response body (positive case)", @@ -72,10 +58,6 @@ def test_verifies_the_response_body_positive_case() -> None: """ -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @scenario( "definition/features/V2/http_provider.feature", "Verifies the response body (negative case)", diff --git a/tests/compatibility_suite/test_v3_http_matching.py b/tests/compatibility_suite/test_v3_http_matching.py index 6de96aa2b..e01c2569e 100644 --- a/tests/compatibility_suite/test_v3_http_matching.py +++ b/tests/compatibility_suite/test_v3_http_matching.py @@ -3,7 +3,6 @@ from __future__ import annotations import re -import sys from typing import TYPE_CHECKING import pytest @@ -32,10 +31,6 @@ ################################################################################ -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @scenario( "definition/features/V3/http_matching.feature", "Comparing accept headers where the actual has additional parameters", @@ -44,10 +39,6 @@ def test_comparing_accept_headers_where_the_actual_has_additional_parameters() - """Comparing accept headers where the actual has additional parameters.""" -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @scenario( "definition/features/V3/http_matching.feature", "Comparing accept headers where the actual has is missing a value", @@ -56,10 +47,6 @@ def test_comparing_accept_headers_where_the_actual_has_is_missing_a_value() -> N """Comparing accept headers where the actual has is missing a value.""" -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @scenario( "definition/features/V3/http_matching.feature", "Comparing content type headers where the actual has a charset", @@ -68,10 +55,6 @@ def test_comparing_content_type_headers_where_the_actual_has_a_charset() -> None """Comparing content type headers where the actual has a charset.""" -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @scenario( "definition/features/V3/http_matching.feature", "Comparing content type headers where the actual has a different charset", @@ -82,10 +65,6 @@ def test_comparing_content_type_headers_where_the_actual_has_a_different_charset """Comparing content type headers where the actual has a different charset.""" -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @scenario( "definition/features/V3/http_matching.feature", "Comparing content type headers where the actual is missing a charset", @@ -94,10 +73,6 @@ def test_comparing_content_type_headers_where_the_actual_is_missing_a_charset() """Comparing content type headers where the actual is missing a charset.""" -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @scenario( "definition/features/V3/http_matching.feature", "Comparing content type headers where they have the same charset", @@ -106,10 +81,6 @@ def test_comparing_content_type_headers_where_they_have_the_same_charset() -> No """Comparing content type headers where they have the same charset.""" -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @scenario( "definition/features/V3/http_matching.feature", "Comparing content type headers which are equal", diff --git a/tests/compatibility_suite/test_v3_message_producer.py b/tests/compatibility_suite/test_v3_message_producer.py index b157529ca..fe8ec008e 100644 --- a/tests/compatibility_suite/test_v3_message_producer.py +++ b/tests/compatibility_suite/test_v3_message_producer.py @@ -5,7 +5,6 @@ import json import logging import re -import sys from pathlib import Path from typing import TYPE_CHECKING, Any @@ -45,10 +44,6 @@ logger = logging.getLogger(__name__) -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @scenario( "definition/features/V3/message_provider.feature", "Incorrect message is generated by the provider", @@ -57,10 +52,6 @@ def test_incorrect_message_is_generated_by_the_provider() -> None: """Incorrect message is generated by the provider.""" -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @scenario( "definition/features/V3/message_provider.feature", "Message with JSON body (negative case)", @@ -69,10 +60,6 @@ def test_message_with_json_body_negative_case() -> None: """Message with JSON body (negative case).""" -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @scenario( "definition/features/V3/message_provider.feature", "Message with JSON body (positive case)", @@ -81,10 +68,6 @@ def test_message_with_json_body_positive_case() -> None: """Message with JSON body (positive case).""" -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @scenario( "definition/features/V3/message_provider.feature", "Message with XML body (negative case)", @@ -93,10 +76,6 @@ def test_message_with_xml_body_negative_case() -> None: """Message with XML body (negative case).""" -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @scenario( "definition/features/V3/message_provider.feature", "Message with XML body (positive case)", @@ -105,10 +84,6 @@ def test_message_with_xml_body_positive_case() -> None: """Message with XML body (positive case).""" -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @scenario( "definition/features/V3/message_provider.feature", "Message with binary body (negative case)", @@ -117,10 +92,6 @@ def test_message_with_binary_body_negative_case() -> None: """Message with binary body (negative case).""" -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @scenario( "definition/features/V3/message_provider.feature", "Message with binary body (positive case)", @@ -129,10 +100,6 @@ def test_message_with_binary_body_positive_case() -> None: """Message with binary body (positive case).""" -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @scenario( "definition/features/V3/message_provider.feature", "Message with plain text body (negative case)", @@ -141,10 +108,6 @@ def test_message_with_plain_text_body_negative_case() -> None: """Message with plain text body (negative case).""" -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @scenario( "definition/features/V3/message_provider.feature", "Message with plain text body (positive case)", @@ -153,10 +116,6 @@ def test_message_with_plain_text_body_positive_case() -> None: """Message with plain text body (positive case).""" -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @scenario( "definition/features/V3/message_provider.feature", "Supports matching rules for the message body (negative case)", @@ -165,10 +124,6 @@ def test_supports_matching_rules_for_the_message_body_negative_case() -> None: """Supports matching rules for the message body (negative case).""" -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @scenario( "definition/features/V3/message_provider.feature", "Supports matching rules for the message body (positive case)", @@ -177,10 +132,6 @@ def test_supports_matching_rules_for_the_message_body_positive_case() -> None: """Supports matching rules for the message body (positive case).""" -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @scenario( "definition/features/V3/message_provider.feature", "Supports matching rules for the message metadata (negative case)", @@ -189,10 +140,6 @@ def test_supports_matching_rules_for_the_message_metadata_negative_case() -> Non """Supports matching rules for the message metadata (negative case).""" -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @scenario( "definition/features/V3/message_provider.feature", "Supports matching rules for the message metadata (positive case)", @@ -201,10 +148,6 @@ def test_supports_matching_rules_for_the_message_metadata_positive_case() -> Non """Supports matching rules for the message metadata (positive case).""" -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @pytest.mark.skip("Currently unable to implement") @scenario( "definition/features/V3/message_provider.feature", @@ -214,10 +157,6 @@ def test_supports_messages_with_body_formatted_for_the_kafka_schema_registry() - """Supports messages with body formatted for the Kafka schema registry.""" -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @scenario( "definition/features/V3/message_provider.feature", "Verifies the message metadata", @@ -226,10 +165,6 @@ def test_verifies_the_message_metadata() -> None: """Verifies the message metadata.""" -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @scenario( "definition/features/V3/message_provider.feature", "Verifying a simple message", @@ -238,10 +173,6 @@ def test_verifying_a_simple_message() -> None: """Verifying a simple message.""" -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @scenario( "definition/features/V3/message_provider.feature", "Verifying an interaction with a defined provider state", @@ -250,10 +181,6 @@ def test_verifying_an_interaction_with_a_defined_provider_state() -> None: """Verifying an interaction with a defined provider state.""" -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @scenario( "definition/features/V3/message_provider.feature", "Verifying multiple Pact files", diff --git a/tests/compatibility_suite/test_v3_provider.py b/tests/compatibility_suite/test_v3_provider.py index 864f118c4..5470bd531 100644 --- a/tests/compatibility_suite/test_v3_provider.py +++ b/tests/compatibility_suite/test_v3_provider.py @@ -5,9 +5,7 @@ from __future__ import annotations import logging -import sys -import pytest from pytest_bdd import given, parsers, scenario from tests.compatibility_suite.util import parse_horizontal_table @@ -33,10 +31,6 @@ ################################################################################ -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @scenario( "definition/features/V3/http_provider.feature", "Verifying an interaction with multiple defined provider states", @@ -47,10 +41,6 @@ def test_verifying_an_interaction_with_multiple_defined_provider_states() -> Non """ -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @scenario( "definition/features/V3/http_provider.feature", "Verifying an interaction with a provider state with parameters", diff --git a/tests/compatibility_suite/test_v4_message_provider.py b/tests/compatibility_suite/test_v4_message_provider.py index 0a4e366ae..3be1dd9ee 100644 --- a/tests/compatibility_suite/test_v4_message_provider.py +++ b/tests/compatibility_suite/test_v4_message_provider.py @@ -5,9 +5,7 @@ from __future__ import annotations import logging -import sys -import pytest from pytest_bdd import scenario from tests.compatibility_suite.util.provider import ( @@ -30,10 +28,6 @@ ################################################################################ -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @scenario( "definition/features/V4/message_provider.feature", "Verifying a pending message interaction", @@ -44,10 +38,6 @@ def test_verifying_a_pending_message_interaction() -> None: """ -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @scenario( "definition/features/V4/message_provider.feature", "Verifying a message interaction with comments", diff --git a/tests/compatibility_suite/test_v4_provider.py b/tests/compatibility_suite/test_v4_provider.py index 9395586de..c466ebcfb 100644 --- a/tests/compatibility_suite/test_v4_provider.py +++ b/tests/compatibility_suite/test_v4_provider.py @@ -5,9 +5,7 @@ from __future__ import annotations import logging -import sys -import pytest from pytest_bdd import given, parsers, scenario from tests.compatibility_suite.util import parse_horizontal_table @@ -35,10 +33,6 @@ ################################################################################ -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @scenario( "definition/features/V4/http_provider.feature", "Verifying a pending HTTP interaction", @@ -49,10 +43,6 @@ def test_verifying_a_pending_http_interaction() -> None: """ -@pytest.mark.skipif( - sys.platform.startswith("win"), - reason="See pact-foundation/pact-python#639", -) @scenario( "definition/features/V4/http_provider.feature", "Verifying a HTTP interaction with comments", diff --git a/tests/test_match.py b/tests/test_match.py index f533fd6ab..e078cc42b 100644 --- a/tests/test_match.py +++ b/tests/test_match.py @@ -6,20 +6,19 @@ import logging import re -import subprocess -import sys import time from contextlib import contextmanager from datetime import datetime from pathlib import Path from random import randint, uniform from threading import Thread -from typing import TYPE_CHECKING, NoReturn +from typing import TYPE_CHECKING import requests from flask import Flask, Response, make_response from yarl import URL +import pact._util from pact import Pact, Verifier, generate, match if TYPE_CHECKING: @@ -29,43 +28,24 @@ @contextmanager -def start_provider() -> Generator[URL, None, None]: # noqa: C901 +def start_provider() -> Generator[URL, None, None]: """ - Start the provider app. + Start the provider app using a daemon thread. + + Yields: + The base URL of the running Flask server. """ - process = subprocess.Popen( # noqa: S603 - [ - sys.executable, - Path(__file__), - ], - cwd=Path.cwd(), - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - encoding="utf-8", - ) + hostname = "127.0.0.1" + port = pact._util.find_free_port() # noqa: SLF001 - pattern = re.compile(r" \* Running on (?P[^ ]+)") - while True: - if process.poll() is not None: - logger.error("Provider process exited with code %d", process.returncode) - logger.error( - "Provider stdout: %s", process.stdout.read() if process.stdout else "" - ) - logger.error( - "Provider stderr: %s", process.stderr.read() if process.stderr else "" - ) - msg = f"Provider process exited with code {process.returncode}" - raise RuntimeError(msg) - if ( - process.stderr - and (line := process.stderr.readline()) - and (match := pattern.match(line)) - ): - break - time.sleep(0.1) + Thread( + target=app.run, + kwargs={"host": hostname, "port": port, "use_reloader": False}, + daemon=True, + ).start() + + url = URL(f"http://{hostname}:{port}") - url = URL(match.group("url")) - logger.debug("Provider started on %s", url) for _ in range(50): try: response = requests.get(str(url / "_test" / "ping"), timeout=1) @@ -73,82 +53,69 @@ def start_provider() -> Generator[URL, None, None]: # noqa: C901 break except (requests.RequestException, AssertionError): time.sleep(0.1) - continue else: msg = "Failed to ping provider" raise RuntimeError(msg) - def redirect() -> NoReturn: - while True: - if process.stdout: - while line := process.stdout.readline(): - logger.debug("Provider stdout: %s", line.strip()) - if process.stderr: - while line := process.stderr.readline(): - logger.debug("Provider stderr: %s", line.strip()) + yield url + + +app = Flask(__name__) + + +@app.route("/path/to/") +def hello_world(test_id: int) -> Response: + random_regex_matches = "1-8 digits: 12345678, 1-8 random letters abcdefgh" + response = make_response({ + "response": { + "id": test_id, + "regexMatches": "must end with 'hello world'", + "randomRegexMatches": random_regex_matches, + "integerMatches": test_id, + "decimalMatches": round(uniform(0, 9), 3), # noqa: S311 + "booleanMatches": True, + "randomIntegerMatches": randint(1, 100), # noqa: S311 + "randomDecimalMatches": round(uniform(0, 9), 1), # noqa: S311 + "randomStringMatches": "hi there", + "includeMatches": "hello world", + "includeWithGeneratorMatches": "say 'hello world' for me", + "minMaxArrayMatches": [ + round(uniform(0, 9), 1) # noqa: S311 + for _ in range(randint(3, 5)) # noqa: S311 + ], + "arrayContainingMatches": [randint(1, 100), randint(1, 100)], # noqa: S311 + "numbers": { + "intMatches": 42, + "floatMatches": 3.1415, + "intGeneratorMatches": randint(1, 100), # noqa: S311, + "decimalGeneratorMatches": round(uniform(10, 99), 2), # noqa: S311 + }, + "dateMatches": "1999-12-31", + "randomDateMatches": "1999-12-31", + "timeMatches": "12:34:56", + "timestampMatches": datetime.now().isoformat(), # noqa: DTZ005 + "nullMatches": None, + "eachKeyMatches": { + "id_1": { + "name": "John Doe", + }, + "id_2": { + "name": "Jane Doe", + }, + }, + } + }) + response.headers["SpecialHeader"] = "Special: Hi" + return response - thread = Thread(target=redirect, daemon=True) - thread.start() - try: - yield url - finally: - process.terminate() +@app.get("/_test/ping") +def ping() -> str: + """Simple ping endpoint for testing.""" + return "pong" if __name__ == "__main__": - app = Flask(__name__) - - @app.route("/path/to/") - def hello_world(test_id: int) -> Response: - random_regex_matches = "1-8 digits: 12345678, 1-8 random letters abcdefgh" - response = make_response({ - "response": { - "id": test_id, - "regexMatches": "must end with 'hello world'", - "randomRegexMatches": random_regex_matches, - "integerMatches": test_id, - "decimalMatches": round(uniform(0, 9), 3), # noqa: S311 - "booleanMatches": True, - "randomIntegerMatches": randint(1, 100), # noqa: S311 - "randomDecimalMatches": round(uniform(0, 9), 1), # noqa: S311 - "randomStringMatches": "hi there", - "includeMatches": "hello world", - "includeWithGeneratorMatches": "say 'hello world' for me", - "minMaxArrayMatches": [ - round(uniform(0, 9), 1) # noqa: S311 - for _ in range(randint(3, 5)) # noqa: S311 - ], - "arrayContainingMatches": [randint(1, 100), randint(1, 100)], # noqa: S311 - "numbers": { - "intMatches": 42, - "floatMatches": 3.1415, - "intGeneratorMatches": randint(1, 100), # noqa: S311, - "decimalGeneratorMatches": round(uniform(10, 99), 2), # noqa: S311 - }, - "dateMatches": "1999-12-31", - "randomDateMatches": "1999-12-31", - "timeMatches": "12:34:56", - "timestampMatches": datetime.now().isoformat(), # noqa: DTZ005 - "nullMatches": None, - "eachKeyMatches": { - "id_1": { - "name": "John Doe", - }, - "id_2": { - "name": "Jane Doe", - }, - }, - } - }) - response.headers["SpecialHeader"] = "Special: Hi" - return response - - @app.get("/_test/ping") - def ping() -> str: - """Simple ping endpoint for testing.""" - return "pong" - app.run()