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