packages/sql-faker/tests/Unit/Generation/Derivation/CompletionFrontierTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\SqlFaker\Generation\Derivation;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\UsesClass;
9use PHPUnit\Framework\TestCase;
10use SqlFaker\Generation\Derivation\CompletionCosts;
11use SqlFaker\Generation\Derivation\CompletionFrontier;
12use SqlFaker\Generation\Derivation\CompletionState;
13use SqlFaker\Generation\Plan\GenerationPlan;
14use SqlFaker\Generation\Plan\ProductionPattern;
15use SqlFaker\Grammar\Model\Grammar;
16use SqlFaker\Grammar\Model\NonTerminal;
17use SqlFaker\Grammar\Model\Production;
18use SqlFaker\Grammar\Model\ProductionRule;
19use SqlFaker\Grammar\Model\Terminal;
20
21#[CoversClass(CompletionFrontier::class)]
22#[UsesClass(CompletionCosts::class)]
23#[UsesClass(CompletionState::class)]
24#[UsesClass(GenerationPlan::class)]
25#[UsesClass(ProductionPattern::class)]
26#[UsesClass(Grammar::class)]
27#[UsesClass(NonTerminal::class)]
28#[UsesClass(Production::class)]
29#[UsesClass(ProductionRule::class)]
30#[UsesClass(Terminal::class)]
31#[UsesClass(\SqlFaker\Generation\Choice\BytePlanCompiler::class)]
32final class CompletionFrontierTest extends TestCase
33{
34 public function testTakeOrdersAffordableCompletionsByEstimatedCost(): void
35 {
36 $grammar = new Grammar('leaf', ['leaf' => new ProductionRule('leaf', [new Production([new Terminal('T')])])]);
37 $frontier = new CompletionFrontier(new CompletionCosts($grammar, static fn (string $name): bool => false), 5);
38 $frontier->offer([new NonTerminal('leaf')], ['leaf' => 1], true, 3);
39 $frontier->offer([new NonTerminal('leaf')], ['leaf' => 0], true, 1);
40 self::assertSame(1, $frontier->take()?->spent);
41 self::assertSame(3, $frontier->take()?->spent);
42 self::assertNull($frontier->take());
43 }
44
45 public function testOfferPrunesOverBudgetAndEquivalentMoreExpensiveStates(): void
46 {
47 $grammar = new Grammar('leaf', ['leaf' => new ProductionRule('leaf', [new Production([new Terminal('T')])])]);
48 $frontier = new CompletionFrontier(new CompletionCosts($grammar, static fn (string $name): bool => false), 3);
49 $frontier->offer([new NonTerminal('leaf')], [], true, 1);
50 $frontier->offer([new NonTerminal('leaf')], [], true, 2);
51 $frontier->offer([new NonTerminal('leaf')], ['leaf' => 1], true, 3);
52 $frontier->offer([], [], false, 4);
53 self::assertSame(1, $frontier->take()?->spent);
54 self::assertNull($frontier->take());
55 }
56
57 public function testOfferRemembersAlreadyEmittedOutputBeforeErasingTerminals(): void
58 {
59 $grammar = new Grammar('leaf', ['leaf' => new ProductionRule('leaf', [new Production([])])]);
60 $frontier = new CompletionFrontier(new CompletionCosts($grammar, static fn (string $name): bool => $name === 'END'), 2);
61 $frontier->offer([new Terminal('END'), new NonTerminal('leaf')], [], true, 0);
62 self::assertNull($frontier->take());
63 $frontier->offer([new Terminal('T'), new NonTerminal('leaf')], [], true, 0);
64 $state = $frontier->take();
65 self::assertNotNull($state);
66 self::assertFalse($state->nonEmpty);
67 self::assertCount(1, $state->symbols);
68 }
69}
70