Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

step-03a-subagent-determinism.md 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. ---
  2. name: 'step-03a-subagent-determinism'
  3. description: 'Subagent: Check test determinism (no random/time dependencies)'
  4. subagent: true
  5. outputFile: '/tmp/tea-test-review-determinism-{{timestamp}}.json'
  6. ---
  7. # Subagent 3A: Determinism Quality Check
  8. ## SUBAGENT CONTEXT
  9. This is an **isolated subagent** running in parallel with other quality dimension checks.
  10. **What you have from parent workflow:**
  11. - Test files discovered in Step 2
  12. - Knowledge fragment: test-quality (determinism criteria)
  13. - Config: test framework
  14. **Your task:** Analyze test files for DETERMINISM violations only.
  15. ---
  16. ## MANDATORY EXECUTION RULES
  17. - 📖 Read this entire subagent file before acting
  18. - ✅ Check DETERMINISM only (not other quality dimensions)
  19. - ✅ Output structured JSON to temp file
  20. - ❌ Do NOT check isolation, maintainability, coverage, or performance (other subagents)
  21. - ❌ Do NOT modify test files (read-only analysis)
  22. - ❌ Do NOT run tests (just analyze code)
  23. ---
  24. ## SUBAGENT TASK
  25. ### 1. Identify Determinism Violations
  26. **Scan test files for non-deterministic patterns:**
  27. **HIGH SEVERITY Violations**:
  28. - `Math.random()` - Random number generation
  29. - `Date.now()` or `new Date()` without mocking
  30. - `setTimeout` / `setInterval` without proper waits
  31. - External API calls without mocking
  32. - File system operations on random paths
  33. - Database queries with non-deterministic ordering
  34. - **PactV4 consumer tests: multiple `pact.addInteraction()` in a single `it()` block** — the Rust FFI non-deterministically drops interactions (see `pactjs-utils-consumer-helpers.md` Example 6). Flag any `.pacttest.ts` file where a single `it()`/`test()` contains more than one `addInteraction()` chain.
  35. - **PactV4 consumer Vitest config missing `fileParallelism: false`** in `vitest.config.pact.ts` — parallel workers race on the shared pact JSON file (see `pact-consumer-framework-setup.md` Example 2). HIGH regardless of file count.
  36. - **PactV4 consumer Vitest config missing `pool: 'forks'` + `poolOptions.forks.singleFork: true`** in `vitest.config.pact.ts` — best current understanding is that the `@pact-foundation/pact` napi-rs binding is not robust across Vitest worker threads sharing a process; once a consumer+provider pair has ≥2 `.pacttest.ts` files, default threads pool produces reproducible "request was expected but not received" flakes on Linux CI. **Severity: HIGH if the repo has ≥2 `.pacttest.ts` files for the same consumer+provider pair; LOW (future-proof advisory) for single-file suites.** See `pact-consumer-framework-setup.md` Example 2.
  37. - **Pact provider Vitest config missing `pool: 'forks'` + `poolOptions.forks.singleFork: true`** in `vitest.config.contract.ts` for multi-file provider suites (especially message providers) — same pool rule as the consumer side (see `pactjs-utils-provider-verifier.md` Example 7).
  38. - **Consumer or provider Vitest config sets any of: `sequence.concurrent: true`, `maxConcurrency > 1`, `maxWorkers > 1`, `isolate: false`** in `vitest.config.pact.ts` / `vitest.config.contract.ts` — each defeats the serialization the forks-singleFork rule relies on. HIGH.
  39. - **Consumer repo lacks a determinism gate** — if `tea_use_pactjs_utils` is enabled, flag any `package.json` whose `test:pact:consumer` script does not run `scripts/check-pact-determinism.sh` (see `pact-consumer-framework-setup.md` Example 10).
  40. **MEDIUM SEVERITY Violations**:
  41. - `page.waitForTimeout(N)` - Hard waits instead of conditions
  42. - Flaky selectors (CSS classes that may change)
  43. - Race conditions (missing proper synchronization)
  44. - Test order dependencies (test A must run before test B)
  45. **LOW SEVERITY Violations**:
  46. - Missing test isolation (shared state between tests)
  47. - Console timestamps without fixed timezone
  48. ### 2. Analyze Each Test File
  49. For each test file from Step 2:
  50. ```javascript
  51. const violations = [];
  52. // Check for Math.random()
  53. if (testFileContent.includes('Math.random()')) {
  54. violations.push({
  55. file: testFile,
  56. line: findLineNumber('Math.random()'),
  57. severity: 'HIGH',
  58. category: 'random-generation',
  59. description: 'Test uses Math.random() - non-deterministic',
  60. suggestion: 'Use faker.seed(12345) for deterministic random data',
  61. });
  62. }
  63. // Check for Date.now()
  64. if (testFileContent.includes('Date.now()') || testFileContent.includes('new Date()')) {
  65. violations.push({
  66. file: testFile,
  67. line: findLineNumber('Date.now()'),
  68. severity: 'HIGH',
  69. category: 'time-dependency',
  70. description: 'Test uses Date.now() or new Date() without mocking',
  71. suggestion: 'Mock system time with test.useFakeTimers() or use fixed timestamps',
  72. });
  73. }
  74. // Check for hard waits
  75. if (testFileContent.includes('waitForTimeout')) {
  76. violations.push({
  77. file: testFile,
  78. line: findLineNumber('waitForTimeout'),
  79. severity: 'MEDIUM',
  80. category: 'hard-wait',
  81. description: 'Test uses waitForTimeout - creates flakiness',
  82. suggestion: 'Replace with expect(locator).toBeVisible() or interceptNetworkCall-based network waits',
  83. });
  84. }
  85. // ... check other patterns
  86. ```
  87. **Detecting Pact Vitest config violations (`vitest.config.pact.ts` / `vitest.config.contract.ts`)**
  88. Vitest configs vary widely — `defineConfig({ test: { ... } })`, `mergeConfig(base, overrides)`, `satisfies UserConfig`, imported constants, TS spreads. A full AST parse is out of scope; use this fallback heuristic and accept false-negatives only for the `mergeConfig` case, which the subagent must flag separately:
  89. ```javascript
  90. // Resolve the config file(s). For consumer: scripts.test:pact:consumer:run in package.json
  91. // usually points at `vitest run --config <path>`. For provider: `vitest run --config <path>`.
  92. // If neither script exists but `.pacttest.ts` files exist, default to 'vitest.config.pact.ts'.
  93. const configPath = resolveVitestConfigPath({ scriptName: 'test:pact:consumer:run', fallback: 'vitest.config.pact.ts' });
  94. const src = fs.readFileSync(configPath, 'utf8');
  95. // 1. Literal-match the two mandatory lines. Tolerate single or double quotes and whitespace.
  96. const hasFileParallelismFalse = /\bfileParallelism\s*:\s*false\b/.test(src);
  97. const hasPoolForks = /\bpool\s*:\s*['"]forks['"]/.test(src);
  98. const hasSingleForkTrue = /\bsingleFork\s*:\s*true\b/.test(src);
  99. // 2. Flag settings that would defeat the rule if a human added them.
  100. const hasSequenceConcurrent = /\bsequence\s*:\s*\{[^}]*\bconcurrent\s*:\s*true/.test(src);
  101. const hasHighMaxConcurrency = /\bmaxConcurrency\s*:\s*([2-9]|\d{2,})/.test(src);
  102. const hasHighMaxWorkers = /\bmaxWorkers\s*:\s*([2-9]|\d{2,})/.test(src);
  103. const hasIsolateFalse = /\bisolate\s*:\s*false\b/.test(src);
  104. // 3. mergeConfig / extends fallback — we cannot reliably follow imports. Emit LOW advisory.
  105. const usesMergeConfig = /\bmergeConfig\s*\(/.test(src) || /\bextends\s*:/.test(src);
  106. // 4. File-count gating for the pool-forks rule.
  107. const pactTestCount = glob.sync('tests/contract/**/*.pacttest.ts').length;
  108. ```
  109. **Violation emission rules** (apply in order; exit on first match per check):
  110. - Missing `fileParallelism: false` → HIGH (always)
  111. - Missing `pool: 'forks'` OR missing `singleFork: true`, AND `pactTestCount >= 2` → HIGH
  112. - Missing `pool: 'forks'` OR missing `singleFork: true`, AND `pactTestCount < 2` → LOW (future-proof advisory)
  113. - Any of `sequence.concurrent: true`, `maxConcurrency > 1`, `maxWorkers > 1`, `isolate: false` present → HIGH
  114. - `usesMergeConfig` AND any of the three mandatory matches missing → LOW + `category: "pact-config-unverifiable"` with a suggestion to inline the pool settings at the leaf config or provide a `// tea:pact-ffi-safe` marker comment the subagent can trust
  115. ### 3. Calculate Determinism Score
  116. **Scoring Logic**:
  117. ```javascript
  118. const totalChecks = testFiles.length * checksPerFile;
  119. const failedChecks = violations.length;
  120. const passedChecks = totalChecks - failedChecks;
  121. // Weight violations by severity
  122. const severityWeights = { HIGH: 10, MEDIUM: 5, LOW: 2 };
  123. const totalPenalty = violations.reduce((sum, v) => sum + severityWeights[v.severity], 0);
  124. // Score: 100 - (penalty points)
  125. const score = Math.max(0, 100 - totalPenalty);
  126. ```
  127. ---
  128. ## OUTPUT FORMAT
  129. Write JSON to temp file: `/tmp/tea-test-review-determinism-{{timestamp}}.json`
  130. ```json
  131. {
  132. "dimension": "determinism",
  133. "score": 85,
  134. "max_score": 100,
  135. "grade": "B",
  136. "violations": [
  137. {
  138. "file": "tests/api/user.spec.ts",
  139. "line": 42,
  140. "severity": "HIGH",
  141. "category": "random-generation",
  142. "description": "Test uses Math.random() - non-deterministic",
  143. "suggestion": "Use faker.seed(12345) for deterministic random data",
  144. "code_snippet": "const userId = Math.random() * 1000;"
  145. },
  146. {
  147. "file": "tests/e2e/checkout.spec.ts",
  148. "line": 78,
  149. "severity": "MEDIUM",
  150. "category": "hard-wait",
  151. "description": "Test uses waitForTimeout - creates flakiness",
  152. "suggestion": "Replace with expect(locator).toBeVisible()",
  153. "code_snippet": "await page.waitForTimeout(5000);"
  154. }
  155. ],
  156. "passed_checks": 12,
  157. "failed_checks": 3,
  158. "total_checks": 15,
  159. "violation_summary": {
  160. "HIGH": 1,
  161. "MEDIUM": 1,
  162. "LOW": 1
  163. },
  164. "recommendations": [
  165. "Use faker with fixed seed for all random data",
  166. "Replace all waitForTimeout with conditional waits",
  167. "Mock Date.now() in tests that use current time"
  168. ],
  169. "summary": "Tests are mostly deterministic with 3 violations (1 HIGH, 1 MEDIUM, 1 LOW)"
  170. }
  171. ```
  172. **On Error:**
  173. ```json
  174. {
  175. "dimension": "determinism",
  176. "success": false,
  177. "error": "Error message describing what went wrong"
  178. }
  179. ```
  180. ---
  181. ## EXIT CONDITION
  182. Subagent completes when:
  183. - ✅ All test files analyzed for determinism violations
  184. - ✅ Score calculated (0-100)
  185. - ✅ Violations categorized by severity
  186. - ✅ Recommendations generated
  187. - ✅ JSON output written to temp file
  188. **Subagent terminates here.** Parent workflow will read output and aggregate with other quality dimensions.
  189. ---
  190. ## 🚨 SUBAGENT SUCCESS METRICS
  191. ### ✅ SUCCESS:
  192. - All test files scanned for determinism violations
  193. - Score calculated with proper severity weighting
  194. - JSON output valid and complete
  195. - Only determinism checked (not other dimensions)
  196. ### ❌ FAILURE:
  197. - Checked quality dimensions other than determinism
  198. - Invalid or missing JSON output
  199. - Score calculation incorrect
  200. - Modified test files (should be read-only)