packages/sql-faker/tests/Unit/Generation/Derivation/TerminationCostTest.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\TerminationCost;
11use SqlFaker\Grammar\Model\Grammar;
12use SqlFaker\Grammar\Model\NonTerminal;
13use SqlFaker\Grammar\Model\Production;
14use SqlFaker\Grammar\Model\ProductionRule;
15use SqlFaker\Grammar\Model\Terminal;
16
17#[CoversClass(TerminationCost::class)]
18#[UsesClass(Grammar::class)]
19#[UsesClass(NonTerminal::class)]
20#[UsesClass(Production::class)]
21#[UsesClass(ProductionRule::class)]
22#[UsesClass(Terminal::class)]
23final class TerminationCostTest extends TestCase
24{
25 public function testOfCountsTheTokensOfTheCheapestAlternative(): void
26 {
27 $grammar = new Grammar('stmt', [
28 'stmt' => new ProductionRule('stmt', [
29 new Production([new Terminal('A'), new Terminal('B'), new Terminal('C')]),
30 new Production([new Terminal('A')]),
31 ]),
32 ]);
33
34 self::assertSame(1, (new TerminationCost($grammar, static fn (string $t): bool => true, 1, 0))->of('stmt'));
35 }
36
37 public function testOfCountsARuleThatCanOnlyExpandIntoItselfAsUnfinishable(): void
38 {
39 $grammar = new Grammar('loop', [
40 'loop' => new ProductionRule('loop', [new Production([new NonTerminal('loop')])]),
41 ]);
42
43 self::assertSame(
44 PHP_INT_MAX,
45 (new TerminationCost($grammar, static fn (string $t): bool => true, 1, 0))->of('loop'),
46 );
47 }
48
49 public function testOfTreatsASymbolWithNoRuleAsAToken(): void
50 {
51 $grammar = new Grammar('stmt', ['stmt' => new ProductionRule('stmt', [new Production([new Terminal('A')])])]);
52
53 self::assertSame(1, (new TerminationCost($grammar, static fn (string $t): bool => true, 1, 0))->of('IDENT'));
54 }
55
56 public function testOfRefusesATokenNothingCanWrite(): void
57 {
58 $grammar = new Grammar('stmt', ['stmt' => new ProductionRule('stmt', [new Production([new Terminal('A')])])]);
59
60 self::assertSame(
61 PHP_INT_MAX,
62 (new TerminationCost($grammar, static fn (string $t): bool => false, 1, 0))->of('IDENT'),
63 );
64 }
65
66 public function testOfProductionAddsUpWhatEachSymbolCosts(): void
67 {
68 $grammar = new Grammar('nm', ['nm' => new ProductionRule('nm', [new Production([new Terminal('IDENT')])])]);
69 $cost = new TerminationCost($grammar, static fn (string $t): bool => true, 1, 0);
70
71 self::assertSame(2, $cost->ofProduction(new Production([new Terminal('SELECT'), new NonTerminal('nm')])));
72 }
73
74 public function testOfProductionCountsExpansionsWhenThatIsWhatAStepIs(): void
75 {
76 $grammar = new Grammar('nm', ['nm' => new ProductionRule('nm', [new Production([new Terminal('IDENT')])])]);
77 $cost = new TerminationCost($grammar, static fn (string $t): bool => true, 0, 1);
78
79 self::assertSame(1, $cost->ofProduction(new Production([new Terminal('SELECT'), new NonTerminal('nm')])));
80 }
81
82 public function testSettledAnswersTheLeastEveryRuleCanCost(): void
83 {
84 $grammar = new Grammar('stmt', [
85 'stmt' => new ProductionRule('stmt', [new Production([new Terminal('SELECT'), new NonTerminal('nm')])]),
86 'nm' => new ProductionRule('nm', [new Production([new Terminal('IDENT')])]),
87 ]);
88 $cost = new TerminationCost($grammar, static fn (string $t): bool => true, 1, 0);
89
90 self::assertSame(['stmt' => 2, 'nm' => 1], $cost->settled($grammar));
91 }
92
93 public function testSumRefusesASequenceCarryingATokenNothingCanWrite(): void
94 {
95 $grammar = new Grammar('stmt', ['stmt' => new ProductionRule('stmt', [new Production([new Terminal('A')])])]);
96 $cost = new TerminationCost($grammar, static fn (string $t): bool => $t !== 'B', 1, 0);
97
98 self::assertSame(PHP_INT_MAX, $cost->sum([new Terminal('A'), new Terminal('B')], []));
99 }
100}
101