Files
openttd-client/docs/TESTING.md
T
kovagoadiandClaude d87c779d94
Continuous Integration / lint-and-security (pull_request) Successful in 41s
Continuous Integration / tests-and-coverage (pull_request) Successful in 28s
Add game event support to the admin client
Everything on the admin GameScript channel so far has been request/reply.
This adds the other direction: subscribe_events() opens a push stream so a
bot can react to the game instead of polling it, consumed either by awaiting
wait_for_event() or via an on_event callback. Both see every event; an event
goes to at most one waiter, and unclaimed ones sit in a bounded buffer.

Sixteen kinds, from two sources. The engine raises no GameScript event for a
vehicle reaching a stop or cargo arriving, so vehicle_arrive, vehicle_depart
and cargo_waiting are synthesised by the bridge sampling state every
`interval` ticks and diffing against the previous sample -- which means a
stop shorter than the interval is never reported, and the first sample only
establishes a baseline. The rest (crashes, industries, towns, companies,
subsidies) are engine events forwarded verbatim. vehicle_lost,
vehicle_waiting_in_depot and vehicle_unprofitable are deliberately absent:
the engine raises those only for AI companies, so a GameScript can never
observe them.

The server-side half lives in the AdminBridge GameScript, which is not in
this repo -- docker/config is gitignored -- so it has to be updated
separately for any of this to work.

Also repoints the scheduled-dispatch E2E test at a dedicated vehicle
(DISPATCH_VEHICLE_ID). It had been silently skipping because vehicle 7
carries a hand-built annual dispatch schedule, which left eight dispatch
methods unverified end to end while check_public_calls.py reported them
green off static analysis of the call sites.

Co-Authored-By: Claude <[email protected]>
2026-08-31 18:14:40 +02:00

4.2 KiB

Testing Guide for OpenTTD Python Client

This guide explains how the test suite is structured, the types of tests available, and how to execute them.


📂 Test Suite Structure

The tests are located in the tests/ directory:

Test File Target Description
test_admin.py OpenTTDAdminClient Tests admin client initialization, admin packet types, and basic protocol constants.
test_protocol.py OpenTTDProtocol Tests binary serialization, custom parsers, and stream encryption/decryption (XChaCha20-Poly1305).
test_logic.py OpenTTDClient Tests client connection lifecycle, company joining flow, authentication, and state management.
test_events.py Game Events Tests event subscription encoding, the push/pull consumption paths (on_event, wait_for_event()), buffering and waiter lifecycle.
test_coverage.py Coverage Helpers Auxiliary unit tests targeting connection errors, fallback packet handlers, and missing passwords to ensure high test coverage.
test_e2e.py Integration / E2E Connects to a running local OpenTTD server (e.g., in Docker) to verify full socket interactions, stream cryptography, and keep-alive frames.

🚀 How to Run Tests

Pytest automatically uses pytest.ini to configure the Python import path (pythonpath = lib). You do not need to manually configure PYTHONPATH.

Make sure your virtual environment is active before running commands:

source venv/bin/activate

1. Run Normal (Non-E2E) Tests

These are unit and logic tests that run instantly in memory without external dependencies:

pytest -m "not e2e"

2. Run E2E Tests Only

Requires a running local OpenTTD server configured with password "asd". If the server is offline, this test will fail:

pytest -m "e2e"

🐳 Starting the OpenTTD Server (Docker)

To run E2E tests locally, you can start the dedicated JGRPP OpenTTD server using the provided Docker Compose configuration in the docker/ directory.

1. Build and Start the Server

Run this command from the project root directory:

docker compose -f docker/docker-compose.yml up -d --build

Note: The first build will clone and compile the JGRPP source code, which may take a few minutes.

2. Monitor Server Logs

To watch the server logs (e.g., to see client connections and events):

docker compose -f docker/docker-compose.yml logs -f

3. Stop the Server

To stop the server container:

docker compose -f docker/docker-compose.yml down

3. Run All Tests

Runs both unit/logic tests and E2E tests:

pytest

4. Run with Coverage Report

To view statement coverage statistics:

pytest --cov=openttd --cov-report=term-missing

📊 Testing Mandate

We enforce the following testing mandates:

  • Normal/Unit tests (pytest -m "not e2e") must achieve 100% code coverage on the codebase independently.
  • E2E tests (pytest -m "e2e") do NOT require 100% code coverage. Instead, they are only required to cover every public function of the client classes (OpenTTDClient and OpenTTDAdminClient) and protocol classes with multiple (varied) inputs/scenarios.

To run/verify tests:

  • Normal Tests (Must achieve 100% coverage):
    pytest -m "not e2e" --cov=openttd --cov-report=term-missing --cov-fail-under=100
    
  • E2E Tests (No coverage mandate, must cover all public functions with multiple inputs):
    pytest -m "e2e"
    

🔧 Pytest Configuration (pytest.ini)

The project uses a pytest.ini file at the root:

  • pythonpath = lib: Simplifies invocation by resolving imports from the local lib/ directory.
  • markers: Registers the custom e2e marker for pytest classification.