generator: emit E2E tests for service-block methods — closeout of hero_rpc#131 #134

Merged
timur merged 2 commits from service-method-test-codegen into development 2026-05-22 14:36:51 +00:00
Owner

Summary

  • tests_emit.rs now walks schema.services alongside the existing rootobject walk and emits tests/generated/<service_snake>_e2e.rs per service block (methods-only domains were previously short-circuited out).
  • One #[tokio::test] <svc>_<method>_round_trip per method. Each test spins up the service via spin_up_service(), builds the typed SDK call with synthesised default arguments, and asserts Ok(_) — proves the wire path round-trips and the handler is wired.
  • Service-method <svc>_ Rust ident is built from service_name.to_ascii_lowercase() to mirror the rpc2 trait emitter, so the generated tests resolve the right SDK methods.
  • sync_tests_cargo_toml now syncs both rootobject and service-block [[test]] entries — rootobjects first (snake-sorted, each with its optional _find_e2e sibling), then service entries.
  • Methods whose parameters can't be defaulted emit a // SKIP: <reason> stub + an eprintln! warning at codegen time rather than failing the whole build.
  • Drive-by fix: find_tests_emit.rs was still emitting the pre-#124 let (svc, _data_dir) + svc.shutdown().await shape; aligned with the post-#124 let svc = ... + Drop-based cleanup so regenerated _find_e2e.rs files compile against the current lab subprocess driver.

Audit before/after

  • Before: a methods-only schema (service Greeter { greet, echo, add, concat, info }) produced zero generated tests. A hybrid schema (ServiceDefinition [rootobject] + service ServiceCatalog { list, get_by_name, list_by_interface, bootstrap, refactor, check, verify }) produced only service_definition_e2e.rs; the 7 service methods got zero coverage.
  • After (validated against hero_service template, see hero_service PR): methods-only greeter schema → 5 generated tests (greeter_e2e.rs). Hybrid catalog schema → service_definition_e2e.rs + service_definition_find_e2e.rs (rootobject CRUD + indexed find) + servicecatalog_e2e.rs with 9 tests (one per method, including the new count / clear additions). All compile and link.

Decisions taken without confirmation

  • Lowercased service ident, not snake_cased. The rpc2 trait emitter uses service_name.to_ascii_lowercase() (so ServiceCatalog.list → SDK method servicecatalog_list, not service_catalog_list). The test emitter mirrors that exactly — pinned with a unit test (service_lowercasing_matches_rpc2_trait_emitter) so a future emitter rename can't silently desync the two sides.
  • Round-trip-only assertion. Generated tests assert result.is_ok() rather than pinning a specific return value. Concrete return values are user-impl-dependent and richer per-method semantics belong in user-owned tests under tests/ siblings — the codegen's job is to prove the wire path and handler are wired.
  • // SKIP rather than build failure for methods whose params can't be defaulted. None of the canary methods (in hero_service: 14 methods across Greeter + ServiceCatalog) hit the skip path, but the safety net is there for future schemas.

Test plan

  • cargo test -p hero_rpc_generator --lib — 150 tests pass (145 pre-existing + 5 new unit tests covering the methods-only emitter, list/option/enum defaulting, and the Cargo.toml entries order).
  • cargo build --workspace clean.
  • Validated against the hero_service template (separate PR linked below): all 13 generated test files compile and link; the new greeter_e2e.rs (5 tests) + extended servicecatalog_e2e.rs (9 tests, was 7) come up alongside the existing rootobject E2Es. Runtime exercise is gated on the lab service <name> --start --ephemeral --json driver landing on the host's lab binary (out of scope here).

Closes #131
Unblocks (META) #132 — six legacy openrpc_client! services with no rootobjects can now migrate onto oschema codegen.

hero_service-side validation: https://forge.ourworld.tf/lhumina_code/hero_service/pulls (PR opened in companion repo)

