From 635d0db2015e910e0b76e269cf14042e1a2e6881 Mon Sep 17 00:00:00 2001 From: Ryan Min <73128364+kcrmin@users.noreply.github.com> Date: Mon, 26 Oct 2026 22:29:49 +0900 Subject: [PATCH 1/4] ci(backtest): drop unused localstack services Testcontainers starts every container the docker suites use, so the service containers and their fixed endpoint variables went unread. --- .github/workflows/ci.yml | 58 +++++++++++++--------------------------- 1 file changed, 18 insertions(+), 40 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4834c09..53e0511 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,8 +19,8 @@ name: CI # gate5 app boot smoke API and worker import without a database # gate6 migration contribution COM07 contribution-root contract # gate7 dependency + secret scan pip-audit + TruffleHog -# gate8 DB integration Testcontainers PostgreSQL 16 -# gate9 object-store integration LocalStack S3 +# gate8 DB integration Testcontainers PostgreSQL 16 + LocalStack +# gate9 object-store integration Testcontainers LocalStack S3 on: pull_request: @@ -291,27 +291,14 @@ jobs: if: github.event_name == 'pull_request' || github.ref == 'refs/heads/main' runs-on: ubuntu-latest timeout-minutes: 30 - # LocalStack as well as PostgreSQL, because `-m docker` is a single set that - # spans both: the worker's SQS tests and the end-to-end test need a queue and - # an object store, not just a database. Without this the queue tests skipped - # here - and their default endpoint is a developer's local container port, so - # they passed on that machine and covered nothing in CI. - services: - localstack: - image: localstack/localstack:4.7.0 - env: - SERVICES: s3,sqs - EAGER_SERVICE_LOADING: "1" - AWS_DEFAULT_REGION: us-east-1 - ports: - - 4566:4566 - options: >- - --health-cmd "curl -sf http://localhost:4566/_localstack/health || exit 1" - --health-interval 10s --health-timeout 5s --health-retries 12 - # `tests/conftest.py` starts its own PostgreSQL 16 container through - # Testcontainers and applies the CENTRAL Flyway bundle to it, so there is no - # `services:` block here: a second, differently-migrated server would only - # invite tests to point at the wrong one. + # `-m docker` is a single set that spans a database, a queue and an object + # store: the worker's SQS tests and the end-to-end tests need all three. + # `tests/conftest.py` starts every one of them itself through Testcontainers: + # a PostgreSQL 16 container migrated with the CENTRAL Flyway bundle, and one + # LocalStack (SQS + S3) for the session. There is deliberately no `services:` + # block here: nothing in the suite reads a fixed endpoint, so a service + # container would run unused, and a second, differently-migrated database + # would only invite tests to point at the wrong one. # # That harness *skips* when Docker is unavailable, and a skip is a silent # pass. The last step therefore asserts, from the JUnit report, that the @@ -336,13 +323,11 @@ jobs: # The container is migrated with the vendored byte-for-byte copy of the # applied central Flyway bundle, so this proves the persistence layer # against the canonical schema and not against a restatement of it. - - name: Integration tests (PostgreSQL 16 via Testcontainers, queue via LocalStack) + - name: Integration tests (PostgreSQL 16 and LocalStack via Testcontainers) env: - # tests/test_worker.py defaults to a developer's local LocalStack port - # and skips when it is absent. Point it at the service container so the - # real SQS coverage actually executes here. - BACKTEST_TEST_SQS_ENDPOINT: http://localhost:4566 - LOCALSTACK_ENDPOINT_URL: http://localhost:4566 + # Dummy credentials. Every boto3 client in the suite carries its own + # endpoint and credentials and passes without these; they only make + # sure a stray default-chain client can never pick up real ones. AWS_ACCESS_KEY_ID: test AWS_SECRET_ACCESS_KEY: test AWS_DEFAULT_REGION: us-east-1 @@ -387,16 +372,9 @@ jobs: if: github.event_name == 'pull_request' || github.ref == 'refs/heads/main' runs-on: ubuntu-latest timeout-minutes: 20 - services: - localstack: - image: localstack/localstack:4.7.0 - env: - SERVICES: s3,sqs - ports: - - 4566:4566 - options: >- - --health-cmd "curl -sf http://localhost:4566/_localstack/health || exit 1" - --health-interval 10s --health-timeout 5s --health-retries 12 + # No `services:` block: the S3 leg starts its own LocalStack through + # Testcontainers (`tests/conftest.py` and `tests/test_object_store.py`) and + # reads no fixed endpoint, so a service container here would run unused. steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 @@ -410,7 +388,7 @@ jobs: # runs against both backends, and the S3 leg must not skip here. - name: Object-store contract against LocalStack S3 env: - LOCALSTACK_ENDPOINT_URL: http://localhost:4566 + # Dummy credentials, for the same reason as in gate8. AWS_ACCESS_KEY_ID: test AWS_SECRET_ACCESS_KEY: test AWS_DEFAULT_REGION: us-east-1 From 1f88cf986257a9c46908419ab32b1cc20263ac01 Mon Sep 17 00:00:00 2001 From: Ryan Min <73128364+kcrmin@users.noreply.github.com> Date: Mon, 26 Oct 2026 22:32:12 +0900 Subject: [PATCH 2/4] test(backtest): skip the rlimit probe on macos macOS rejects setrlimit(RLIMIT_AS), so the forked probe died and the unit suite failed on every Mac. Linux CI still runs it. --- tests/test_attempt_coordinator.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test_attempt_coordinator.py b/tests/test_attempt_coordinator.py index 96ec068..825b930 100644 --- a/tests/test_attempt_coordinator.py +++ b/tests/test_attempt_coordinator.py @@ -417,6 +417,10 @@ def _run(conn: Any) -> None: return applied +@pytest.mark.skipif( + sys.platform == "darwin", + reason="macOS rejects setrlimit(RLIMIT_AS); the Linux worker image is what enforces it", +) def test_monitor_names_the_enforcement_mechanism_actually_in_force() -> None: if sys.platform == "win32": # Windows has no POSIX rlimit, so the call is a no-op on this process From 7b05241082666f1e1e97cfdc9bfab023bcf24e87 Mon Sep 17 00:00:00 2001 From: Ryan Min <73128364+kcrmin@users.noreply.github.com> Date: Mon, 26 Oct 2026 22:32:44 +0900 Subject: [PATCH 3/4] ci(backtest): hold unit coverage at its 77% floor gate3 measured coverage but enforced nothing. 77 is today's CI total (77.6%) rounded down, so the gate can only ratchet upward. --- .github/workflows/ci.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 53e0511..ecacc0a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ name: CI # # gate1 lint ruff # gate2 types mypy -# gate3 unit tests + coverage pytest (Docker-free suite) +# gate3 unit tests + coverage pytest (Docker-free suite), floor 77% # gate4 contract validation JSON Schema round-trip over the fixtures # gate5 app boot smoke API and worker import without a database # gate6 migration contribution COM07 contribution-root contract @@ -99,6 +99,11 @@ jobs: # stdout is a pipe, so the kill discarded every line and the log named no # test. The same command finishes locally in ~16s. Unbuffered output plus # a per-test timeout turn a hang into a named test with a stack trace. + # + # The coverage floor is the measured total (statements plus branches), + # rounded down: 77.6% on develop at a568df0. data-pipeline holds 88; this + # repository is not there yet, so the floor only ratchets upward. Raise it + # when coverage grows and never lower it to make a change pass. - name: pytest (Docker-free suite) env: PYTHONUNBUFFERED: "1" @@ -106,6 +111,7 @@ jobs: python -m pytest -p no:cacheprovider -m "not docker" --timeout=120 --timeout-method=thread --cov=backtest_engine --cov-report=term-missing --cov-report=xml + --cov-fail-under=77 - name: Upload coverage if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 From bb172152111a2747f6f0c2fa674b610d8309365a Mon Sep 17 00:00:00 2001 From: Ryan Min <73128364+kcrmin@users.noreply.github.com> Date: Mon, 26 Oct 2026 22:33:09 +0900 Subject: [PATCH 4/4] docs(deps): correct the httpx2 comment httpx2 exists, and starlette.testclient prefers it over httpx. --- pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 84e8f09..f4422f2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,8 @@ dependencies = [ ] [project.optional-dependencies] -# `httpx` (not the non-existent `httpx2`) is what starlette.testclient imports. +# starlette.testclient prefers `httpx2` and falls back to this `httpx` with a +# StarletteDeprecationWarning. Moving to `httpx2` is a separate, deliberate bump. test = [ "freezegun==1.5.5", "hypothesis==6.138.15",