Skip to content

feat(context): add gen_test_case_guid for globally unique test case ids - #1166

Open
max-trunk wants to merge 4 commits into
mainfrom
max/gen-test-case-guid
Open

feat(context): add gen_test_case_guid for globally unique test case ids#1166
max-trunk wants to merge 4 commits into
mainfrom
max/gen-test-case-guid

Conversation

@max-trunk

Copy link
Copy Markdown
Contributor

Summary

Adds gen_test_case_guid to the context crate: a single, globally unique id for a test case in a test collection, derived deterministically from the ids that already identify it.

Why

A test case id on its own does not identify a test case within a collection:

  • ids are generated from framework-internal values (an xcresult identifier URL, an RSpec example id, a file/classname/name triple), so they are opaque to anything that did not generate them;
  • a collection spans repos, and --no-repo deliberately makes the same test share one id across them.

So the real identity is the (test_collection_id, repo_id, test_case_id) tuple. Anything that needs to name one test case with one value — a link, a request path, a webhook payload — has nothing to use today.

What it does

pub fn gen_test_case_guid(test_collection_id: Uuid, repo_id: Uuid, test_case_id: Uuid) -> Uuid

Hashes the whole tuple, so the result is unique by construction and inherits the tuple's semantics rather than re-deciding them — including --no-repo, where the nil repo id collapses to one guid per collection, which is the point of that flag.

The hash contract is frozen:

  1. the three ids as canonical lowercase hyphenated text,
  2. joined with # (matching gen_info_id's convention),
  3. SHA-256, first 16 bytes,
  4. stamped as an RFC 9562 UUIDv8 (exactly Uuid::new_v8),
  5. rendered lowercase.

Step 4 is required rather than cosmetic: consumers validate this value with a UUID matcher that enforces the version and variant nibbles, which an unstamped truncated hash fails most of the time. v8 also visibly distinguishes it from the v4/v5 ids next to it.

A repo rename mints a new guid, because test_case_id embeds the repo name. That is identical to today's behavior for the underlying id, and accepted.

Tests

Two golden vectors are pinned in the Rust tests and mirrored into the JS and Python binding suites — the vectors, not a shared build, are what keep the copies honest. Also covered: determinism, uppercase input normalizing to the same value, the v8/variant stamp, each tuple member actually participating, and malformed input erroring rather than being hashed into a plausible-looking id.

Note the context test module is gated on the bindings feature, so run these with cargo nextest run -p context --features bindings.

Compatibility

gen_info_id's signature and behavior are untouched, so existing bindings and their pinned tests are unaffected. New deps: sha2 (already in Cargo.lock transitively) and uuid's v8 feature.

🤖 Generated with Claude Code

A test case id is only unique within a (collection, repo) pairing: ids are
generated from framework-internal values, and `--no-repo` deliberately makes the
same test share one id across a collection's repos. Consumers that need to
address a single test case by one value -- links, APIs, webhooks -- have no id
to use.

Add `gen_test_case_guid(test_collection_id, repo_id, test_case_id)`, a
deterministic UUIDv8 derived from that whole identity tuple, so it is unique by
construction and inherits the tuple's semantics (including the `--no-repo`
collapse to one id per collection).

The hash contract is frozen: the three ids as canonical lowercase text, joined
with `#`, SHA-256, first 16 bytes, stamped as an RFC 9562 UUIDv8. The stamp is
required rather than cosmetic -- consumers validate the value with a UUID
matcher that enforces the version and variant nibbles. Two golden vectors are
pinned in the Rust tests and mirrored into the JS and Python binding suites,
which is what keeps every copy of the contract honest.

`gen_info_id` is untouched, so existing bindings and their pinned tests are
unaffected. Exported through context-js and context-py alongside it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@trunk-io

trunk-io Bot commented Aug 24, 2026

Copy link
Copy Markdown

Merging to main in this repository is managed by Trunk.

  • To merge this pull request, check the box to the left or comment /trunk merge below.

After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here

@codecov-commenter

codecov-commenter commented Aug 24, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 59.72222% with 29 lines in your changes missing coverage. Please review.
✅ Project coverage is 83.26%. Comparing base (64052e5) to head (104cbd0).
⚠️ Report is 4 commits behind head on main.

Files with missing lines Patch % Lines
context-py/src/lib.rs 0.00% 15 Missing ⚠️
context-js/src/lib.rs 0.00% 14 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1166      +/-   ##
==========================================
+ Coverage   82.97%   83.26%   +0.29%     
==========================================
  Files          71       72       +1     
  Lines       16044    16269     +225     
==========================================
+ Hits        13312    13547     +235     
+ Misses       2732     2722      -10     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@trunk-staging-io

trunk-staging-io Bot commented Aug 24, 2026

Copy link
Copy Markdown

Static BadgeStatic BadgeStatic Badge

Failed Test Failure Summary Logs
pending_quarantine_test should be quarantined when run with variant A test marked as pending was expected to fail but unexpectedly passed. Logs ↗︎
variant_quarantine_test should be quarantined when run with variant A test expected the sum of 2 + 2 to be 5, but it was actually 4, indicating a failing assertion. Logs ↗︎

View Full Report ↗︎Docs

@trunk-io

trunk-io Bot commented Aug 24, 2026

Copy link
Copy Markdown

Static BadgeStatic BadgeStatic Badge

View Full Report ↗︎Docs

max-trunk and others added 3 commits August 24, 2026 15:54
The doc comments restated the algorithm the code already shows. Keep only what
is not visible from reading it: why the tuple is the identity, that the contract
is frozen, and that the v8 stamp is load-bearing rather than decorative.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Nine new lint findings, all in the two test files this PR adds:

- `vitest/prefer-describe-function-title` wants the identifier when a describe
  title is exactly a function name, while `vitest/valid-title` rejects a
  non-string title — the two conflict, and trunk's autofix for the first
  produced a violation of the second. A string that isn't an exact match
  satisfies both. The `gen_info_id` describe was pre-existing but became a "new"
  finding once my import shifted its line.
- `vitest/require-to-throw-message`: assert the message rather than bare throw.
- pytest isn't resolvable in the pyright environment (nothing else here imports
  it), so the raises test uses try/except instead of depending on it.
- `context_py`'s stub is generated rather than committed, so pyright strict
  can't type anything imported from it. Annotating or `str()`-wrapping just
  relocates the unknown, so the two call sites and the import carry targeted
  suppressions, matching the existing precedent in test_parse_codeowners.py.

`trunk check` is clean; 113 Rust and 33 context-js tests still pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…uppressions

pyright type-checks context-py against context_py.pyi, which is generated by a
trunk action rather than committed. Trunk actions run on git hooks, not during
`trunk check`, so a developer's tree has the stub and CI never does — every
symbol imported from context_py is `Unknown` there, which is most of this
repo's standing pyright findings and why adding one import produced a new one.

The trunk check job already builds the wasm package for exactly this reason on
the JS side (eslint needs the generated pkg/ to resolve). Generating the Python
stub is the missing counterpart, and the job already builds the workspace, so it
is nearly free.

With the stub present the suppressions are unnecessary, so they are gone and the
tests keep their natural shape. Existing pyright findings across context-py drop
from 70 to 47 as a side effect.

The raises test still uses try/except rather than pytest.raises: pytest is not
resolvable in the pyright environment either, and nothing else in this directory
imports it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants