|
| 1 | +# AGENTS.md - AI Agent Development Guide |
| 2 | + |
| 3 | +This guide provides instructions for AI agents working with the cardano-node-tests repository, assuming the development environment is already activated. |
| 4 | + |
| 5 | +--- |
| 6 | + |
| 7 | +## 🧪 Running Individual Tests |
| 8 | + |
| 9 | +When the development environment is activated, you can run individual tests using pytest. |
| 10 | + |
| 11 | +### Basic Test Execution |
| 12 | + |
| 13 | +Run a specific test by name: |
| 14 | + |
| 15 | +```sh |
| 16 | +pytest -k "test_minting_one_token" cardano_node_tests/tests/tests_plutus |
| 17 | +``` |
| 18 | + |
| 19 | +Run multiple tests matching a pattern: |
| 20 | + |
| 21 | +```sh |
| 22 | +pytest -k "test_missing_tx_out or test_multiple_same_txins" cardano_node_tests |
| 23 | +``` |
| 24 | + |
| 25 | +Run tests with a specific marker: |
| 26 | + |
| 27 | +```sh |
| 28 | +pytest -m smoke cardano_node_tests/tests/test_cli.py |
| 29 | +``` |
| 30 | + |
| 31 | +### Debugging and Verbose Output |
| 32 | + |
| 33 | +Enable full CLI command logging: |
| 34 | + |
| 35 | +```sh |
| 36 | +pytest -s --log-level=debug -k test_minting_one_token cardano_node_tests/tests/tests_plutus |
| 37 | +``` |
| 38 | + |
| 39 | +### Test Organization |
| 40 | + |
| 41 | +Tests are organized under: |
| 42 | + |
| 43 | +- `cardano_node_tests/tests/` - Main test directory |
| 44 | +- `cardano_node_tests/tests/tests_plutus/` - Plutus-specific tests for all Plutus versions |
| 45 | +- `cardano_node_tests/tests/tests_plutus_v2/` - Plutus-specific tests for PlutusV2 |
| 46 | +- `cardano_node_tests/tests/tests_plutus_v3/` - Plutus-specific tests for PlutusV3 |
| 47 | +- `cardano_node_tests/tests/tests_conway/` - Conway era specific tests |
| 48 | +- `cardano_node_tests/tests/data/` - Test data files |
| 49 | + |
| 50 | +### Framework files |
| 51 | + |
| 52 | +- `cardano_node_tests/cluster_management/` - Cluster management utilities for managing local testnet clusters |
| 53 | +- `cardano_node_tests/utils/` - Utility functions for tests and the framework |
| 54 | +- `cardano_node_tests/pytest_plugins/` - Pytest plugins, namely a custom pytest-xdist scheduler |
| 55 | + |
| 56 | +--- |
| 57 | + |
| 58 | +## 🔧 Making Code Changes |
| 59 | + |
| 60 | +### Coding Guidelines |
| 61 | + |
| 62 | +1. **Follow the Google Python Style Guide** |
| 63 | + - See: <https://google.github.io/styleguide/pyguide.html> |
| 64 | + |
| 65 | +2. **Use Type Hints** |
| 66 | + - All functions and methods should have type hints for parameters and return types |
| 67 | + - Test functions and methods should also include type hints, with the exception that return type `None` is omitted |
| 68 | + |
| 69 | +3. **Use Ruff for Formatting** |
| 70 | + - Formatting is enforced via pre-commit hooks |
| 71 | + - Ruff handles both linting and formatting |
| 72 | + |
| 73 | +4. **Run Linters** |
| 74 | + |
| 75 | + ```sh |
| 76 | + make lint |
| 77 | + ``` |
| 78 | + |
| 79 | +### Making Changes to Dependencies |
| 80 | + |
| 81 | +If you modify dependencies in `pyproject.toml`, update the lockfile: |
| 82 | + |
| 83 | +```sh |
| 84 | +make update-lockfile |
| 85 | +``` |
| 86 | + |
| 87 | +--- |
| 88 | + |
| 89 | +## 📝 Testing Workflow |
| 90 | + |
| 91 | +1. **Make your code changes** following the Google Python Style Guide |
| 92 | + |
| 93 | +2. **Run relevant tests** to verify your changes: |
| 94 | + |
| 95 | + ```sh |
| 96 | + pytest -k "your_test_pattern" cardano_node_tests |
| 97 | + ``` |
| 98 | + |
| 99 | +3. **Run linters** to ensure code quality: |
| 100 | + |
| 101 | + ```sh |
| 102 | + make lint |
| 103 | + ``` |
| 104 | + |
| 105 | +## Changing Markdown and ReStructuredText Documentation |
| 106 | + |
| 107 | +1. **Make your changes** |
| 108 | + |
| 109 | +2. **Run Linters to check formatting**: |
| 110 | + |
| 111 | + ```sh |
| 112 | + make lint |
| 113 | + ``` |
| 114 | + |
| 115 | +--- |
| 116 | + |
| 117 | +## 🎯 Common Test Patterns |
| 118 | + |
| 119 | +### Running Tests by Category |
| 120 | + |
| 121 | +By marker: |
| 122 | + |
| 123 | +```sh |
| 124 | +pytest -m smoke cardano_node_tests |
| 125 | +``` |
| 126 | + |
| 127 | +By test path: |
| 128 | + |
| 129 | +```sh |
| 130 | +pytest cardano_node_tests/tests/test_cli.py |
| 131 | +``` |
| 132 | + |
| 133 | +Framework tests: |
| 134 | + |
| 135 | +```sh |
| 136 | +pytest framework_tests/ |
| 137 | +``` |
| 138 | + |
| 139 | +### Running Tests with Custom Configuration |
| 140 | + |
| 141 | +Filter by multiple conditions: |
| 142 | + |
| 143 | +```sh |
| 144 | +pytest -k "test_stake_pool_low_cost or test_reward_amount" cardano_node_tests |
| 145 | +``` |
| 146 | + |
| 147 | +With marker exclusion: |
| 148 | + |
| 149 | +```sh |
| 150 | +pytest -m "not long" cardano_node_tests |
| 151 | +``` |
| 152 | + |
| 153 | +--- |
| 154 | + |
| 155 | +## 🔍 Verifying Your Environment |
| 156 | + |
| 157 | +Before running tests, verify: |
| 158 | + |
| 159 | +1. **Local testnet is running** - Check with `[ -S "$CARDANO_NODE_SOCKET_PATH" ] && echo RUNNING || echo NOT RUNNING` |
| 160 | +2. **Python Virtual environment is activated** - Check with `[ -n "$VIRTUAL_ENV" ] && echo ACTIVE || echo NOT ACTIVE` |
| 161 | +3. **Needed binaries are available** - Check that `cardano-node` and `cardano-cli` are in your PATH |
| 162 | + |
| 163 | +--- |
| 164 | + |
| 165 | +## 🔐 Cluster Management and Resource Locking |
| 166 | + |
| 167 | +The test framework uses a sophisticated cluster management system that allows multiple tests to run in parallel on shared cluster instances while coordinating access to shared resources. |
| 168 | + |
| 169 | +### How Cluster Management Works |
| 170 | + |
| 171 | +**Pool of Cluster Instances**: The framework can run multiple cluster instances concurrently (configured via `CLUSTERS_COUNT`). Each test worker requests a cluster instance to run a test on. |
| 172 | + |
| 173 | +**Coordination via File System**: Workers communicate through status files created in a shared temporary directory. These files act as locks and signals indicating: |
| 174 | + |
| 175 | +- Which test is running on which cluster instance |
| 176 | +- Which resources are locked or in use |
| 177 | +- When a cluster needs to be respun (restarted to clean state) |
| 178 | + |
| 179 | +**Resource Management**: Tests declare what resources they need. The `ClusterManager` ensures only one test uses exclusive resources at a time. |
| 180 | + |
| 181 | +**Cluster Respin**: Some tests modify cluster state irreversibly. These tests can request a "respin" to re-initialize the cluster to a clean state. |
| 182 | + |
| 183 | +### Available Resources |
| 184 | + |
| 185 | +Resources that are defined in `cluster_management.Resources`: |
| 186 | + |
| 187 | +- **`CLUSTER`** - Whole cluster instance (used by every test, can be locked for exclusive access) |
| 188 | +- **`POOL1`, `POOL2`, `POOL3`, `ALL_POOLS`** - Individual stake pools or tuple of all pools |
| 189 | +- **`POOL_FOR_OFFLINE`** - Reserved pool for tests where pool stops producing blocks |
| 190 | +- **`PLUTUS`** - Plutus script resources |
| 191 | +- **`RESERVES`** - Reserve pot |
| 192 | +- **`TREASURY`** - Treasury pot |
| 193 | +- **`REWARDS`** - Rewards pot |
| 194 | +- **`POTS`** - Tuple of all pots (RESERVES, TREASURY, REWARDS) |
| 195 | +- **`PERF`** - Performance testing resources |
| 196 | +- **`DREPS`** - DRep resources |
| 197 | + |
| 198 | +Other shared resources can exist, like Plutus scripts, when the same script is used in multiple tests. |
| 199 | + |
| 200 | +### Resource Locking vs. Using |
| 201 | + |
| 202 | +**`lock_resources`**: Exclusive access. No other test can use these resources until the current test finishes. Use when a test will modify the resource state. |
| 203 | + |
| 204 | +**`use_resources`**: Shared access. Multiple tests can use the same resource, but the framework tracks usage. Use when a test only reads or makes non-conflicting changes. Other tests cannot lock the resource while it's in use. |
| 205 | + |
| 206 | +### Writing Tests with Resource Management |
| 207 | + |
| 208 | +#### Basic Pattern - Using the `cluster` Fixture |
| 209 | + |
| 210 | +```python |
| 211 | +def test_something(cluster: clusterlib.ClusterLib): |
| 212 | + # Basic usage - cluster automatically manages common resources |
| 213 | + # No explicit resource declaration needed for simple tests |
| 214 | + ... |
| 215 | +``` |
| 216 | + |
| 217 | +#### Custom Fixtures with Resource Requirements |
| 218 | + |
| 219 | +**Lock specific resources** (exclusive access): |
| 220 | + |
| 221 | +```python |
| 222 | +@pytest.fixture |
| 223 | +def cluster_lock_pool_and_pots( |
| 224 | + cluster_manager: cluster_management.ClusterManager, |
| 225 | +) -> tuple[clusterlib.ClusterLib, str]: |
| 226 | + cluster_obj = cluster_manager.get( |
| 227 | + lock_resources=[ |
| 228 | + *cluster_management.Resources.POTS, |
| 229 | + resources_management.OneOf(resources=cluster_management.Resources.ALL_POOLS), |
| 230 | + ] |
| 231 | + ) |
| 232 | + pool_name = cluster_manager.get_locked_resources( |
| 233 | + from_set=cluster_management.Resources.ALL_POOLS |
| 234 | + )[0] |
| 235 | + return cluster_obj, pool_name |
| 236 | +``` |
| 237 | + |
| 238 | +**Use resources** (shared access): |
| 239 | + |
| 240 | +```python |
| 241 | +@pytest.fixture |
| 242 | +def cluster_use_pool_and_rewards( |
| 243 | + cluster_manager: cluster_management.ClusterManager, |
| 244 | +) -> tuple[clusterlib.ClusterLib, str]: |
| 245 | + cluster_obj = cluster_manager.get( |
| 246 | + use_resources=[ |
| 247 | + resources_management.OneOf(resources=cluster_management.Resources.ALL_POOLS), |
| 248 | + cluster_management.Resources.REWARDS, |
| 249 | + ] |
| 250 | + ) |
| 251 | + pool_name = cluster_manager.get_used_resources( |
| 252 | + from_set=cluster_management.Resources.ALL_POOLS |
| 253 | + )[0] |
| 254 | + return cluster_obj, pool_name |
| 255 | +``` |
| 256 | + |
| 257 | +**Lock multiple pools** (e.g., for pool interaction tests): |
| 258 | + |
| 259 | +```python |
| 260 | +@pytest.fixture |
| 261 | +def cluster_lock_two_pools( |
| 262 | + cluster_manager: cluster_management.ClusterManager, |
| 263 | +) -> tuple[clusterlib.ClusterLib, str, str]: |
| 264 | + cluster_obj = cluster_manager.get( |
| 265 | + lock_resources=[ |
| 266 | + resources_management.OneOf(resources=cluster_management.Resources.ALL_POOLS), |
| 267 | + resources_management.OneOf(resources=cluster_management.Resources.ALL_POOLS), |
| 268 | + ] |
| 269 | + ) |
| 270 | + pool_names = cluster_manager.get_locked_resources( |
| 271 | + from_set=cluster_management.Resources.ALL_POOLS |
| 272 | + ) |
| 273 | + return cluster_obj, pool_names[0], pool_names[1] |
| 274 | +``` |
| 275 | + |
| 276 | +**Lock shared Plutus script**: |
| 277 | + |
| 278 | +```python |
| 279 | +@pytest.fixture |
| 280 | +def cluster_lock_stake_script( |
| 281 | + cluster_manager: cluster_management.ClusterManager, |
| 282 | +) -> clusterlib.ClusterLib: |
| 283 | + """Make sure just one staking Plutus test run at a time. |
| 284 | +
|
| 285 | + Plutus script always has the same address. When one script is used in multiple |
| 286 | + tests that are running in parallel, the balances etc. don't add up. |
| 287 | + """ |
| 288 | + cluster_obj = cluster_manager.get( |
| 289 | + lock_resources=[helpers.checksum(plutus_common.STAKE_PLUTUS_V2)], |
| 290 | + use_resources=[cluster_management.Resources.PLUTUS], |
| 291 | + ) |
| 292 | + return cluster_obj |
| 293 | +``` |
| 294 | + |
| 295 | +### The `OneOf` Filter |
| 296 | + |
| 297 | +`resources_management.OneOf` selects one available resource from a set: |
| 298 | + |
| 299 | +```python |
| 300 | +# Select any available pool |
| 301 | +resources_management.OneOf(resources=cluster_management.Resources.ALL_POOLS) |
| 302 | + |
| 303 | +# Use OneOf multiple times to select different resources |
| 304 | +[ |
| 305 | + resources_management.OneOf(resources=cluster_management.Resources.ALL_POOLS), |
| 306 | + resources_management.OneOf(resources=cluster_management.Resources.ALL_POOLS), |
| 307 | +] |
| 308 | +# This will select two DIFFERENT pools from ALL_POOLS |
| 309 | +``` |
| 310 | + |
| 311 | +The filter automatically handles: |
| 312 | + |
| 313 | +- Resources already locked by other tests |
| 314 | +- Resources already selected by previous filters in the same request |
| 315 | +- Returning empty list if no resource is available (test will wait) |
| 316 | + |
| 317 | +### Best Practices |
| 318 | + |
| 319 | +1. **Use `lock_resources` when modifying state**: If your test changes pool rewards, treasury, or other shared state, lock those resources. |
| 320 | + |
| 321 | +2. **Use `use_resources` for read-only or non-conflicting operations**: If you only query data or make isolated changes, use shared access. |
| 322 | + |
| 323 | +3. **Be specific about resource needs**: Only lock/use resources you actually need to minimize test blocking. |
| 324 | + |
| 325 | +4. **Request respin when needed**: If your test leaves cluster in unusable state: |
| 326 | + |
| 327 | + ```python |
| 328 | + cluster_manager.set_needs_respin() |
| 329 | + ``` |
| 330 | + |
| 331 | +5. **Use custom fixtures**: Create reusable fixtures for common resource patterns rather than repeating `cluster_manager.get()` calls. |
| 332 | + |
| 333 | +6. **Check resource usage**: Use `cluster_manager.get_locked_resources()` or `cluster_manager.get_used_resources()` to see what was allocated. |
| 334 | + |
| 335 | +### Example: Real-World Test Pattern |
| 336 | + |
| 337 | +```python |
| 338 | +@pytest.fixture |
| 339 | +def cluster_and_pool( |
| 340 | + cluster_manager: cluster_management.ClusterManager, |
| 341 | +) -> tuple[clusterlib.ClusterLib, str]: |
| 342 | + """Get cluster instance and select one pool.""" |
| 343 | + cluster_obj = cluster_manager.get( |
| 344 | + use_resources=[ |
| 345 | + resources_management.OneOf(resources=cluster_management.Resources.ALL_POOLS), |
| 346 | + ] |
| 347 | + ) |
| 348 | + pool_name = cluster_manager.get_used_resources( |
| 349 | + from_set=cluster_management.Resources.ALL_POOLS |
| 350 | + )[0] |
| 351 | + return cluster_obj, pool_name |
| 352 | + |
| 353 | + |
| 354 | +def test_stake_pool_rewards( |
| 355 | + cluster_and_pool: tuple[clusterlib.ClusterLib, str], |
| 356 | +): |
| 357 | + """Test staking rewards with proper resource management.""" |
| 358 | + cluster, pool_name = cluster_and_pool |
| 359 | + |
| 360 | + # Test implementation using the selected pool |
| 361 | + # Multiple tests can run concurrently, each using different pools |
| 362 | + ... |
| 363 | +``` |
| 364 | + |
| 365 | +### Troubleshooting Resource Issues |
| 366 | + |
| 367 | +**Test hangs indefinitely**: Check if all required resources are available. Use `SCHEDULING_LOG` environment variable to see cluster scheduling decisions, if available. |
| 368 | + |
| 369 | +**"Manager is already initialized" error**: Don't use multiple `cluster` fixtures in the same test. Use a single custom fixture that requests all needed resources at once. |
0 commit comments