packages/sql-faker/tests/Unit/Generation/Derivation/Completion/CompletionWitnessTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\SqlFaker\Generation\Derivation\Completion;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\UsesClass;
9use PHPUnit\Framework\TestCase;
10use SqlFaker\Generation\Derivation\Completion\CompletionWitness;
11use SqlFaker\Generation\Derivation\Completion\PatternProductions;
12use SqlFaker\Generation\Derivation\CompletionCosts;
13use SqlFaker\Generation\Derivation\CompletionMemo;
14use SqlFaker\Generation\Plan\GenerationPlan;
15use SqlFaker\Generation\Plan\ProductionPattern;
16use SqlFaker\Grammar\Model\Grammar;
17use SqlFaker\Grammar\Model\NonTerminal;
18use SqlFaker\Grammar\Model\Production;
19use SqlFaker\Grammar\Model\ProductionRule;
20use SqlFaker\Grammar\Model\Terminal;
21
22#[CoversClass(CompletionWitness::class)]
23#[UsesClass(PatternProductions::class)]
24#[UsesClass(CompletionCosts::class)]
25#[UsesClass(CompletionMemo::class)]
26#[UsesClass(\SqlFaker\Generation\Derivation\CompletionState::class)]
27#[UsesClass(GenerationPlan::class)]
28#[UsesClass(ProductionPattern::class)]
29#[UsesClass(Grammar::class)]
30#[UsesClass(NonTerminal::class)]
31#[UsesClass(Production::class)]
32#[UsesClass(ProductionRule::class)]
33#[UsesClass(Terminal::class)]
34final class CompletionWitnessTest extends TestCase
35{
36 public function testCompleteWalksOccurrencePatternsAndPreservesPendingOutput(): void
37 {
38 $grammar = new Grammar('leaf', ['leaf' => new ProductionRule('leaf', [new Production([]), new Production([new Terminal('T')])])]);
39 $witness = new CompletionWitness(new CompletionCosts($grammar, static fn (string $name): bool => false), new PatternProductions($grammar), new CompletionMemo());
40 $plan = GenerationPlan::constrained('leaf', ['leaf' => [ProductionPattern::at(0), ProductionPattern::at(1)]]);
41 self::assertSame(2, $witness->complete([new NonTerminal('leaf'), new NonTerminal('leaf')], $plan, [], true, 2));
42 self::assertNull($witness->complete([new NonTerminal('leaf'), new NonTerminal('leaf')], $plan, [], true, 1));
43 self::assertNull($witness->complete([new NonTerminal('leaf')], $plan, [], true, 2));
44 self::assertSame(1, $witness->complete([new NonTerminal('leaf')], $plan, ['leaf' => 1], true, 1));
45 }
46
47 public function testCompleteYieldsToTheSolverWhenAGreedyPathNeedsAnotherChoice(): void
48 {
49 $grammar = new Grammar('root', [
50 'root' => new ProductionRule('root', [new Production([new NonTerminal('leaf')]), new Production([new Terminal('T')])]),
51 'leaf' => new ProductionRule('leaf', [new Production([])]),
52 ]);
53 $witness = new CompletionWitness(new CompletionCosts($grammar, static fn (string $name): bool => false), new PatternProductions($grammar), new CompletionMemo());
54 self::assertNull($witness->complete([new NonTerminal('root')], GenerationPlan::all()->withPatternForEveryOccurrence('root', ProductionPattern::at(0)), [], true, 10));
55 self::assertSame(1, $witness->complete([new NonTerminal('root')], GenerationPlan::all(), [], true, 1));
56 self::assertSame(0, $witness->complete([new Terminal('T')], GenerationPlan::all(), [], true, 0));
57 }
58
59 public function testCompleteBoundsTheOptionalProbeWithoutClaimingImpossibility(): void
60 {
61 $grammar = new Grammar('leaf', ['leaf' => new ProductionRule('leaf', [new Production([new NonTerminal('leaf')]), new Production([new Terminal('T')])])]);
62 $witness = new CompletionWitness(new CompletionCosts($grammar, static fn (string $name): bool => false), new PatternProductions($grammar), new CompletionMemo());
63 $plan = GenerationPlan::constrained('leaf', ['leaf' => [...array_fill(0, 260, ProductionPattern::at(0)), ProductionPattern::at(1)]]);
64 self::assertNull($witness->complete([new NonTerminal('leaf')], $plan, [], true, 300));
65 self::assertSame(1, $witness->complete([new NonTerminal('leaf')], $plan, ['leaf' => 260], true, 1));
66 }
67
68 public function testRememberCachesOnlyTheSuccessfulSuffixAndPreservesExactMinimumIndependence(): void
69 {
70 $grammar = new Grammar('leaf', ['leaf' => new ProductionRule('leaf', [new Production([new Terminal('T')])])]);
71 $memo = new CompletionMemo();
72 $witness = new CompletionWitness(new CompletionCosts($grammar, static fn (string $name): bool => false), new PatternProductions($grammar), $memo);
73 $plan = GenerationPlan::all();
74 self::assertSame(4, $witness->remember([['root', 0], ['suffix', 3]], $plan, 4, 5));
75 self::assertSame(1, $memo->recall($plan, 'suffix', 1, false));
76 self::assertNull($memo->recall($plan, 'suffix', 1, true));
77 self::assertNull($memo->recall($plan, 'root', 3, false));
78 }
79
80 public function testChoicesCachesBothOutputPossibilitiesUnderEachDistinctPattern(): void
81 {
82 $empty = new Production([]);
83 $emitting = new Production([new Terminal('T')]);
84 $grammar = new Grammar('leaf', ['leaf' => new ProductionRule('leaf', [$empty, $emitting])]);
85 $witness = new CompletionWitness(new CompletionCosts($grammar, static fn (string $name): bool => false), new PatternProductions($grammar), new CompletionMemo());
86 self::assertSame([[$empty, [0, PHP_INT_MAX]], [$emitting, [PHP_INT_MAX, 0]]], $witness->choices('leaf', null));
87 self::assertSame($witness->choices('leaf', null), $witness->choices('leaf', null));
88 self::assertSame([[$emitting, [PHP_INT_MAX, 0]]], $witness->choices('leaf', ProductionPattern::at(1)));
89 self::assertSame([], $witness->choices('missing', null));
90 }
91}
92