Skip to content

Commit 9afa940

Browse files
authored
Merge pull request #1229 from ably/refactor/uts-objects-unit-into-liveobjects
refactor(uts): make :uts a shared test-infra module, move UTS suites to owning modules; objects spec-conformance
2 parents ee2e1f1 + e20bc69 commit 9afa940

86 files changed

Lines changed: 10268 additions & 5116 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/skills/uts-to-kotlin/SKILL.md

Lines changed: 79 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
---
2-
description: "Translate the UTS pseudocode test specs in a whole module directory into runnable Kotlin tests in the ably-java uts module. Takes a UTS module directory (e.g. <cloned-ably-specification-repo-path>/uts/objects), validates its structure, resolves the target ably-java module, lets you pick a tier (unit/integration/proxy) and which specs, then derives a Kotlin test per spec. Usage: /uts-to-kotlin <path-to-uts-module-directory>"
2+
description: "Translate the UTS pseudocode test specs in a whole module directory into runnable Kotlin tests in the owning ably-java module (:java for realtime/rest, :liveobjects for objects; :uts hosts the shared infra + smoke examples). Takes a UTS module directory (e.g. <cloned-ably-specification-repo-path>/uts/objects), validates its structure, resolves the target ably-java module, lets you pick a tier (unit/integration/proxy) and which specs, then derives a Kotlin test per spec. Usage: /uts-to-kotlin <path-to-uts-module-directory>"
33
allowed-tools: Bash, Read, Edit, Write, WebFetch
44
---
55

66
Translate the UTS pseudocode test specs under the **module directory** `$ARGUMENTS` into runnable Kotlin
7-
tests in the ably-java `uts` module.
7+
tests in the owning ably-java module (`:java` for realtime/rest, `:liveobjects` for objects; `:uts` hosts
8+
the shared infra + smoke examples).
89

910
`$ARGUMENTS` is a UTS *module* directory — a directory sitting directly under the spec repo's `uts/`,
1011
e.g. `<cloned-ably-specification-repo-path>/uts/objects`. Its name (`objects`, `realtime`,
@@ -46,9 +47,10 @@ python3 .claude/skills/uts-to-kotlin/scripts/resolve_uts.py "<module-dir>"
4647

4748
It prints one JSON object. **If `ok` is `false`, relay `message` to the user and stop** — error codes:
4849
`NOT_A_UTS_MODULE_PATH` (not a `.../uts/<module>` directory), `DIR_NOT_FOUND`, `NO_TIER_DIRS` (no `unit/`
49-
or `integration/`). On success it gives `sourceModule`, `mapped`, `testRoot`, `translationNotes`, and a
50+
or `integration/`). On success it gives `sourceModule`, `mapped`, `translationNotes`, and a
5051
`tiers` object with one entry per tier (`unit` / `integration` / `proxy`), each carrying `present`,
51-
`sourceDir`, `targetDir`, `package`, and `specs` (a list of `{file, className}`). Everything downstream
52+
`sourceDir`, `targetDir`, `package`, `module` (the owning Gradle module — `:java` / `:liveobjects` /
53+
`:uts`), and `specs` (a list of `{file, className}`). Everything downstream
5254
reads from this output — treat it as the single source of truth and don't recompute paths or names by hand.
5355

5456
`translationNotes` is the path to a per-module ably-js → ably-java type/interface map when the module
@@ -61,6 +63,16 @@ Phase 2** — see Step 1.
6163
The target dirs come from `uts-package-mapping.json` (alongside this skill); spec and ably-java module names
6264
don't always match (e.g. `objects``liveobjects`), which is why it's explicit.
6365

66+
Each tier's mapping value is **one repo-root-relative path** (never a machine-absolute `/Users/...` path).
67+
The resolver derives everything from it: `targetDir` is the path, `package` is the path after
68+
`src/test/kotlin/` with `/``.`, and `module` is the owning Gradle module from the path's first segment
69+
(`lib/``:java`, `liveobjects/``:liveobjects`, `uts/``:uts`). Use the resolver's `targetDir` /
70+
`package` / `module` output directly — don't recompute. The `objects` entry is **hand-maintained**: its
71+
tiers live in `:liveobjects`'s own test source set (its specs assert on `:liveobjects` internals only
72+
visible to that module's own tests) under `liveobjects/src/test/kotlin/io/ably/lib/liveobjects/uts/{unit,
73+
integration,proxy}` — a sibling layout that differs from the `--create` template below, so `--create` never
74+
regenerates it.
75+
6476
- **If `mapped` is `true`**: show the resolved `targetDir` for each present tier and ask the user to confirm.
6577
If they say the mapping is wrong, ask for the correct ably-java module base name and re-run with `--create`
6678
(below) to overwrite the entry, then re-resolve.
@@ -72,9 +84,13 @@ don't always match (e.g. `objects` → `liveobjects`), which is why it's explici
7284
python3 .claude/skills/uts-to-kotlin/scripts/resolve_uts.py "<module-dir>" --create <target>
7385
```
7486