## Summary - `tests_emit.rs` now walks `schema.services` alongside the existing rootobject walk and emits `tests/generated/<service_snake>_e2e.rs` per service block (methods-only domains were previously short-circuited out). - One `#[tokio::test] <svc>_<method>_round_trip` per method. Each test spins up the service via `spin_up_service()`, builds the typed SDK call with synthesised default arguments, and asserts `Ok(_)` — proves the wire path round-trips and the handler is wired. - Service-method `<svc>_` Rust ident is built from `service_name.to_ascii_lowercase()` to mirror the rpc2 trait emitter, so the generated tests resolve the right SDK methods. - `sync_tests_cargo_toml` now syncs both rootobject and service-block `[[test]]` entries — rootobjects first (snake-sorted, each with its optional `_find_e2e` sibling), then service entries. - Methods whose parameters can't be defaulted emit a `// SKIP: <reason>` stub + an `eprintln!` warning at codegen time rather than failing the whole build. - Drive-by fix: `find_tests_emit.rs` was still emitting the pre-#124 `let (svc, _data_dir)` + `svc.shutdown().await` shape; aligned with the post-#124 `let svc = ...` + Drop-based cleanup so regenerated `_find_e2e.rs` files compile against the current lab subprocess driver. ## Audit before/after - **Before:** a methods-only schema (`service Greeter { greet, echo, add, concat, info }`) produced **zero** generated tests. A hybrid schema (`ServiceDefinition [rootobject]` + `service ServiceCatalog { list, get_by_name, list_by_interface, bootstrap, refactor, check, verify }`) produced **only** `service_definition_e2e.rs`; the 7 service methods got zero coverage. - **After (validated against hero_service template, see hero_service PR):** methods-only `greeter` schema → 5 generated tests (`greeter_e2e.rs`). Hybrid `catalog` schema → `service_definition_e2e.rs` + `service_definition_find_e2e.rs` (rootobject CRUD + indexed find) + `servicecatalog_e2e.rs` with 9 tests (one per method, including the new `count` / `clear` additions). All compile and link. ## Decisions taken without confirmation - **Lowercased service ident, not snake_cased.** The rpc2 trait emitter uses `service_name.to_ascii_lowercase()` (so `ServiceCatalog.list` → SDK method `servicecatalog_list`, not `service_catalog_list`). The test emitter mirrors that exactly — pinned with a unit test (`service_lowercasing_matches_rpc2_trait_emitter`) so a future emitter rename can't silently desync the two sides. - **Round-trip-only assertion.** Generated tests assert `result.is_ok()` rather than pinning a specific return value. Concrete return values are user-impl-dependent and richer per-method semantics belong in user-owned tests under `tests/` siblings — the codegen's job is to prove the wire path and handler are wired. - **`// SKIP` rather than build failure** for methods whose params can't be defaulted. None of the canary methods (in hero_service: 14 methods across `Greeter` + `ServiceCatalog`) hit the skip path, but the safety net is there for future schemas. ## Test plan - `cargo test -p hero_rpc_generator --lib` — 150 tests pass (145 pre-existing + 5 new unit tests covering the methods-only emitter, list/option/enum defaulting, and the Cargo.toml entries order). - `cargo build --workspace` clean. - Validated against the hero_service template (separate PR linked below): all 13 generated test files compile and link; the new `greeter_e2e.rs` (5 tests) + extended `servicecatalog_e2e.rs` (9 tests, was 7) come up alongside the existing rootobject E2Es. Runtime exercise is gated on the `lab service <name> --start --ephemeral --json` driver landing on the host's `lab` binary (out of scope here). Closes https://forge.ourworld.tf/lhumina_code/hero_rpc/issues/131 Unblocks (META) https://forge.ourworld.tf/lhumina_code/hero_rpc/issues/132 — six legacy `openrpc_client!` services with no rootobjects can now migrate onto oschema codegen. hero_service-side validation: https://forge.ourworld.tf/lhumina_code/hero_service/pulls (PR opened in companion repo)
The workspace-root tests emitter (`OschemaBuilder::emit_tests_module`)
previously short-circuited on domains with no rootobjects, so any
schema declaring only `service Foo { ... }` blocks (or a hybrid
domain's service methods alongside rootobject CRUD) ended up with
zero generated coverage.

Walk `schema.services` alongside the rootobject walk and emit
`tests/generated/<service_snake>_e2e.rs` per service block. One
`#[tokio::test] <svc_snake>_<method_snake>_round_trip` per method;
each test spins up the service via the existing `spin_up_service()`
helper, builds the typed SDK call with synthesised defaults, and
asserts the call returns Ok(_) — proves the wire path round-trips
and the handler is wired. Methods whose parameters can't be defaulted
emit a `// SKIP` stub + an eprintln warning rather than failing the
whole build.

The `<svc>_` Rust ident is built from `service_name.to_ascii_lowercase()`
to mirror the rpc2 trait emitter (`emit/rust_rpc2.rs::render_trait_method`),
so the generated tests resolve the right SDK methods.

`sync_tests_cargo_toml` now syncs both rootobject and service-block
[[test]] entries — rootobjects first (snake-sorted, each with its
optional `_find_e2e` sibling), then service entries (snake-sorted).
First-scaffold output stays byte-identical for rootobject-only
templates; the build.rs path picks up service additions on the next
regen.
generator(find_tests_emit): align spin_up + cleanup with #124 driver
Some checks failed
Test / test (push) Failing after 2m32s
Test / test (pull_request) Failing after 2m32s
562f1991cb
The find-test emitter still wrote `let (svc, _data_dir) = spin_up_service()`
and an explicit `svc.shutdown().await` — the shape that predates
hero_rpc#124's lab subprocess driver. The post-#124 `ServiceHandle`
returns a single value and tears the child down through `Drop`, so
those generated files failed to compile on any consumer that
regenerated against current development.

Bring the find-test emitter in line with the rootobject CRUD and
service-method emitters: `let svc = spin_up_service()` and a
trailing `// svc drops here` comment. No functional change on the
runtime path — the Drop guard was already doing the cleanup work.
timur merged commit 2b2aeb6ce9 into development 2026-05-22 14:36:51 +00:00
timur deleted branch service-method-test-codegen 2026-05-22 14:36:51 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lhumina_code/hero_rpc!134
No description provided.