packages/sql-faker/tests/Unit/Generation/Coverage/GenerationTraceTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\SqlFaker\Generation\Coverage;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\TestCase;
9use SqlFaker\Generation\Coverage\GenerationTrace;
10
11#[CoversClass(GenerationTrace::class)]
12final class GenerationTraceTest extends TestCase
13{
14 public function testBeginAttemptStartsWithNoSelections(): void
15 {
16 $trace = new GenerationTrace(9, 'stmt', []);
17 $trace->beginAttempt(0);
18 self::assertSame([], $trace->value['attempts'][0]['events']);
19 self::assertSame('in-progress', $trace->value['status']);
20 }
21
22 public function testRecordDistinguishesSiblingOccurrencesWithTheSameRule(): void
23 {
24 $trace = new GenerationTrace(9, 'expr', []);
25 $trace->beginAttempt(0);
26 $trace->record(0, null, null, 'expr', 'recursive', 'input');
27 $trace->record(1, 0, 0, 'expr', 'literal', 'budget');
28 $trace->record(3, 0, 2, 'expr', 'literal', 'budget');
29 self::assertSame([0, 1, 3], array_column($trace->value['attempts'][0]['events'], 'nodeId'));
30 self::assertSame([null, 0, 2], array_column($trace->value['attempts'][0]['events'], 'rhsPosition'));
31 self::assertSame(['recursive', 'literal'], $trace->value['reachedIds']);
32 }
33
34 public function testDiscardRetainsFailedSelectionsAndTheirError(): void
35 {
36 $trace = new GenerationTrace(1, 'stmt', []);
37 $trace->beginAttempt(0);
38 $trace->record(0, null, null, 'stmt', 'p0', 'input');
39 $trace->discard('bad lexical token');
40 self::assertSame(['p0'], $trace->value['reachedIds']);
41 self::assertSame([], $trace->value['emittedIds']);
42 self::assertSame('bad lexical token', $trace->value['attempts'][0]['error']);
43 }
44
45 public function testCommitIncludesOnlyTheSuccessfulAttempt(): void
46 {
47 $trace = new GenerationTrace(1, 'stmt', []);
48 $trace->beginAttempt(0);
49 $trace->record(0, null, null, 'stmt', 'failed', 'input');
50 $trace->discard('retry');
51 $trace->beginAttempt(1);
52 $trace->record(0, null, null, 'stmt', 'success', 'input');
53 self::assertSame(['success'], $trace->commit('hash'));
54 self::assertSame(['failed', 'success'], $trace->value['reachedIds']);
55 self::assertSame('hash', $trace->value['attempts'][1]['sqlHash']);
56 }
57
58 public function testEndFinalizesAnInterruptedGeneration(): void
59 {
60 $trace = new GenerationTrace(1, 'stmt', []);
61 $trace->beginAttempt(0);
62 $trace->end();
63 self::assertSame('failed', $trace->value['status']);
64 self::assertSame('discarded', $trace->value['attempts'][0]['status']);
65 }
66
67 public function testTraceAndEventsRetainThePublicDiagnosticIdentity(): void
68 {
69 $plan = ['budget' => 12, 'lexicalTarget' => 'identifier'];
70 $trace = new GenerationTrace(42, 'expr', $plan);
71 self::assertSame(['generationId' => 42, 'root' => 'expr', 'planSummary' => $plan,
72 'attempts' => [], 'features' => [], 'candidateSources' => [], 'candidateConditions' => [], 'candidateRejections' => [], 'rewriteOperations' => [], 'rewrites' => [],
73 'spacingEvents' => [],
74 'lexicalEvents' => ['identifier'], 'status' => 'in-progress',
75 'reachedIds' => [], 'emittedIds' => []], $trace->value);
76 $trace->beginAttempt(7);
77 self::assertSame(['id' => 7, 'events' => [], 'status' => 'in-progress', 'error' => null, 'sqlHash' => null], $trace->value['attempts'][0]);
78 $trace->record(12, 9, 3, 'term', 'term#2', 'budget');
79 self::assertSame(['generationId' => 42, 'attemptId' => 7, 'nodeId' => 12,
80 'parentNodeId' => 9, 'rhsPosition' => 3, 'ruleId' => 'term',
81 'productionId' => 'term#2', 'selectionReason' => 'budget'], $trace->value['attempts'][0]['events'][0]);
82 }
83
84 public function testCommittedTraceSurvivesEndAndDeduplicatesRepeatedProductions(): void
85 {
86 $trace = new GenerationTrace(1, 'stmt', []);
87 $trace->beginAttempt(0);
88 $trace->record(0, null, null, 'stmt', 'p0', 'input');
89 $trace->record(1, 0, 0, 'stmt', 'p0', 'input');
90 self::assertSame(['p0'], $trace->commit('sql'));
91 $trace->end();
92 self::assertSame('success', $trace->value['status']);
93 self::assertSame('committed', $trace->value['attempts'][0]['status']);
94 self::assertNull($trace->value['attempts'][0]['error']);
95 self::assertSame(['p0'], $trace->value['emittedIds']);
96 }
97
98 public function testEndingBeforeAnyAttemptDoesNotInventAnAttemptOrProduction(): void
99 {
100 $trace = new GenerationTrace(1, 'stmt', ['lexicalTarget' => null]);
101 $trace->discard('before start');
102 self::assertSame([], $trace->commit('none'));
103 $trace->end();
104 self::assertSame([], $trace->value['attempts']);
105 self::assertSame([], $trace->value['lexicalEvents']);
106 self::assertSame('failed', $trace->value['status']);
107 }
108
109}
110