|
| 1 | +# Test Framework for typespec-python |
| 2 | + |
| 3 | +This document describes the test framework used in the `typespec-python` package |
| 4 | +and how it relates to the upstream |
| 5 | +[`http-client-python`](https://github.com/microsoft/typespec/tree/main/packages/http-client-python) |
| 6 | +package in the typespec repository. |
| 7 | + |
| 8 | +## Overview |
| 9 | + |
| 10 | +The test framework is a **dual-flavor testing system** (Azure and Unbranded) built |
| 11 | +on **pytest** and **tox**. Tests run against a mock API server |
| 12 | +([tsp-spector](https://github.com/microsoft/typespec)) that serves TypeSpec-defined |
| 13 | +HTTP endpoints on `localhost:3000`. |
| 14 | + |
| 15 | +## Folder Structure |
| 16 | + |
| 17 | +``` |
| 18 | +packages/typespec-python/ |
| 19 | +└── tests/ |
| 20 | + ├── conftest.py # Root fixtures (server lifecycle, core_library, credentials, image data) |
| 21 | + ├── install_packages.py # Installs generated SDK packages before test runs |
| 22 | + ├── pytest.ini # Pytest config (asyncio_mode = auto) |
| 23 | + ├── tox.ini # Test environments (test, lint, mypy, pyright, docs, ci) |
| 24 | + │ |
| 25 | + ├── data/ # Static test data (image.png, image.jpg) |
| 26 | + │ |
| 27 | + ├── requirements/ # Dependency files |
| 28 | + │ ├── base.txt # Common: pytest, pytest-asyncio, tox, coverage, etc. |
| 29 | + │ ├── azure.txt # Azure flavor: azure-core, azure-mgmt-core, geojson |
| 30 | + │ ├── unbranded.txt # Unbranded flavor: corehttp |
| 31 | + │ ├── lint.txt # Linting: pylint, black |
| 32 | + │ ├── typecheck.txt # Type checking: pyright, mypy |
| 33 | + │ └── docs.txt # Documentation: sphinx, myst_parser |
| 34 | + │ |
| 35 | + ├── generated/ # Auto-generated SDK packages from TypeSpec specs |
| 36 | + │ ├── azure/ # ~116 Azure-flavored packages |
| 37 | + │ └── unbranded/ # ~64 Unbranded packages |
| 38 | + │ |
| 39 | + └── mock_api/ # Hand-written integration tests |
| 40 | + ├── azure/ # Azure-specific tests |
| 41 | + │ ├── conftest.py # Azure fixtures (credentials, LRO polling, header validation) |
| 42 | + │ ├── asynctests/ # Async test variants |
| 43 | + │ ├── data/ # Test image data |
| 44 | + │ └── test_*.py # Sync test files |
| 45 | + ├── shared/ # Tests that run for both flavors |
| 46 | + │ ├── conftest.py # Shared fixtures |
| 47 | + │ ├── asynctests/ # Async test variants |
| 48 | + │ ├── unittests/ # Unit tests (e.g. pyproject parsing) |
| 49 | + │ ├── data/ # Test image data |
| 50 | + │ └── test_*.py # Sync test files |
| 51 | + └── unbranded/ # Unbranded-specific tests |
| 52 | + ├── conftest.py # Unbranded fixtures |
| 53 | + ├── asynctests/ # Async test variants |
| 54 | + ├── data/ # Test image data |
| 55 | + └── test_*.py # Sync test files |
| 56 | +``` |
| 57 | + |
| 58 | +## Test Flavors |
| 59 | + |
| 60 | +| Flavor | Core library | Credential class | What it tests | |
| 61 | +|--------|-------------|-----------------|---------------| |
| 62 | +| **azure** | `azure.core` | `AzureKeyCredential` | Azure SDK conventions, ARM resources, LRO, paging | |
| 63 | +| **unbranded** | `corehttp` | `ServiceKeyCredential` | Non-Azure SDK generation without Azure branding | |
| 64 | + |
| 65 | +When tests run: |
| 66 | +- **Azure**: `pytest mock_api/azure mock_api/shared` with `FLAVOR=azure` |
| 67 | +- **Unbranded**: `pytest mock_api/unbranded mock_api/shared` with `FLAVOR=unbranded` |
| 68 | + |
| 69 | +The `shared/` tests run for **both** flavors. Root `conftest.py` uses `core_library()` |
| 70 | +to dynamically import the appropriate core library. |
| 71 | + |
| 72 | +## Running Tests |
| 73 | + |
| 74 | +```bash |
| 75 | +cd packages/typespec-python/tests |
| 76 | + |
| 77 | +# Run Azure flavor tests |
| 78 | +tox -e test-azure |
| 79 | + |
| 80 | +# Run Unbranded flavor tests |
| 81 | +tox -e test-unbranded |
| 82 | + |
| 83 | +# Run all CI checks for a flavor (tests + lint + type checking) |
| 84 | +tox -e ci-azure |
| 85 | +tox -e ci-unbranded |
| 86 | +``` |
| 87 | + |
| 88 | +### Available tox Environments |
| 89 | + |
| 90 | +| Environment | Description | |
| 91 | +|------------|-------------| |
| 92 | +| `test-azure` / `test-unbranded` | Run pytest integration tests | |
| 93 | +| `lint-azure` / `lint-unbranded` | Run pylint | |
| 94 | +| `mypy-azure` / `mypy-unbranded` | Run mypy type checking | |
| 95 | +| `pyright-azure` / `pyright-unbranded` | Run pyright type checking | |
| 96 | +| `docs-azure` / `docs-unbranded` | Build API docs with Sphinx | |
| 97 | +| `ci-azure` / `ci-unbranded` | All checks combined | |
| 98 | + |
| 99 | +## Key Components |
| 100 | + |
| 101 | +### Mock API Server |
| 102 | + |
| 103 | +Tests rely on `tsp-spector` to serve TypeSpec-defined mock endpoints. The root |
| 104 | +`conftest.py` starts the server automatically at session start and tears it down |
| 105 | +after all tests complete. The server runs on `localhost:3000`. |
| 106 | + |
| 107 | +### Generated Packages |
| 108 | + |
| 109 | +Each test spec produces a generated SDK package under `tests/generated/{flavor}/`. |
| 110 | +Before tests run, `install_packages.py` installs all generated packages into the |
| 111 | +test environment using `uv pip install --no-deps`. |
| 112 | + |
| 113 | +### Async Tests |
| 114 | + |
| 115 | +Every `test_*.py` in the sync directory has a corresponding `test_*_async.py` in |
| 116 | +`asynctests/`. Async fixtures use `@pytest_asyncio.fixture` and `pytest.ini` |
| 117 | +configures `asyncio_mode = auto`. |
| 118 | + |
| 119 | +## Folder Mapping to the typespec Repository |
| 120 | + |
| 121 | +The test files are shared with the upstream |
| 122 | +[`http-client-python`](https://github.com/microsoft/typespec/tree/main/packages/http-client-python) |
| 123 | +package. The **typespec repo is the source of truth** for shared test files. |
| 124 | + |
| 125 | +### Path Mapping |
| 126 | + |
| 127 | +| typespec repo | autorest.python repo | |
| 128 | +|--------------|---------------------| |
| 129 | +| `packages/http-client-python/tests/mock_api/azure/` | `packages/typespec-python/tests/mock_api/azure/` | |
| 130 | +| `packages/http-client-python/tests/mock_api/shared/` | `packages/typespec-python/tests/mock_api/shared/` | |
| 131 | +| `packages/http-client-python/tests/mock_api/unbranded/` | `packages/typespec-python/tests/mock_api/unbranded/` | |
| 132 | +| `packages/http-client-python/tests/requirements/` | `packages/typespec-python/tests/requirements/` | |
| 133 | +| `packages/http-client-python/eng/scripts/ci/regenerate-common.ts` | `packages/typespec-python/eng/scripts/regenerate-common.ts` | |
| 134 | + |
| 135 | +### What Is Synced |
| 136 | + |
| 137 | +The script `eng/scripts/sync_from_typespec.py` copies from typespec → autorest.python: |
| 138 | + |
| 139 | +1. **`regenerate-common.ts`** — shared regeneration logic |
| 140 | +2. **Requirements files** — `azure.txt` and `unbranded.txt` (marker-delimited |
| 141 | + common sections are synced; repo-specific deps like `geojson` are preserved) |
| 142 | +3. **Test files** — all files under `mock_api/{shared,azure,unbranded}` except: |
| 143 | + - `conftest.py` (each repo has its own) |
| 144 | + - `tox.ini`, `requirements.txt`, `dev_requirements.txt` |
| 145 | + - `.pyc` files |
| 146 | + |
| 147 | +### What Is NOT Synced |
| 148 | + |
| 149 | +| Item | Reason | |
| 150 | +|------|--------| |
| 151 | +| `conftest.py` files | Different server startup and fixture logic per repo | |
| 152 | +| `tests/mock_api/shared/unittests/` | Repo-specific unit tests | |
| 153 | +| `tests/generated/` | Regenerated independently in each repo | |
| 154 | +| `tests/unit/` (typespec only) | Internal to http-client-python | |
| 155 | +| `pytest.ini`, `tox.ini` | Different CI configurations per repo | |
| 156 | + |
| 157 | +### Requirements Marker Convention |
| 158 | + |
| 159 | +Requirements files (`azure.txt`, `unbranded.txt`) use markers to delimit the |
| 160 | +common section synced between repos: |
| 161 | + |
| 162 | +``` |
| 163 | +# === common azure dependencies across repos === |
| 164 | +# Azure SDK dependencies |
| 165 | +-r base.txt |
| 166 | +azure-core>=1.37.0 |
| 167 | +azure-mgmt-core==1.6.0 |
| 168 | +# === end common azure dependencies across repos === |
| 169 | +geojson>=3.0.0 # <-- autorest.python-only dependency, outside markers |
| 170 | +``` |
| 171 | + |
| 172 | +Dependencies outside the markers are preserved during sync. |
| 173 | + |
| 174 | +### Sync Workflow |
| 175 | + |
| 176 | +The sync is run automatically as part of [pipeline](https://dev.azure.com/azure-sdk/internal/_build?definitionId=7257), or manually: |
| 177 | + |
| 178 | +```bash |
| 179 | +python eng/scripts/sync_from_typespec.py <path-to-typespec-repo> |
| 180 | +``` |
0 commit comments