Skip to content

Add @TableSource for dense test matrices#273

Merged
em3s merged 2 commits intomainfrom
feat/table-source-annotation
Apr 24, 2026
Merged

Add @TableSource for dense test matrices#273
em3s merged 2 commits intomainfrom
feat/table-source-annotation

Conversation

@em3s
Copy link
Copy Markdown
Contributor

@em3s em3s commented Apr 24, 2026

Summary

Adds @TableSource, a second YAML-based data source for @ObjectSourceParameterizedTest. It accepts a columns header and rows list so dense primitive matrices keep CSV-like density while staying pure YAML and sharing the same name-based parameter binding as @ObjectSource.

Why not just keep @CsvSource?

@CsvSource is a fine tool and for a single file often looks slightly more concise than @TableSource. The reason we are introducing @TableSource is not density — it is type-processing consistency.

Every data-driven test in the project should flow through the same pipeline:

  • One parser (ObjectMappers.YAML, Jackson) for all parameterized data.
  • One place to register custom domain-type deserializers — state machines, event types, storage handles — and have them work everywhere.
  • One null marker (~), one boolean convention, one nested-object story.

@CsvSource uses a separate conversion pipeline (JUnit's ArgumentConverter), which silently pushes string→domain conversion into test bodies. Today in this repo that shows up as ad-hoc helpers like toBooleanFlexible(), toStateValue(), handleSpecialValue(), toEventType(), toEventSequence() inside state transition tests — every file re-implements the same CSV-string-to-domain-type coercion. With @TableSource (and a Jackson module registered for the domain types) those helpers become unnecessary: YAML values are already typed when the test body runs.

The density argument alone does not justify a new annotation. The unified type pipeline does: one change in one module picks up custom types for every data-driven test, and test bodies stop repeating string-parsing adapters.

Shape comparison, for reference:

// @CsvSource — dense, but each file must convert strings to domain types
@ParameterizedTest
@CsvSource(
    "IDLE,    START, RUNNING",
    "RUNNING, STOP,  IDLE",
)
fun test(from: State, event: Event, expected: State) { ... }

// @TableSource — +1 line, but shares @ObjectSource's Jackson pipeline
@ObjectSourceParameterizedTest
@TableSource("""
    columns: [from, event, expected]
    rows:
      - [IDLE,    START, RUNNING]
      - [RUNNING, STOP,  IDLE]
""")
fun test(from: State, event: Event, expected: State) { ... }

Changes

  • New: TableSource annotation (core/src/testFixtures/.../params/TableSource.kt).
  • Modified: ObjectSourceExtension now dispatches on whichever annotation is present; existing @ObjectSource behavior is unchanged.
  • Docs: TESTING.md gets a short "Dense matrices" subsection explaining the split.
  • Tests: TableSourceTest covers columns/rows expansion, ~ → null, enum binding, and @Nested classes.

This PR is purely additive — no rename, no existing class moved. If we want to rename ObjectSourceExtension to something more neutral now that it handles both sources, that can land as a separate rename-only PR (100% similarity, blame survives squash merge).

Test plan

  • ./gradlew :core:test --tests '*TableSourceTest' --tests '*ObjectSourceTest' green
  • ./gradlew :core:test full suite green
  • Documentation example paths exist
  • CI checks pass (CodeQL, Analyze, submit-gradle, CLA)

Out of scope

  • Migration of existing @CsvSource / @MethodSource tests (tracked in Migrate 33 Kotlin data-driven tests to @ObjectSource #271). Adopting @TableSource is what unlocks removing the string-parsing helpers in state transition tests.
  • Jackson module registration for specific domain types (State, StateValue, EventType, ...). That will happen alongside the first migration PR that needs it — there is no need to over-engineer upfront.
  • Rename of ObjectSourceExtension to a neutral name. Separate PR from the original author.
  • Java support. @ObjectSource/@TableSource are both Kotlin-only today; extending to Java requires enabling -parameters project-wide and a Java-reflection fallback.

@em3s em3s force-pushed the feat/table-source-annotation branch from 18fefde to 999ce3a Compare April 24, 2026 13:46
Introduces `@TableSource`, a second YAML-based data source for
`@ObjectSourceParameterizedTest`. It accepts a `columns` header and `rows`
list so a dense primitive matrix keeps CSV-like density while staying pure
YAML and using the same name-based parameter binding as `@ObjectSource`.

Intended use:

- `@ObjectSource` — nested / heterogeneous / single-case data
- `@TableSource` — dense primitive matrices where repeating per-case keys
  would hurt readability

Example:

    @ObjectSourceParameterizedTest
    @TableSource("""
        columns: [from, event, expected]
        rows:
          - [IDLE,    START, RUNNING]
          - [RUNNING, STOP,  IDLE]
    """)
    fun `transition moves state`(from: State, event: Event, expected: State)

Implementation:

- `ObjectSourceExtension` now dispatches on whichever annotation is present
  and reuses the same invocation context and parameter resolver. Only one
  of the two annotations may appear on a method.
- `TESTING.md` gets a short "Dense matrices" subsection so the decision
  between the two annotations is documented in one place.
- `TableSourceTest` covers columns/rows expansion, null handling (`~`),
  enum binding, and `@Nested` classes.

This PR is purely additive — no rename, no existing class moved. A later
rename of `ObjectSourceExtension` to something more neutral can land as a
separate, blame-preserving PR from the original author.
@em3s em3s force-pushed the feat/table-source-annotation branch from 999ce3a to 7ab8500 Compare April 24, 2026 13:52
Covers the error paths introduced by the parser so future changes do not
regress them silently:

- Blank `value`
- Missing `columns` / `rows` keys
- Row size does not match columns size
- Non-string entries in `columns`
- Non-list entries in `rows`
- Empty rows list (produces zero test cases)

Exposes `parseTableSource` on `ObjectSourceExtension` as a public helper
so tests can drive it directly; the test-fixtures source set is a separate
Kotlin module from the regular test source set, so `internal` is not
sufficient here.
@em3s em3s marked this pull request as ready for review April 24, 2026 14:10
@dosubot dosubot Bot added size:S This PR changes 10-29 lines, ignoring generated files. enhancement New feature or request labels Apr 24, 2026
@em3s em3s merged commit 5bb0f03 into main Apr 24, 2026
7 checks passed
@em3s em3s assigned em3s and unassigned em3s Apr 24, 2026
@em3s em3s deleted the feat/table-source-annotation branch April 24, 2026 15:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request size:S This PR changes 10-29 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant