packages/sql-faker/tests/Unit/Generation/Candidate/IntegerLexemeGeneratorTest.php

1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\SqlFaker\Generation\Candidate;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\DataProvider;
9use PHPUnit\Framework\Attributes\UsesClass;
10use PHPUnit\Framework\TestCase;
11use SqlFaker\Generation\Candidate\IntegerLexemeGenerator;
12use SqlFaker\Generation\Lexeme\Lexeme;
13use SqlFaker\Generation\Lexeme\LexemeCandidates;
14use SqlFaker\Generation\Lexeme\LexemeInput;
15use SqlFaker\Generation\Lexeme\LexemeSequence;
16use SqlFaker\Generation\Lexeme\ResolvedOutput;
17use SqlFaker\Generation\Token\TerminalOccurrence;
18use SqlFaker\Generation\Token\TerminalSequence;
19
20#[CoversClass(IntegerLexemeGenerator::class)]
21#[UsesClass(Lexeme::class)]
22#[UsesClass(LexemeCandidates::class)]
23#[UsesClass(LexemeInput::class)]
24#[UsesClass(LexemeSequence::class)]
25#[UsesClass(ResolvedOutput::class)]
26#[UsesClass(TerminalOccurrence::class)]
27#[UsesClass(TerminalSequence::class)]
28#[UsesClass(\SqlFaker\Generation\Lexeme\SpacingConstraint::class)]
29#[UsesClass(\SqlFaker\Generation\Token\ProductionOccurrence::class)]
30#[UsesClass(\SqlFaker\Generation\Value\ValueChoices::class)]
31#[UsesClass(\SqlFaker\Generation\Value\IntegerDomain::class)]
32final class IntegerLexemeGeneratorTest extends TestCase
33{
34    #[DataProvider('providerUnsigned64')]
35    public function testAcceptsUsesTheExactUnsignedMagnitudeInsteadOfAPhpIntegerCast(string $value, bool $expected): void
36    {
37        $generator = new IntegerLexemeGenerator('UINT64', '9223372036854775808', '18446744073709551615', ['18446744073709551615'], 'scanner:uint64');
38        self::assertSame($expected, $generator->accepts($value));
39    }
40
41    /**
42     * @return iterable<string, array{string, bool}>
43     */
44    public static function providerUnsigned64(): iterable
45    {
46        yield 'minimum' => ['9223372036854775808', true];
47        yield 'maximum' => ['18446744073709551615', true];
48        yield 'below minimum' => ['9223372036854775807', false];
49        yield 'above maximum' => ['18446744073709551616', false];
50        yield 'shorter magnitude' => ['1844674407370955161', false];
51        yield 'longer magnitude' => ['100000000000000000000', false];
52        yield 'leading zeroes' => ['00018446744073709551615', true];
53        yield 'fraction' => ['9223372036854775808.0', false];
54        yield 'exponent' => ['1e19', false];
55        yield 'negative' => ['-9223372036854775808', false];
56        yield 'signed spelling' => ['+9223372036854775808', false];
57        yield 'whitespace' => [' 9223372036854775808', false];
58        yield 'empty' => ['', false];
59    }
60
61    public function testAcceptsTreatsZeroAsOneSignificantDigit(): void
62    {
63        $generator = new IntegerLexemeGenerator('NUM', '0', '2147483647', ['0'], 'scanner:num');
64        self::assertTrue($generator->accepts('0'));
65        self::assertTrue($generator->accepts('000'));
66        self::assertTrue($generator->accepts('2147483647'));
67        self::assertFalse($generator->accepts('2147483648'));
68    }
69
70    public function testGeneratePreservesExplicitLeadingZeroesAndTheirSourceOccurrence(): void
71    {
72        $generator = new IntegerLexemeGenerator('NUM', '0', '2147483647', ['1'], 'scanner:num');
73        $input = new LexemeInput(TerminalSequence::fromNames(['NUM']), 0, new ResolvedOutput(), '00037');
74        $result = $generator->generate($input);
75        self::assertNotNull($result);
76        $candidate = [...$result->sequences()][0];
77        self::assertSame('00037', $candidate->lexemes[0]->text);
78        self::assertSame($input->terminal(), $candidate->lexemes[0]->origin);
79    }
80
81    public function testGenerateSeparatesAnInvalidValueFromNonApplicability(): void
82    {
83        $generator = new IntegerLexemeGenerator('NUM', '0', '2147483647', ['1'], 'scanner:num');
84        $invalid = $generator->generate(new LexemeInput(TerminalSequence::fromNames(['NUM']), 0, new ResolvedOutput(), '2147483648'));
85        self::assertNotNull($invalid);
86        self::assertSame([], [...$invalid->sequences()]);
87        self::assertNull($generator->generate(new LexemeInput(TerminalSequence::fromNames(['LONG_NUM']), 0, new ResolvedOutput())));
88    }
89
90    public function testAcceptsSupportsTheScannerOverflowFamilyWithNoArtificialExplicitUpperBound(): void
91    {
92        $generator = new IntegerLexemeGenerator('FCONST', '2147483648', null, ['2147483648'], 'scanner:overflow');
93        self::assertFalse($generator->accepts('2147483647'));
94        self::assertTrue($generator->accepts('2147483648'));
95        self::assertTrue($generator->accepts(str_repeat('9', 1000)));
96    }
97
98    public function testGenerateBoundsExplorationOfAnUnboundedScannerFamilyWithoutEnumeratingItsValues(): void
99    {
100        $generator = new IntegerLexemeGenerator('FCONST', '2147483648', null, ['2147483648'], 'scanner:overflow');
101        $input = new LexemeInput(TerminalSequence::fromNames(['FCONST']), 0, new ResolvedOutput(), values: new \SqlFaker\Generation\Value\ValueChoices(static fn (int $count): int => $count - 1));
102        $result = $generator->generate($input);
103        self::assertNotNull($result);
104        $value = [...$result->sequences()][0]->lexemes[0]->text;
105        self::assertSame(str_repeat('0', 16) . str_repeat('9', 65), $value);
106        self::assertTrue($generator->accepts($value));
107    }
108}
109