Skip to content

Commit 2d7323a

Browse files
gnd(test): Add mock-based subgraph test runner (#6361)
* gnd(test): Migrate from monolithic test.rs to modular test/ structure Replaces monolithic gnd/src/commands/test.rs with organized test/ directory containing: - mod.rs: Main entry point and test orchestration - runner.rs: Test execution and infrastructure setup - assertion.rs: GraphQL assertion logic - block_stream.rs: Mock block stream implementation - noop.rs: Stub trait implementations - schema.rs: JSON schema and test types - trigger.rs: ABI encoding for test triggers - output.rs: Test result formatting - mock_chain.rs: Block pointer helpers Updates main.rs to make Test command async (.await). Adds dependencies for test runner (graph-chain-ethereum, graph-graphql, graph-store-postgres). * gnd(test): Add remaining test module files Adds supporting modules for test infrastructure: - mock_chain: Helpers for block pointer construction - schema: JSON schema types and parsing - output: Console output formatting - trigger: ABI encoding of test triggers * gnd(test): Update module structure with new submodules Adds module declarations for refactored components: - mod assertion - mod block_stream - mod noop Updates module documentation to reflect the new structure and improved separation of concerns. * gnd(test): Refactor runner.rs for readability - remove extracted code Removes ~500 lines from runner.rs by delegating to new focused modules: - block_stream: Mock block delivery infrastructure - noop: Stub trait implementations - assertion: GraphQL assertion logic runner.rs now focuses exclusively on test orchestration: - setup_stores: Initialize PostgreSQL and chain store - setup_chain: Construct mock Ethereum chain - setup_context: Wire up graph-node components - wait_for_sync: Poll store until indexing completes Reduced from 1198 to 729 lines (39% reduction). Improves readability by separating concerns. * gnd(test): Extract GraphQL assertion logic to dedicated module Moves assertion execution to gnd/src/commands/test/assertion.rs: - run_assertions: Execute all test assertions - run_single_assertion: Execute and compare a single query - r_value_to_json: Convert graph-node's r::Value to serde_json - json_equal: Compare JSON with string-vs-number coercion Makes TestContext fields pub(super) to allow assertion module access. * gnd(test): Extract noop/stub trait implementations to dedicated module Moves unused adapter stubs to gnd/src/commands/test/noop.rs: - StaticBlockRefetcher - NoopRuntimeAdapter / NoopRuntimeAdapterBuilder - NoopAdapterSelector - NoopTriggersAdapter These satisfy Chain constructor trait bounds but are never called during normal test execution since triggers are pre-built and host functions are not available in mocks. * gnd(test): Add eth_call mocking and refactor test output * gnd(test): Add EIP-1559 base fee support and refactor block creation - Add baseFeePerGas field to TestBlock schema - Parse and apply base fee when creating test blocks - Replace graph-node helper functions with direct alloy types - Extract dummy_transaction creation into dedicated function - Use alloy Block::empty() constructor for cleaner block creation * gnd(test): Simplify test schema by auto-injecting block triggers - Rename 'triggers' field to 'events' in TestBlock - Remove TestTrigger enum and BlockTrigger type - Keep LogEvent as the only event type users specify - Auto-inject Start and End block triggers for every block - This ensures block handlers fire correctly without explicit config - Update docs to reflect that block triggers are automatic * gnd(test): Add support for manifests with startBlock > 0 - Extract min startBlock from manifest in extract_start_block_from_manifest() - Use startBlock as default test block numbering base - Create start_block_override to bypass on-chain validation - Pass override through setup_context() to SubgraphRegistrar - This allows testing subgraphs that specify startBlock without needing a real chain * gnd(test): Fix issues after rebase Signed-off-by: Maksim Dimitrov <dimitrov.maksim@gmail.com> * gnd(test): Fix matchstick path Signed-off-by: Maksim Dimitrov <dimitrov.maksim@gmail.com> * gnd(test): Reuse load_manifest() Signed-off-by: Maksim Dimitrov <dimitrov.maksim@gmail.com> * gnd(test): Add README, add important inline notes Signed-off-by: Maksim Dimitrov <dimitrov.maksim@gmail.com> * gnd: Fix missing fields, update readme Signed-off-by: Maksim Dimitrov <dimitrov.maksim@gmail.com> * gnd(test): Refactor CLI to accept test files/dirs as positional args - Replace positional `manifest` arg with `--manifest` / `-m` flag (default: subgraph.yaml) - Add positional `tests` args accepting file or directory - When no args given, default to scanning `tests/` - Bare filenames resolve to `tests/<filename>` for convenience (e.g., `gnd test foo.json` → `tests/foo.json`) - Remove `--test-dir` flag (replaced by positional args) - Update README with new usage examples Signed-off-by: Maksim Dimitrov <dimitrov.maksim@gmail.com> * gnd(test): Fix diff colors — red for expected, green for actual * gnd(test): Support Array, FixedArray, and Tuple in Solidity type conversion - Add Array, FixedArray, Tuple branches to json_to_sol_value - Fix strip_prefix("0x") usage (trim_start_matches incorrectly strips repeated 0s) - Fix i64::MIN two's complement handling via I256::into_raw() - Add block number overflow check against i32::MAX * gnd(test): Recurse into subdirectories when discovering test files - discover_test_files now walks subdirectories recursively - Skip entries starting with non-alphanumeric chars (.hidden, _fixture) * gnd(test): Validate eth_call parameter and return value counts Add upfront arity checks in encode_function_call and encode_return_value so mismatches produce clear errors instead of silently truncating. Also simplify redundant .with_context() wrappers in populate_single_call. * gnd(test): Ensure subgraph cleanup and remove FilterStoreEventEndedDrain - Remove FilterStoreEventEndedDrain log filter and unused logger field - Always call stop_subgraph after test, even on error - Warn when a test has blocks but no assertions - Add block number overflow check against i32::MAX * gnd(test): Validate datasource name to prevent shell injection in Docker mode Add alphanumeric/hyphen/underscore validation before interpolating the datasource name into Docker's sh -c command. Also simplify redundant .with_context() wrappers. * gnd(test): Replace unwrap/unimplemented with proper error handling - block_stream: .unwrap() -> .expect() on mutex lock - noop: unimplemented!() -> Err(anyhow!(...)) - mod: Fail early with bail! on missing test file * gnd(test): Fix ServerAddress initialization Signed-off-by: Maksim Dimitrov <dimitrov.maksim@gmail.com> * gnd(test): Add verbose logging flag and extract manifest loading Add `-v` / `--verbose` flag with count semantics for controlling graph-node log verbosity during tests (-v=info, -vv=debug, -vvv=trace). GRAPH_LOG env var always takes precedence when set. Extract manifest loading into `ManifestInfo` struct loaded once per run, avoiding redundant parsing across tests. Thread a single logger through setup_stores/setup_chain instead of creating ad-hoc loggers. * gnd(test): Add integration test harness with pgtemp fixes Add gnd_test integration test suite with fixture subgraph and test cases for blocks, transfers, templates, and expected failures. Fix pgtemp Unix socket path overflow on macOS by overriding unix_socket_directories to /tmp. Reduce default pool_size to 2. * gnd(compiler): Look for asc binary in local if global does not exist. * gnd(test): Use --postgres-url for tests * gnd(test): Fix test cleanup for --postgres-url * gnd(test): Replace TempPgHandle with TestDatabase enum Makes the two database lifecycle paths explicit and self-documenting. `TestDatabase::Temporary` vs `TestDatabase::Persistent` (--postgres-url, needs cleanup) replaces the opaque `Option<TempPgHandle>`. Cleanup in `setup_stores` is now gated on `db.needs_cleanup()` instead of running unconditionally. * gnd(test): Clean up persistent DB state after each test When using --postgres-url, cleanup only ran at the start of each test (to remove stale state from a previous run). The last test's deployment was left in the DB, which broke unrelated unit test suites calling remove_all_subgraphs_for_test_use_only() — they don't set GRAPH_NODE_DISABLE_DEPLOYMENT_HASH_VALIDATION, so parsing the file-path deployment hash fails. Add post-test cleanup for persistent databases, mirroring the pre-test cleanup. Pre-test handles interrupted runs; post-test handles the normal case. Together they keep the DB clean regardless of how the run ends. * gnd(test): Use Qm-prefixed SHA-1 hash as deployment hash Each test run now computes a fake-but-valid DeploymentHash as "Qm" + hex(sha1(manifest_path + seed)) where seed is the Unix epoch in milliseconds. This: - Passes DeploymentHash validation without bypassing it - Produces a unique hash and subgraph name per run, so sequential runs never conflict in the store - Removes the pre-test cleanup (it would never match a fresh hash) - Registers the hash as a FileLinkResolver alias so clone_for_manifest can resolve it to the real manifest path - Reuses the existing sha1 dep — no new dependencies * gnd(test): Fix stale docs after pre-test cleanup removal * gnd(test): Rename and move README into docs/ * gnd(test): Simplify TestResult, remove baseFeePerGas, fix timestamp default - Flatten TestResult from enum to struct (handler_error + assertions fields) - Remove AssertionOutcome/TestResult accessor methods; use public fields directly - Remove baseFeePerGas from test block format (YAGNI) - Change default block timestamp from number * 12 to number (chain-agnostic, avoids future timestamps on high-block-number chains like Arbitrum) * gnd(test): Move docs to correct location under gnd/docs/ * gnd(test): Fix fixture contract address Update the contract address in the fixture subgraph manifest and transfer.json test from the old placeholder address to the correct one used by the test assertions. * gnd(test): Fix AnyTxEnvelope and AnyHeader types in trigger.rs dummy_transaction was returning Transaction<TxEnvelope> instead of the required AnyTransaction (Transaction<AnyTxEnvelope>). Wrap the envelope in AnyTxEnvelope::Ethereum(...) to match the expected type. Also wrap ConsensusHeader in AnyHeader::from(...) when constructing the block Header, producing Header<AnyHeader> as required by LightEthereumBlock::new. * gnd(test): Address PR review comments - Add -s short flag for --skip-build - Print deprecation warning when --matchstick is used; update README - Replace npm with pnpm in integration test setup - Replace hand-rolled r_value_to_json with serde_json::to_value via r::Value's built-in Serialize impl (also fixes Timestamp serialization) - Make find_asc_binary pub and re-export from compiler module; reuse it in gnd_test.rs instead of duplicating the lookup logic - Skip DB cleanup on test failure for persistent databases and print the connection URL so the data can be inspected * gnd(test): Document lowercase hex requirement for assertions graph-node returns all hex-encoded values (addresses, tx hashes, bytes) in lowercase. Add a note in the Assertions section's comparison behavior table and in Tips & Best Practices. Event inputs are normalised automatically and can be mixed case. Also fix the transfer.json fixture assertion to use lowercase token ID. --------- Signed-off-by: Maksim Dimitrov <dimitrov.maksim@gmail.com>
1 parent db95c7d commit 2d7323a

36 files changed

+4708
-291
lines changed

Cargo.lock

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

gnd/Cargo.toml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,22 @@ path = "tests/cli_commands.rs"
1515
name = "codegen_verification"
1616
path = "tests/codegen_verification.rs"
1717

18+
[[test]]
19+
name = "gnd_test"
20+
path = "tests/gnd_test.rs"
21+
1822
[dependencies]
1923
# Core graph dependencies
2024
graph = { path = "../graph" }
2125
graph-chain-ethereum = { path = "../chain/ethereum" }
2226
graph-core = { path = "../core" }
2327
graph-node = { path = "../node" }
28+
graph-graphql = { path = "../graphql" }
29+
graph-store-postgres = { path = "../store/postgres" }
30+
31+
# Test command dependencies
32+
hex = "0.4"
33+
async-trait = { workspace = true }
2434

2535
# Direct dependencies from current dev.rs
2636
anyhow = { workspace = true }
@@ -49,6 +59,7 @@ thiserror = { workspace = true }
4959
# Console output
5060
indicatif = "0.18"
5161
console = "0.16"
62+
similar = "2"
5263

5364
# Code generation
5465
graphql-tools = { workspace = true }
@@ -78,4 +89,3 @@ pgtemp = { git = "https://github.com/graphprotocol/pgtemp", branch = "initdb-arg
7889
[dev-dependencies]
7990
tempfile = "3"
8091
walkdir = "2"
81-
similar = "2"

gnd/README.md

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -301,25 +301,47 @@ Keys are stored in `~/.graph-cli.json`.
301301

302302
### `gnd test`
303303

304-
Run Matchstick tests for the subgraph.
304+
Run subgraph tests.
305305

306306
```bash
307-
gnd test [DATASOURCE]
307+
gnd test [TEST_FILES...]
308308
```
309309

310310
**Arguments:**
311-
- `DATASOURCE`: Specific data source to test (optional)
311+
- `PATHS`: Test JSON files or directories to scan. Defaults to `tests/` when nothing is specified.
312312

313313
**Flags:**
314314

315315
| Flag | Short | Description |
316316
|------|-------|-------------|
317-
| `--coverage` | `-c` | Run with coverage reporting |
318-
| `--docker` | `-d` | Run in Docker container |
319-
| `--force` | `-f` | Force redownload of Matchstick binary |
320-
| `--logs` | `-l` | Show debug logs |
321-
| `--recompile` | `-r` | Force recompilation before testing |
322-
| `--version` | `-v` | Matchstick version to use |
317+
| `--manifest` | `-m` | Path to subgraph manifest (default: `subgraph.yaml`) |
318+
| `--skip-build` | `-s` | Skip building the subgraph before testing |
319+
| `--postgres-url` | | PostgreSQL connection URL (env: `POSTGRES_URL`) |
320+
| `--matchstick` | | Use legacy Matchstick runner (**deprecated** — migrate to JSON-based tests) |
321+
| `--docker` | `-d` | Run Matchstick in Docker (requires `--matchstick`) |
322+
| `--coverage` | `-c` | Run with coverage reporting (requires `--matchstick`) |
323+
| `--recompile` | `-r` | Force recompilation (requires `--matchstick`) |
324+
| `--force` | `-f` | Force redownload of Matchstick binary (requires `--matchstick`) |
325+
326+
**Examples:**
327+
328+
```bash
329+
# Run all tests in tests/ directory (default)
330+
gnd test
331+
332+
# Run specific test files
333+
gnd test transfer.json approval.json
334+
gnd test tests/transfer.json
335+
336+
# Scan a custom directory
337+
gnd test my-tests/
338+
339+
# Use a different manifest
340+
gnd test -m subgraph.staging.yaml tests/transfer.json
341+
342+
# Skip automatic build
343+
gnd test -s
344+
```
323345

324346
### `gnd clean`
325347

0 commit comments

Comments
 (0)