packages/sql-faker/tests/Unit/MySql/Generation/Lexeme/BoundedIntegerLexemeGeneratorTest.php

1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\SqlFaker\MySql\Generation\Lexeme;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\DataProvider;
9use PHPUnit\Framework\Attributes\UsesClass;
10use PHPUnit\Framework\TestCase;
11use SqlFaker\Generation\Lexeme\LexemeInput;
12use SqlFaker\Generation\Lexeme\ResolvedOutput;
13use SqlFaker\Generation\Token\TerminalSequence;
14use SqlFaker\MySql\Generation\Lexeme\BoundedIntegerLexemeGenerator;
15
16#[CoversClass(BoundedIntegerLexemeGenerator::class)]
17#[UsesClass(\SqlFaker\Generation\Candidate\IntegerLexemeGenerator::class)]
18#[UsesClass(\SqlFaker\Generation\Lexeme\Lexeme::class)]
19#[UsesClass(\SqlFaker\Generation\Lexeme\LexemeCandidates::class)]
20#[UsesClass(LexemeInput::class)]
21#[UsesClass(\SqlFaker\Generation\Lexeme\LexemeSequence::class)]
22#[UsesClass(ResolvedOutput::class)]
23#[UsesClass(\SqlFaker\Generation\Lexeme\SpacingConstraint::class)]
24#[UsesClass(\SqlFaker\Generation\Token\ProductionOccurrence::class)]
25#[UsesClass(\SqlFaker\Generation\Token\TerminalOccurrence::class)]
26#[UsesClass(TerminalSequence::class)]
27#[UsesClass(\SqlFaker\Generation\Value\CharacterDomain::class)]
28#[UsesClass(\SqlFaker\Generation\Value\IntegerDomain::class)]
29#[UsesClass(\SqlFaker\MySql\Generation\Value\RadixDomain::class)]
30#[UsesClass(\SqlFaker\Generation\Value\SequenceDomain::class)]
31#[UsesClass(\SqlFaker\Generation\Value\WordDomain::class)]
32final class BoundedIntegerLexemeGeneratorTest extends TestCase
33{
34    public function testGenerateSupportsASingletonDomainAndContinuesPastInvalidDefaults(): void
35    {
36        $generator = new BoundedIntegerLexemeGenerator('NUMBER', 1, 1, ['0', '1', '2', '0x1'], 'source');
37        $result = $generator->generate(new LexemeInput(TerminalSequence::fromNames(['NUMBER']), 0, new ResolvedOutput()));
38        self::assertNotNull($result);
39        self::assertSame(['1', '0x1'], array_map(static fn ($candidate): string => $candidate->lexemes[0]->text, [...$result->sequences()]));
40        self::assertCount(2, array_unique(array_map(static fn ($candidate): string => $candidate->id, [...$result->sequences()])));
41    }
42
43    public function testAcceptsUsesBothConfiguredBoundsForDecimalAndHexadecimalValues(): void
44    {
45        $delay = new BoundedIntegerLexemeGenerator('DELAY', 0, 2147483647, ['0'], 'delay');
46        self::assertTrue($delay->accepts('0'));
47        self::assertTrue($delay->accepts("X''"));
48        self::assertTrue($delay->accepts('0x7fffffff'));
49        self::assertFalse($delay->accepts('0x80000000'));
50        $pages = new BoundedIntegerLexemeGenerator('PAGES', 1, 65535, ['1'], 'pages');
51        self::assertFalse($pages->accepts('0'));
52        self::assertTrue($pages->accepts('65535'));
53        self::assertTrue($pages->accepts('0xffff'));
54        self::assertFalse($pages->accepts('65536'));
55        self::assertFalse($pages->accepts('0x10000'));
56    }
57
58    #[DataProvider('providerIndices')]
59    public function testAcceptsUnsignedFormsWithinTheSourceInterval(string $spelling, bool $valid): void
60    {
61        self::assertSame($valid, (new BoundedIntegerLexemeGenerator('BINLOG_RESET_INDEX', 1, 2000000000, ['1', '2000000000', '0x1'], 'sql/sql_yacc.yy:source_reset_options'))->accepts($spelling));
62    }
63
64    /**
65     * @return list<array{string, bool}>
66     */
67    public static function providerIndices(): array
68    {
69        return [['1', true], ['0001', true], ['2000000000', true], ['0x77359400', true], ["X'77359400'", true], ["x'0001'", true], ['0x000000001', true], ['0', false], ['2000000001', false], ['0x77359401', false], ["X'77359401'", false], ['0xfffffffff', false], ['0x000', false], ["X''", false], ["X'1'", false], ['0X1', false], ['1e0', false], ['-1', false], ['', false]];
70    }
71
72    public function testGenerateKeepsTheExplicitHexSpellingAndOrigin(): void
73    {
74        $sequence = TerminalSequence::fromNames(['BINLOG_RESET_INDEX']);
75        $generator = new BoundedIntegerLexemeGenerator('BINLOG_RESET_INDEX', 1, 2000000000, ['1', '2000000000', '0x1'], 'sql/sql_yacc.yy:source_reset_options');
76        $result = $generator->generate(new LexemeInput($sequence, 0, new ResolvedOutput(), "X'0001'"));
77        self::assertNotNull($result);
78        self::assertSame("X'0001'", [...$result->sequences()][0]->lexemes[0]->text);
79        self::assertSame($sequence->terminals[0], [...$result->sequences()][0]->lexemes[0]->origin);
80        $invalid = $generator->generate(new LexemeInput($sequence, 0, new ResolvedOutput(), '0'));
81        self::assertNotNull($invalid);
82        self::assertSame([], [...$invalid->sequences()]);
83    }
84
85    public function testGenerateProvidesBoundaryRepresentativesWithoutClaimingOtherNumbers(): void
86    {
87        $generator = new BoundedIntegerLexemeGenerator('BINLOG_RESET_INDEX', 1, 2000000000, ['1', '2000000000', '0x1'], 'sql/sql_yacc.yy:source_reset_options');
88        $result = $generator->generate(new LexemeInput(TerminalSequence::fromNames(['BINLOG_RESET_INDEX']), 0, new ResolvedOutput()));
89        self::assertNotNull($result);
90        self::assertSame(['1', '2000000000', '0x1'], array_map(static fn ($candidate): string => $candidate->lexemes[0]->text, [...$result->sequences()]));
91        self::assertNull($generator->generate(new LexemeInput(TerminalSequence::fromNames(['NUM']), 0, new ResolvedOutput())));
92    }
93}
94