| # Test Data Manager - Agent Guidelines |
| |
| Automated system for managing test data files across multiple test configurations. |
| |
| ## Module Overview |
| |
| This module provides infrastructure for: |
| - Comparing test outputs with expected files using variant chains |
| - Automatic file management (creation, update, redundancy removal) |
| - Test discovery, grouping, and conflict detection |
| |
| **Structure:** |
| - `testFixtures/` — Runtime API for use by other modules |
| - `tests/` — Module's own test suite |
| |
| For conceptual details (variant chains, conflicts, convergence), see [README.md](README.md). |
| For running test data management tasks (checking/updating test data via Gradle), see [test-data-manager-convention](../../repo/gradle-build-conventions/test-data-manager-convention/README.md). |
| |
| ## Testing Guidelines (for tests within this module) |
| |
| ### Core Principles |
| |
| 1. **Readable multi-line string expectations** — Format results as human-readable strings, compare with `assertEquals` |
| 2. **Custom formatters** — Create formatters that produce deterministic, readable output |
| 3. **Domain-specific assertion helpers** — Encapsulate complex assertions in named functions |
| 4. **Descriptive test names** — Use backticks with clear descriptions |
| |
| ### Testing Patterns |
| |
| #### Pattern 1: Readable Output Formatting |
| |
| Create formatters that produce deterministic, human-readable output for complex results. |
| |
| From `TestDiscoveryAndGroupingIntegrationTest.kt`: |
| |
| ```kotlin |
| private fun formatResult(result: GroupingResult): String = buildString { |
| for (group in result.groups) { |
| val header = if (group.variantDepth == 0) "Group 0 (golden)" else "Group ${group.variantDepth}" |
| appendLine("=== $header ===") |
| for (test in group.tests.sortedBy { it.displayName }) { |
| appendLine("${test.displayName} -> ${test.variantChain}") |
| } |
| appendLine() |
| } |
| }.trimEnd() |
| |
| @Test |
| fun `discovery finds all tests`() { |
| val result = runDiscovery() |
| assertEquals(expected.trimIndent(), formatResult(result)) |
| } |
| ``` |
| |
| #### Pattern 2: Domain-Specific Assertions |
| |
| Encapsulate complex assertions in helper functions with clear names. |
| |
| From `TestDataManagerGroupingTest.kt`: |
| |
| ```kotlin |
| private fun assertGrouping(tests: List<DiscoveredTest>, expected: String) { |
| val result = groupByVariantDepth(tests) |
| val actual = result.groups.joinToString("\n") { group -> |
| "depth=${group.variantDepth}: ${group.uniqueVariantChains.joinToString(", ")}" |
| } |
| assertEquals(expected.trimIndent(), actual) |
| } |
| |
| private fun assertConflicts(tests: List<DiscoveredTest>, expected: String) { |
| val conflicts = validateConflicts(tests) |
| val actual = conflicts.joinToString("\n") { |
| "${it.chainA} vs ${it.chainB}: '${it.conflictingVariant}'" |
| } |
| assertEquals(expected.trimIndent(), actual) |
| } |
| |
| @Test |
| fun `tests grouped by variant depth`() { |
| assertGrouping( |
| tests = listOf( |
| DiscoveredTest("1", "golden", emptyList()), |
| DiscoveredTest("2", "js", listOf("js")), |
| ), |
| expected = """ |
| depth=0: [] |
| depth=1: [js] |
| """ |
| ) |
| } |
| ``` |
| |
| #### Pattern 3: State-Based Testing with Setup/Assert Helpers |
| |
| For file-based operations, use setup and assertion helpers. |
| |
| From `ManagedTestAssertionsTest.kt`: |
| |
| ```kotlin |
| private fun assertFileState(expected: String) { |
| val actual = listOf("test.txt", "test.js.txt").mapNotNull { name -> |
| val file = tempDir.resolve(name) |
| if (file.exists()) "$name: ${file.readText().trim()}" else null |
| }.joinToString("\n") |
| assertEquals(expected.trimIndent(), actual) |
| } |
| |
| private fun setupFiles(vararg files: Pair<String, String>) { |
| for ((name, content) in files) { |
| tempDir.resolve(name).writeText("$content\n") |
| } |
| } |
| |
| @Test |
| fun `UPDATE mode - mismatch updates file`() { |
| setupFiles("test.txt" to "old") |
| runAssertion(variantChain = emptyList(), actual = "new") |
| assertFileState("test.txt: new") |
| } |
| ``` |
| |
| #### Pattern 4: Filter Testing with Base Class |
| |
| For JUnit filter tests, extend `AbstractPostDiscoveryFilterTest`. |
| |
| From `ManagedTestFilterTest.kt`: |
| |
| ```kotlin |
| internal class ManagedTestFilterTest : AbstractPostDiscoveryFilterTest() { |
| @Test |
| fun `ClassSource with ManagedTest is included`() { |
| assertIncluded( |
| filter = ManagedTestFilter, |
| descriptor = descriptorFromClass<FakeGoldenAnalysisApiTestGenerated>(), |
| ) |
| } |
| |
| @Test |
| fun `ClassSource without ManagedTest is excluded`() { |
| assertExcluded( |
| filter = ManagedTestFilter, |
| descriptor = descriptorFromClass<NoMetadataClass>(), |
| ) |
| } |
| } |
| ``` |
| |
| Available utilities from `AbstractPostDiscoveryFilterTest`: |
| - `assertIncluded(filter, descriptor)` / `assertExcluded(filter, descriptor)` |
| - `descriptorFromClass<T>()` — Create descriptor from class |
| - `descriptorFromMethod(method)` — Create descriptor from method reference |
| - `descriptorWithSource(source)` — Create descriptor with custom source |
| |
| #### Pattern 5: Fake Test Classes for Integration Testing |
| |
| Create fake test classes in `tests/.../fakes/` to simulate real test configurations. |
| |
| ```kotlin |
| // Base class for all fakes |
| abstract class FakeManagedTest : ManagedTest |
| |
| // Golden test (no variant) |
| @TestMetadata("testData/analysis/api") |
| class FakeGoldenAnalysisApiTestGenerated : FakeManagedTest() { |
| override val variantChain = emptyList<String>() |
| |
| @Test |
| @TestMetadata("symbols.kt") |
| fun testSymbols() {} |
| } |
| |
| // Multi-level variant test |
| @TestMetadata("testData/lightClasses") |
| class FakeWasmLightClassesTestGenerated : FakeManagedTest() { |
| override val variantChain = listOf("knm", "wasm") |
| |
| @Test |
| @TestMetadata("simple.kt") |
| fun testSimple() {} |
| } |
| ``` |
| |
| ## Usage from Other Modules |
| |
| ### Implementing ManagedTest |
| |
| Implement `ManagedTest` interface and provide variant chain: |
| |
| ```kotlin |
| abstract class MyTestBase : ManagedTest { |
| override val variantChain: List<String> |
| get() = emptyList() |
| } |
| ``` |
| |
| Variant chain rules: |
| - `[]` (empty) — Golden/default configuration, writes to `.txt` |
| - `["js"]` — Single variant, writes to `.js.txt` |
| - `["knm", "wasm"]` — Multi-level variant, writes to `.wasm.txt` (last element only) |
| |
| ### Using Assertions |
| |
| Use the extension function `ManagedTest.assertEqualsToTestDataFile()` for comparing test output: |
| |
| ```kotlin |
| class MyTest : ManagedTest { |
| override val variantChain = listOf("js") |
| |
| fun runTest(testDataFile: File) { |
| val actual = computeResult() |
| assertEqualsToTestDataFile( |
| testDataPath = testDataFile.toPath(), |
| actual = actual, |
| extension = ".txt", |
| ) |
| } |
| } |
| ``` |
| |
| Or use `ManagedTestAssertions.assertEqualsToTestDataFile()` directly: |
| |
| ```kotlin |
| ManagedTestAssertions.assertEqualsToTestDataFile( |
| testDataPath = testDataFile.toPath(), |
| actual = actualContent, |
| variantChain = variantChain, |
| extension = ".txt", |
| ) |
| ``` |
| |
| |
| ### Behavior Matrix |
| |
| | Scenario | UPDATE mode | CHECK mode (local) | CHECK mode (CI) | |
| |---------------------------|-------------|--------------------|-----------------| |
| | actual=null, file missing | Pass | Pass | Pass | |
| | actual=null, file exists | Delete | Delete + throw | Throw | |
| | File missing (golden) | Create | Create + throw | Throw | |
| | File missing (secondary) | Create | Throw | Throw | |
| | Content matches | Pass | Pass | Pass | |
| | Write-target redundant | Delete | Delete + throw | Throw | |
| | Content mismatch | Update | Throw | Throw | |
| |
| ## Key Classes Reference |
| |
| | Class | Location | Purpose | |
| |--------------------------|----------------------|--------------------------------------------------| |
| | `ManagedTest` | testFixtures | Interface for tests managed by the system | |
| | `ManagedTestAssertions` | testFixtures | Assertion functions for test data comparison | |
| | `TestDataManagerRunner` | testFixtures | Main runner (discovery, grouping, execution) | |
| | `TestDataContext` | testFixtures | File path resolution and mode for variant chains | |
| | `ManagedTestFilter` | testFixtures/filters | JUnit filter for ManagedTest implementations | |
| | `TestMetadataFilter` | testFixtures/filters | JUnit filter by @TestMetadata paths | |
| | `VariantChainComparator` | testFixtures | Orders variant chains by depth | |