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