diff --git a/.github/workflows/ci-test.yml b/.github/workflows/ci-test.yml index 510479aa..f601e44d 100644 --- a/.github/workflows/ci-test.yml +++ b/.github/workflows/ci-test.yml @@ -90,7 +90,6 @@ jobs: #CACHIER_TEST_DB: "dummy_db" #CACHIER_TEST_USERNAME: "myuser" #CACHIER_TEST_PASSWORD: "yourpassword" - CACHIER_TEST_VS_DOCKERIZED_MONGO: "true" CACHIER_TEST_REDIS_HOST: "localhost" CACHIER_TEST_REDIS_PORT: "6379" CACHIER_TEST_REDIS_DB: "0" @@ -137,7 +136,7 @@ jobs: # prefix-key: "mongo-db" # images: mongo:8 - name: Start MongoDB in docker - if: matrix.backend == 'mongodb' + if: matrix.backend == 'mongodb' && runner.os != 'Windows' run: | # start MongoDB in a container docker run -d -p ${{ env.CACHIER_TEST_PORT }}:27017 --name mongodb mongo:8 @@ -146,9 +145,18 @@ jobs: # show running containers docker ps -a - - name: Unit tests (DB) - if: matrix.backend == 'mongodb' + - name: Unit tests (MongoDB, Docker) + if: matrix.backend == 'mongodb' && runner.os != 'Windows' + env: + CACHIER_TEST_VS_DOCKERIZED_MONGO: "true" run: pytest -m "mongo" --cov=cachier --cov-report=term --cov-report=xml:cov.xml + + - name: Unit tests (MongoDB, in-memory) + if: matrix.backend == 'mongodb' && runner.os == 'Windows' + env: + CACHIER_TEST_VS_DOCKERIZED_MONGO: "false" + run: pytest -m "mongo" --cov=cachier --cov-report=term --cov-report=xml:cov.xml + - name: Speed eval run: python tests/speed_eval.py diff --git a/README.rst b/README.rst index 3d42fd9a..e9319eb9 100644 --- a/README.rst +++ b/README.rst @@ -853,11 +853,11 @@ This script automatically handles Docker container lifecycle, environment variab # Clean up docker stop cachier-test-mongo && docker rm cachier-test-mongo -**CI Environment:** The ``CACHIER_TEST_VS_DOCKERIZED_MONGO`` environment variable is set to ``True`` in the GitHub Actions CI environment, which runs tests against a real MongoDB instance on every commit and pull request. +**CI Environment:** GitHub Actions runs MongoDB tests against a real Dockerized MongoDB instance on Linux. Windows MongoDB jobs use the in-memory MongoDB backend because GitHub-hosted Windows runners do not provide a stable Docker daemon for Linux MongoDB containers. -Contributors are encouraged to test against a real MongoDB instance before submitting PRs to ensure compatibility, though the in-memory MongoDB instance serves as a good proxy for most development. +Contributors are encouraged to test against a real MongoDB instance before submitting PRs to ensure compatibility, though the in-memory MongoDB instance serves as a good proxy for most development and CI platform coverage. -**HOWEVER, the tests run against a live MongoDB instance when you submit a PR are the determining tests for deciding whether your code functions correctly against MongoDB.** +**HOWEVER, only the Linux MongoDB CI jobs are live-server integration tests. Windows MongoDB CI jobs still exercise the Mongo backend API surface, but they are compatibility coverage over ``pymongo_inmemory`` rather than proof of live MongoDB server behavior.** Testing all backends locally diff --git a/scripts/test-local.sh b/scripts/test-local.sh index 5682da44..519ebb75 100755 --- a/scripts/test-local.sh +++ b/scripts/test-local.sh @@ -312,6 +312,28 @@ check_dependencies() { print_message $GREEN "All required dependencies are installed!" } +run_pytest_phase() { + local phase_name="$1" + shift + + print_message $BLUE "Running $phase_name: $(printf '%q ' "$@")" + + local phase_exit_code=0 + if "$@"; then + phase_exit_code=0 + else + phase_exit_code=$? + fi + + if [ "$phase_exit_code" -eq 0 ]; then + print_message $GREEN "$phase_name passed" + else + print_message $RED "$phase_name failed with exit code $phase_exit_code" + fi + + return "$phase_exit_code" +} + # MongoDB functions start_mongodb() { print_message $YELLOW "Starting MongoDB container..." @@ -613,23 +635,42 @@ EOF PYTEST_ARGS+=(--cov=cachier --cov-report="$COVERAGE_REPORT") SERIAL_PYTEST_ARGS+=(--cov=cachier --cov-report="$COVERAGE_REPORT" --cov-append) - # Print and run the command - print_message $BLUE "Running: $(printf '%q ' "${PYTEST_ARGS[@]}")" - "${PYTEST_ARGS[@]}" + MAIN_TEST_EXIT_CODE=0 + SERIAL_TEST_EXIT_CODE=0 + MAIN_TEST_STATUS="skipped" + SERIAL_TEST_STATUS="skipped" + + if run_pytest_phase "main pytest phase" "${PYTEST_ARGS[@]}"; then + MAIN_TEST_EXIT_CODE=0 + MAIN_TEST_STATUS="passed" + else + MAIN_TEST_EXIT_CODE=$? + MAIN_TEST_STATUS="failed ($MAIN_TEST_EXIT_CODE)" + fi if [ "$run_serial_local_tests" = true ]; then - print_message $BLUE "Running serial local tests (pickle, memory) with: $(printf '%q ' "${SERIAL_PYTEST_ARGS[@]}")" - "${SERIAL_PYTEST_ARGS[@]}" + if run_pytest_phase "serial local pytest phase" "${SERIAL_PYTEST_ARGS[@]}"; then + SERIAL_TEST_EXIT_CODE=0 + SERIAL_TEST_STATUS="passed" + else + SERIAL_TEST_EXIT_CODE=$? + SERIAL_TEST_STATUS="failed ($SERIAL_TEST_EXIT_CODE)" + fi else print_message $BLUE "Skipping serial local tests (pickle, memory) since not requested" fi - TEST_EXIT_CODE=$? + TEST_EXIT_CODE=0 + if [ "$MAIN_TEST_EXIT_CODE" -ne 0 ]; then + TEST_EXIT_CODE=$MAIN_TEST_EXIT_CODE + elif [ "$SERIAL_TEST_EXIT_CODE" -ne 0 ]; then + TEST_EXIT_CODE=$SERIAL_TEST_EXIT_CODE + fi if [ $TEST_EXIT_CODE -eq 0 ]; then print_message $GREEN "All tests passed!" else - print_message $RED "Some tests failed. Exit code: $TEST_EXIT_CODE" + print_message $RED "Some tests failed. Main phase: $MAIN_TEST_STATUS, serial local phase: $SERIAL_TEST_STATUS" fi # Exit with test status diff --git a/tests/mongo_tests/helpers.py b/tests/mongo_tests/helpers.py index 6af2474d..9480b939 100644 --- a/tests/mongo_tests/helpers.py +++ b/tests/mongo_tests/helpers.py @@ -2,7 +2,9 @@ import platform import sys +import warnings from contextlib import suppress +from typing import Any from urllib.parse import quote_plus from birch import Birch # type: ignore[import-not-found] @@ -41,6 +43,16 @@ class CfgKey: _COLLECTION_NAME = f"cachier_test_{platform.system()}_{'.'.join(map(str, sys.version_info[:3]))}" +def _build_inmemory_mongo_client() -> Any: + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", + message="Python 3.14 will, by default, filter extracted tar archives.*", + category=DeprecationWarning, + ) + return InMemoryMongoClient() + + def _get_cachier_db_mongo_client(): host = quote_plus(CFG[CfgKey.HOST]) port = quote_plus(CFG[CfgKey.PORT]) @@ -55,7 +67,7 @@ def _test_mongetter(): _test_mongetter.client = _get_cachier_db_mongo_client() else: print("Using in-memory MongoDB instance for testing.") - _test_mongetter.client = InMemoryMongoClient() + _test_mongetter.client = _build_inmemory_mongo_client() db_obj = _test_mongetter.client["cachier_test"] if _COLLECTION_NAME not in db_obj.list_collection_names(): db_obj.create_collection(_COLLECTION_NAME)