75-
This adds `unit/<target>`, `integration/standard/<target>`, and `integration/proxy/<target>` under
76-
`packages` and re-prints the resolved output. (`<target>` must be a simple module base name — letters,
77-
digits, underscore; the script returns `BAD_TARGET_NAME` otherwise, so just ask again.)
87+
This adds full `lib/`-rooted (`:java`) paths under `packages`
88+
`lib/src/test/kotlin/io/ably/lib/uts/unit/<target>`, `.../integration/standard/<target>`, and
89+
`.../integration/proxy/<target>` — and re-prints the resolved output. **`--create` only scaffolds
90+
`:java`-hosted modules;** a module whose tiers live in another Gradle module (like `objects`
91+
`:liveobjects`, a sibling `uts/{unit,integration,proxy}` layout) still needs a hand-edit afterwards.
92+
(`<target>` must be a simple module base name — letters, digits, underscore; the script returns
93+
`BAD_TARGET_NAME` otherwise, so just ask again.)
7894

7995
## Step C — Choose the tier
8096

@@ -156,15 +172,36 @@ integration tests** section; **proxy** → the proxy subsections of the **Integr
156172
> and a file-map of every infra helper with its public surface (Appendix B). Skim it for the *why* and
157173
> the *what's available*; the per-file list below is the *what to open for exact signatures* before
158174
> writing code.
159-
160-
Infrastructure is split by tier under `uts/src/test/kotlin/io/ably/lib/uts/infra/`:
175+
>
176+
> README §9–§11's walkthroughs use the `:uts` infra smoke tests (`UnitInfraSmokeTest` /
177+
> `IntegrationInfraSmokeTest` / `ProxyInfraSmokeTest`) as examples — treat them as the **structural**
178+
> template only (client wiring, guarded awaits, teardown). They are deliberately NOT spec-derived: no
179+
> `@UTS` marker, several teaching points folded into each method. Every derived test still needs one
180+
> `@Test` with a `@UTS <id>` KDoc per spec Test ID — never copy the smokes' marker-less
181+
> multi-point-per-method shape.
182+
183+
Infrastructure lives in `:uts`'s **main** source set —
184+
`uts/src/main/kotlin/io/ably/lib/uts/infra/` — so other Gradle modules can consume it via
185+
`testImplementation(project(":uts"))`. Kotlin packages are `io.ably.lib.uts.infra.*`, so imports in
186+
generated tests are unaffected. It is split by tier:
161187

162188
- `infra/Utils.kt` — shared async helpers (`awaitState`, `awaitChannelState`, `pollUntil`), package `io.ably.lib.uts.infra`.
163189
- `infra/unit/` — unit-test mocks/factories (`ClientFactories.kt`, `MockWebSocket.kt`, `MockHttpClient.kt`, `FakeClock.kt`, `MockEvent.kt`, the `PendingConnection`/`PendingRequest` pairs, and `Utils.kt` with the `ConnectionDetails { }` builder), package `io.ably.lib.uts.infra.unit`.
164190
- `infra/integration/` + `infra/integration/proxy/` — direct-sandbox + proxy helpers (`SandboxApp.kt`, `ProxyManager.kt`, `ProxySession.kt`) — see the **Integration tests** section.
165191

166192
For a **unit** test, read all files under `infra/unit/` plus `infra/Utils.kt` before generating any code (you need exact method signatures).
167193

