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