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