194+
**Module-local helpers.** Every module's tiers live in that module's own test source set (the resolver's
195+
`targetDir` / `module`), so also read any module-local helpers alongside the target — they sit in the
196+
tier's `targetDir`. For **objects/unit** (module `:liveobjects`) that's
197+
`liveobjects/src/test/kotlin/io/ably/lib/liveobjects/uts/unit/Helpers.kt` (package
198+
`io.ably.lib.liveobjects.uts.unit`) — `setupSyncedChannel`, the `build_*` message builders (typed `Wire*`
199+
constructions, no JSON/reflection), `STANDARD_POOL_OBJECTS` and the canonical serial constants. Access
200+
convention for that suite: **public-tier specs use only the public API + helpers**; only the five
201+
internal-graph specs (`internal_live_counter`, `internal_live_map`, `object_id`, `objects_pool`,
202+
`parent_references`) and documented deviations may reference `io.ably.lib.liveobjects` `internal` members —
203+
their symbol map is `references/objects-mapping.md` §17.
204+
168205
## Step 4 — Generate the Kotlin test file
169206

170207
Apply the translation rules below, then write the file.
@@ -355,6 +392,12 @@ This scaffold is for the **unit** tier — it wires the mocked transport (`infra
355392
`ConnectionDetails`). For the **integration** (direct sandbox) and **proxy** tiers, start from the
356393
**Proxy integration tests** section instead (`SandboxApp` / `ProxySession` wiring), not from this template.
357394

395+
The `package` is always the resolver's `package` for the chosen tier (Step 2), and the
396+
`io.ably.lib.uts.infra.*` imports stay valid whatever module the tier lands in. For **objects/unit** the
397+
same scaffold applies with the resolver's `io.ably.lib.liveobjects.uts.unit` package, and channel/objects
398+
setup goes through the module-local helpers (`setupSyncedChannel` etc. from the same package — see Step 3)
399+
rather than raw `MockWebSocket` wiring.
400+
358401
```kotlin
359402
package <package> // the resolver's package for the chosen tier (Step 2)
360403

@@ -409,10 +452,15 @@ class <className> {
409452

410453
## Step 5 — Compile
411454

455+
Compile the module the resolver reports for the chosen tier (its `module` field — Step A):
456+
412457
```bash
413-
./gradlew :uts:compileTestKotlin
458+
./gradlew :java:compileTestKotlin # realtime / rest tiers (module :java)
459+
./gradlew :liveobjects:compileTestKotlin # objects tiers (module :liveobjects)
414460
```
415461

462+
(`:uts` is no longer a spec-test compile target — it holds only the shared infra + smoke tests.)
463+
416464
Fix any compilation errors and recompile until clean. Common issues:
417465
- Missing imports
418466
- Method names differ from what you read in the mock files (use the exact names from Step 3)
@@ -430,18 +478,24 @@ every failure via the decision tree below. Each test must end in exactly one of
430478
- a documented **UTS spec error****fails fast** (the spec is wrong; fix belongs in the spec). This is the
431479
one acceptable red.
432480

433-
Use the per-tier task that matches the chosen tier (both are registered in `uts/build.gradle.kts`), and the
481+
Use the per-tier task of the module the resolver reports for the tier (its `module` field), and the
434482
resolver's `package` + the spec's `className` for the `--tests` filter:
435483

436484
```bash
437-
# unit tier → io.ably.lib.uts.unit.*
438-
./gradlew :uts:runUtsUnitTests --tests "<package>.<className>"
485+
# realtime / rest unit (module :java) → io.ably.lib.uts.unit.*
486+
./gradlew :java:runUtsUnitTests --tests "<package>.<className>"
487+
488+
# realtime / rest integration + proxy (module :java) → io.ably.lib.uts.integration.*
489+
./gradlew :java:runUtsIntegrationTests --tests "<package>.<className>"
490+
491+
# objects unit (module :liveobjects) → io.ably.lib.liveobjects.uts.unit.*
492+
./gradlew :liveobjects:runLiveObjectsUnitTests --tests "<package>.<className>"
439493

440-
# integration / proxy → io.ably.lib.uts.integration.*
441-
./gradlew :uts:runUtsIntegrationTests --tests "<package>.<className>"
494+
# objects integration + proxy (module :liveobjects) → io.ably.lib.liveobjects.uts.{integration,proxy}.*
495+
./gradlew :liveobjects:runLiveObjectsIntegrationTests --tests "<package>.<className>"
442496
```
443497

444-
(`./gradlew :uts:test` still runs all tiers — unit, standard, and proxy.)
498+
(`./gradlew :uts:test` now runs only the infra smoke tests — see `uts/README.md`.)
445499

446500
Handle test failures using this decision tree (the **Required reading** doc you fetched up front has the full detail):
447501

@@ -454,15 +508,15 @@ Test fails
454508
| YES
455509
| +-- Does test accurately translate the UTS spec?
456510
| NO → fix the test (no deviation entry needed)
457-
| YES → SDK deviation — adapt test, record in deviations file
511+
| YES → SDK deviation — env-gated skip or adapted assertion (below); record in deviations file
458512
```
459513

460514
### Test patterns for a diagnosed failure
461515

462516
Two patterns are for an **SDK deviation** (both write the spec-correct assertions); the third,
463517
**spec-error fail-fast**, is for a **UTS spec error** and is not a deviation.
464518

465-
**Env-gated skip (preferred)** — test contains spec-correct assertions but is skipped by default:
519+
**Env-gated skip** (preferred *for a deviation you expect to be fixed*) — test contains spec-correct assertions but is skipped by default:
466520

467521
```kotlin
468522
/**
@@ -477,7 +531,7 @@ fun `RSA4c2 - callback error connecting disconnected`() = runTest {
477531
}
478532
```
479533

480-
**Adapted assertion**when you still want to assert on the SDK's actual behaviour to prevent regressions:
534+
**Adapted assertion** — assert the SDK's actual behaviour to prevent regressions. **Prefer this over an env-gated skip when the divergence is permanent or intentional** (a running test guards regressions; a permanently-skipped spec assertion verifies nothing):
481535

482536
```kotlin
483537
// DEVIATION: spec requires error code 40106, SDK returns 40160 — see deviations.md
@@ -502,8 +556,10 @@ fun `RTLC7c2 - LOCAL source does not write siteTimeserials`() = runTest {
502556

503557
### Deviations file
504558

505-
Append to `uts/src/test/kotlin/io/ably/lib/uts/deviations.md`, using the manual's **Recording deviations**
506-
entry format and sections. The ably-java-specific mapping: a **UTS Spec Error** (test fails fast — fix in
559+
Append to the deviations file that belongs to the tier's module:
560+
`lib/src/test/kotlin/io/ably/lib/uts/deviations.md` for realtime/rest tiers (module `:java`), or
561+
`liveobjects/src/test/kotlin/io/ably/lib/liveobjects/uts/deviations.md` for objects/unit. Use the manual's
562+
**Recording deviations** entry format and sections. The ably-java-specific mapping: a **UTS Spec Error** (test fails fast — fix in
507563
the spec) goes under the manual's *UTS Spec Errors* section; an **SDK deviation** (env-gated/adapted — fix
508564
in the SDK) goes under *Failing Tests* / *Adapted Tests*.
509565

@@ -589,7 +645,7 @@ For each test case, verify:
589645
Deviations are discovered by running, so this check applies in evaluate mode. For any place where the
590646
generated test diverges from the spec pseudocode (adapted assertion, env-gated skip, or omitted step):
591647
- [ ] A `// DEVIATION:` comment explains why
592-
- [ ] The deviation is recorded in `uts/src/test/kotlin/io/ably/lib/uts/deviations.md`
648+
- [ ] The deviation is recorded in `lib/src/test/kotlin/io/ably/lib/uts/deviations.md`
593649

594650
If you find gaps during this review, fix them, then **re-run the audit script** until `missingInKotlin` /
595651
`orphanInKotlin` are empty and every `perTest` entry reconciles, and re-run Step 5 (compile) — and, in
@@ -668,7 +724,7 @@ Use generous timeouts (10–30s) — real network is involved. Everything else i
668724

669725
### Infrastructure
670726

671-
Three helpers live under `uts/src/test/kotlin/io/ably/lib/uts/infra/integration/`. **Read the ones your tier uses before translating an integration spec** — they hold the exact method signatures. `SandboxApp` serves **both** tiers; `ProxyManager` and `ProxySession` are **proxy-only**.
727+
Three helpers live under `uts/src/main/kotlin/io/ably/lib/uts/infra/integration/`. **Read the ones your tier uses before translating an integration spec** — they hold the exact method signatures. `SandboxApp` serves **both** tiers; `ProxyManager` and `ProxySession` are **proxy-only**.
672728

673729
- **`ProxyManager`** (`infra/integration/proxy/ProxyManager.kt`, package `io.ably.lib.uts.infra.integration.proxy`) — downloads/starts the shared `uts-proxy` process. Call `ProxyManager.ensureProxy()` once per suite in setup.
674730
- **`ProxySession`** (`infra/integration/proxy/ProxySession.kt`, same package) — one programmable session wrapping the proxy control API; also defines the `connectThroughProxy` extension and the rule-builder helpers.

0 commit comments

Comments
 (0)