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