Skip to content

WIP: test(docs): run every tutorial end to end against a live backend - #63

Open
samuelhel wants to merge 3 commits into
masterfrom
test/doc-tutorial-tests
Open

WIP: test(docs): run every tutorial end to end against a live backend#63
samuelhel wants to merge 3 commits into
masterfrom
test/doc-tutorial-tests

Conversation

@samuelhel

Copy link
Copy Markdown
Contributor

The doc pages had drifted out of sync with SDK 0.2.0 and nothing caught it, because nothing ran them. This adds a suite that does: it assembles each page's own fenced code into a program, runs it against a real stack, and then asks the backend whether the page's promise came true.

The code always comes from the page, never a copy, so there is no second copy to drift and nowhere to "fix" a red test except the documentation.

Covers all 71 pages that contain runnable code (Python; Java and Rust runners are implemented and selectable with --langs). 205 of 232 checks pass; the 27 failures are real defects, each verified by reproducing it with plain SDK calls outside the harness before it was believed.

What makes it hold up:

  • Block-count pinning. Plans address blocks by index, so an edited page fails the plan instead of silently re-pointing it at different code.
  • Bounded-run substitutions that must keep matching, so a rewritten loop fails loudly rather than hanging CI.
  • A coverage gate: a new page with runnable code is red until someone writes a plan or records why it cannot run. UNTRIAGED.toml is empty and should stay so.
  • Assertions on the backend, not on exit codes. Several tutorials catch their own exceptions by design, so exiting 0 proves nothing about whether data landed.
  • Sweeps before and after each run, so the suite is repeatable rather than passing only against a virgin backend.
  • 18 harness self-tests (test_harness.py) that prove those guards actually fire. They need no backend, so CI can run them anywhere.

Live listeners are bounded and fed real traffic rather than skipped, graph reads are settled before assertions, and pages needing credentials or optional packages skip with the reason instead of failing.

Signed-off-by: samuel samuel@intellistream.ai

samuelhel and others added 2 commits September 2, 2026 13:36
The doc pages had drifted out of sync with SDK 0.2.0 and nothing caught it,
because nothing ran them. This adds a suite that does: it assembles each page's
own fenced code into a program, runs it against a real stack, and then asks the
backend whether the page's promise came true.

The code always comes from the page, never a copy, so there is no second copy to
drift and nowhere to "fix" a red test except the documentation.

Covers all 71 pages that contain runnable code (Python; Java and Rust runners
are implemented and selectable with --langs). 205 of 232 checks pass; the 27
failures are real defects, each verified by reproducing it with plain SDK calls
outside the harness before it was believed.

What makes it hold up:

- Block-count pinning. Plans address blocks by index, so an edited page fails
  the plan instead of silently re-pointing it at different code.
- Bounded-run substitutions that must keep matching, so a rewritten loop fails
  loudly rather than hanging CI.
- A coverage gate: a new page with runnable code is red until someone writes a
  plan or records why it cannot run. UNTRIAGED.toml is empty and should stay so.
- Assertions on the backend, not on exit codes. Several tutorials catch their own
  exceptions by design, so exiting 0 proves nothing about whether data landed.
- Sweeps before and after each run, so the suite is repeatable rather than
  passing only against a virgin backend.
- 18 harness self-tests (test_harness.py) that prove those guards actually fire.
  They need no backend, so CI can run them anywhere.

Live listeners are bounded and fed real traffic rather than skipped, graph reads
are settled before assertions, and pages needing credentials or optional packages
skip with the reason instead of failing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoqhJA3haV5BqiqHvhPBZf
Adds the workflow and the two backend-free tiers it leans on, so the suite is
useful before anyone has stood up a stack for CI.

- test_api_surface.py: checks every SDK name and service method the docs use
  against the built SDK. Needs a build, not a backend. This is the tier that would
  have caught the 0.2.0 rename and the removal of BasicEventFilter in the pull
  request that did them, rather than months later. It reads the service classes off
  a locally-constructed client, since they are not exported at module level, and it
  treats a namespace package as missing — a stale empty `datahub_sdk/` directory
  answers `find_spec` while importing nothing, which is precisely how the rename
  stayed invisible.

- doc-tutorials.yml: three jobs. `structure` needs only pytest and runs in under a
  second. `api-surface` builds the SDK from the ref under test. `tutorials` runs the
  live suite when DOCTEST_BASE_URL is set, and posts a notice instead of failing
  when it is not — a missing backend is not a broken tutorial.

The workflow accepts `repository_dispatch` (type `sdk-updated`) carrying an
`sdk_ref`, so the SDK repo can have its changes checked against the docs.

Also fixes run.sh, which prepended the whole suite even when given a specific file.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoqhJA3haV5BqiqHvhPBZf
@samuelhel
samuelhel requested a review from olavgg September 2, 2026 13:22
Review pass over the suite. Three kinds of repetition, none of them harmless:

- conftest's seeding fixture had its own copy of the walk from plan to runnable
  program — resolve the chain, read the blocks, compose. It had already drifted:
  only the tutorial path validated the plan against the page it was about to run,
  so a stale selection in a seeding page would have gone unnoticed. Both now go
  through scenario.build(), which validates once and is the only place that knows
  how a plan becomes a program.

- The same two calls answered "did the page do what it promised" in two places.
  Now backend.unmet_expectations().

- The two eventual-consistency waits in tutorial_support were the same poll loop
  written twice.

And 444 lines out of the plans. Sixty-five carried

    [java]
    disabled = "Java scenario not written yet."

which means precisely what omitting the section already means — Plan.lang() falls
back to a disabled scenario — while burying the `disabled` reasons that are
specific. The [blocks] table still pins each language's fence count, so nothing is
lost. Twenty-seven repeated a three-line explanation of what a Recorder is, which
belongs in the skill and is there.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoqhJA3haV5BqiqHvhPBZf
@samuelhel samuelhel changed the title test(docs): run every tutorial end to end against a live backend WIP: test(docs): run every tutorial end to end against a live backend Sep 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant