diff --git a/ext/random/tests/02_engine/all_serialize_native.phpt b/ext/random/tests/02_engine/all_serialize_native.phpt index df56456d133..cadb208a664 100644 --- a/ext/random/tests/02_engine/all_serialize_native.phpt +++ b/ext/random/tests/02_engine/all_serialize_native.phpt @@ -13,6 +13,8 @@ $engines[] = new PcgOneseq128XslRr64(1234); $engines[] = new Xoshiro256StarStar(1234); foreach ($engines as $engine) { + echo $engine::class, PHP_EOL; + for ($i = 0; $i < 10_000; $i++) { $engine->generate(); } @@ -21,9 +23,7 @@ foreach ($engines as $engine) { for ($i = 0; $i < 10_000; $i++) { if ($engine->generate() !== $engine2->generate()) { - $className = $engine::class; - - die("failure: {$className} at {$i}"); + die("failure: state differs at {$i}"); } } } @@ -32,4 +32,7 @@ die('success'); ?> --EXPECT-- +Random\Engine\Mt19937 +Random\Engine\PcgOneseq128XslRr64 +Random\Engine\Xoshiro256StarStar success diff --git a/ext/random/tests/02_engine/all_serialize_user.phpt b/ext/random/tests/02_engine/all_serialize_user.phpt index de3fe2f8292..9ae742314f5 100644 --- a/ext/random/tests/02_engine/all_serialize_user.phpt +++ b/ext/random/tests/02_engine/all_serialize_user.phpt @@ -4,18 +4,11 @@ Random: Engine: Serialization of user engines must preserve the sequence count); - } -} - -final class User32 implements Engine +final class CountingEngine32 implements Engine { private int $count = 0; @@ -26,12 +19,12 @@ final class User32 implements Engine } $engines = []; -if (PHP_INT_SIZE >= 8) { - $engines[] = new User64(); -} -$engines[] = new User32(); +$engines[] = new CountingEngine32(); +$engines[] = new TestShaEngine(); foreach ($engines as $engine) { + echo $engine::class, PHP_EOL; + for ($i = 0; $i < 10_000; $i++) { $engine->generate(); } @@ -40,9 +33,7 @@ foreach ($engines as $engine) { for ($i = 0; $i < 10_000; $i++) { if ($engine->generate() !== $engine2->generate()) { - $className = $engine::class; - - die("failure: {$className} at {$i}"); + die("failure: state differs at {$i}"); } } } @@ -51,4 +42,6 @@ die('success'); ?> --EXPECT-- +CountingEngine32 +Random\Engine\Test\TestShaEngine success diff --git a/ext/random/tests/02_engine/pcgoneseq128xslrr64_jump_error.phpt b/ext/random/tests/02_engine/pcgoneseq128xslrr64_jump_error.phpt index 02be8ecb6c9..cbc6e370b17 100644 --- a/ext/random/tests/02_engine/pcgoneseq128xslrr64_jump_error.phpt +++ b/ext/random/tests/02_engine/pcgoneseq128xslrr64_jump_error.phpt @@ -16,7 +16,7 @@ try { for ($i = 0; $i < 10_000; $i++) { if ($engine->generate() !== $referenceEngine->generate()) { - die('failure: state changed'); + die("failure: state differs at {$i}"); } } diff --git a/ext/random/tests/02_engine/user_compatibility.phpt b/ext/random/tests/02_engine/user_compatibility.phpt index 11ac4514312..401541e7141 100644 --- a/ext/random/tests/02_engine/user_compatibility.phpt +++ b/ext/random/tests/02_engine/user_compatibility.phpt @@ -6,19 +6,10 @@ Random: Engine: Native engines can be wrapped without changing their sequence use Random\Engine; use Random\Engine\Mt19937; use Random\Engine\PcgOneseq128XslRr64; +use Random\Engine\Test\TestWrapperEngine; use Random\Engine\Xoshiro256StarStar; -class WrapperEngine implements Engine -{ - public function __construct(private readonly Engine $engine) - { - } - - public function generate(): string - { - return $this->engine->generate(); - } -} +require __DIR__ . "/../engines.inc"; $engines = []; $engines[] = new Mt19937(1234); @@ -26,13 +17,14 @@ $engines[] = new PcgOneseq128XslRr64(1234); $engines[] = new Xoshiro256StarStar(1234); foreach ($engines as $engine) { + echo $engine::class, PHP_EOL; + $native_engine = clone $engine; - $user_engine = new WrapperEngine(clone $engine); + $user_engine = new TestWrapperEngine(clone $engine); for ($i = 0; $i < 10_000; $i++) { if ($native_engine->generate() !== $user_engine->generate()) { - $className = $engine::class; - die("failure: {$className} at {$i}"); + die("failure: state differs at {$i}"); } } } @@ -41,4 +33,7 @@ die('success'); ?> --EXPECT-- +Random\Engine\Mt19937 +Random\Engine\PcgOneseq128XslRr64 +Random\Engine\Xoshiro256StarStar success diff --git a/ext/random/tests/02_engine/xoshiro256starstar_seed.phpt b/ext/random/tests/02_engine/xoshiro256starstar_seed.phpt index f4fa0a40b98..ec31c358fd6 100644 --- a/ext/random/tests/02_engine/xoshiro256starstar_seed.phpt +++ b/ext/random/tests/02_engine/xoshiro256starstar_seed.phpt @@ -6,17 +6,17 @@ Random: Engine: Xoshiro256StarStar: The seed parameter must work as expected use Random\Engine\Xoshiro256StarStar; echo "Random integer seed", PHP_EOL; -$engine = new Xoshiro256StarStar(\random_int(\PHP_INT_MIN, \PHP_INT_MAX)); +$engine = new Xoshiro256StarStar(random_int(PHP_INT_MIN, PHP_INT_MAX)); echo PHP_EOL, PHP_EOL; echo "Random string seed", PHP_EOL; -$engine = new Xoshiro256StarStar(\random_bytes(32)); +$engine = new Xoshiro256StarStar(random_bytes(32)); echo PHP_EOL, PHP_EOL; echo "Invalid data type", PHP_EOL; try { $engine = new Xoshiro256StarStar(1.0); -} catch (\Throwable $e) { +} catch (Throwable $e) { echo $e->getMessage(), PHP_EOL; } echo PHP_EOL, PHP_EOL; @@ -24,15 +24,15 @@ echo PHP_EOL, PHP_EOL; echo "Invalid string seed length", PHP_EOL; try { $engine = new Xoshiro256StarStar('foobar'); -} catch (\Throwable $e) { +} catch (Throwable $e) { echo $e->getMessage(), PHP_EOL; } echo PHP_EOL, PHP_EOL; echo "Null seed", PHP_EOL; try { - $engine = new Random\Engine\Xoshiro256StarStar(\str_repeat("\x00", 32)); -} catch (\Throwable $e) { + $engine = new Xoshiro256StarStar(str_repeat("\x00", 32)); +} catch (Throwable $e) { echo $e->getMessage(), PHP_EOL; } echo PHP_EOL, PHP_EOL; diff --git a/ext/random/tests/03_randomizer/basic.phpt b/ext/random/tests/03_randomizer/basic.phpt deleted file mode 100644 index a2f6378a71e..00000000000 --- a/ext/random/tests/03_randomizer/basic.phpt +++ /dev/null @@ -1,81 +0,0 @@ ---TEST-- -Random: Randomizer: basic ---FILE-- -nextInt(); - } catch (\Random\RandomException $e) { - if ($e->getMessage() !== 'Generated value exceeds size of int') { - die($engine::class . ": nextInt: failure: {$e->getMessage()}"); - } - } - } - - // getInt - for ($i = 0; $i < 1000; $i++) { - $result = $randomizer->getInt(-50, 50); - if ($result > 50 || $result < -50) { - die($engine::class . ': getInt: failure'); - } - } - - // getBytes - for ($i = 0; $i < 1000; $i++) { - $length = \random_int(1, 1024); - if (\strlen($randomizer->getBytes($length)) !== $length) { - die($engine::class . ': getBytes: failure'); - } - } - - // shuffleArray - $array = range(1, 1000); - $shuffled_array = $randomizer->shuffleArray($array); - (function () use ($array, $shuffled_array): void { - for ($i = 0; $i < count($array); $i++) { - if ($array[$i] !== $shuffled_array[$i]) { - return; - } - } - - die($engine::class . ': shuffleArray: failure'); - })(); - - // shuffleBytes - $string = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'; - $shuffled_string = $randomizer->shuffleBytes($string); - if ($string === $shuffled_string) { - die($engine::class . ': shuffleBytes: failure'); - } -} - -die('success'); -?> ---EXPECTF-- -success diff --git a/ext/random/tests/03_randomizer/pick_array_keys.phpt b/ext/random/tests/03_randomizer/compatibility_array_rand.phpt similarity index 73% rename from ext/random/tests/03_randomizer/pick_array_keys.phpt rename to ext/random/tests/03_randomizer/compatibility_array_rand.phpt index 168d01dbc42..364267faf6b 100644 --- a/ext/random/tests/03_randomizer/pick_array_keys.phpt +++ b/ext/random/tests/03_randomizer/compatibility_array_rand.phpt @@ -1,8 +1,11 @@ --TEST-- -Random: Randomizer: pickArrayKeys +Random: Randomizer: The Mt19937 engine and pickArrayKeys are consistent with array_rand() --FILE-- 0, 'bar' => 1, 'baz' => 2]; $list = range(1, 10); @@ -14,7 +17,7 @@ $mapPickTwoFunc = array_rand($map, 2); $listPickOneFunc = array_rand($list, 1); $listPickTwoFunc = array_rand($list, 2); -$randomizer = new \Random\Randomizer(new \Random\Engine\Mt19937(1234)); +$randomizer = new Randomizer(new Mt19937(1234)); [$mapPickOneMethod] = $randomizer->pickArrayKeys($map, 1); $mapPickTwoMethod = $randomizer->pickArrayKeys($map, 2); @@ -24,25 +27,30 @@ $listPickTwoMethod = $randomizer->pickArrayKeys($list, 2); if ($mapPickOneFunc !== $mapPickOneMethod) { var_dump($mapPickOneFunc, $mapPickOneMethod); - die('failure mapPickOne'); + + die('failure: mapPickOne'); } if ($mapPickTwoFunc !== $mapPickTwoMethod) { var_dump($mapPickTwoFunc, $mapPickTwoMethod); - die('failure mapPickTwo'); + + die('failure: mapPickTwo'); } if ($listPickOneFunc !== $listPickOneMethod) { var_dump($listPickOneFunc, $listPickOneMethod); - die('failure listPickOne'); + + die('failure: listPickOne'); } if ($listPickTwoFunc !== $listPickTwoMethod) { var_dump($listPickTwoFunc, $listPickOneMethod); - die('failure listPickTwo'); + + die('failure: listPickTwo'); } die('success'); + ?> ---EXPECTF-- +--EXPECT-- success diff --git a/ext/random/tests/03_randomizer/compatibility_mt.phpt b/ext/random/tests/03_randomizer/compatibility_mt.phpt deleted file mode 100644 index 9740a494d6f..00000000000 --- a/ext/random/tests/03_randomizer/compatibility_mt.phpt +++ /dev/null @@ -1,24 +0,0 @@ ---TEST-- -Random: Randomizer: Compatibility: Mt19937 ---FILE-- -nextInt() !== \mt_rand()) { - die('failure'); - } -} - -$randomizer = new \Random\Randomizer(new \Random\Engine\Mt19937(1234, \MT_RAND_MT19937)); -\mt_srand(1234, \MT_RAND_MT19937); -for ($i = 0; $i < 1000; $i++) { - if ($randomizer->nextInt() !== \mt_rand()) { - die('failure'); - } -} - -die('success'); ---EXPECT-- -success diff --git a/ext/random/tests/03_randomizer/compatibility_mt_rand.phpt b/ext/random/tests/03_randomizer/compatibility_mt_rand.phpt new file mode 100644 index 00000000000..a69c50d0cb6 --- /dev/null +++ b/ext/random/tests/03_randomizer/compatibility_mt_rand.phpt @@ -0,0 +1,49 @@ +--TEST-- +Random: Randomizer: The Mt19937 engine is a drop-in replacement for mt_rand() +--FILE-- +nextInt() !== mt_rand()) { + die("failure: state differs at {$i} for nextInt()"); + } +} + +for ($i = 0; $i < 10_000; $i++) { + if ($randomizer->getInt(0, $i) !== mt_rand(0, $i)) { + die("failure: state differs at {$i} for getInt()"); + } +} + +echo "MT_RAND_MT19937", PHP_EOL; + +$randomizer = new Randomizer(new Mt19937(1234, MT_RAND_MT19937)); +mt_srand(1234, MT_RAND_MT19937); + +for ($i = 0; $i < 10_000; $i++) { + if ($randomizer->nextInt() !== mt_rand()) { + die("failure: state differs at {$i} for nextInt()"); + } +} + +for ($i = 0; $i < 10_000; $i++) { + if ($randomizer->getInt(0, $i) !== mt_rand(0, $i)) { + die("failure: state differs at {$i} for getInt()"); + } +} + +die('success'); + +?> +--EXPECT-- +MT_RAND_PHP +MT_RAND_MT19937 +success diff --git a/ext/random/tests/03_randomizer/compatibility_user.phpt b/ext/random/tests/03_randomizer/compatibility_user.phpt index 3b4251aecbe..a4601b55ab0 100644 --- a/ext/random/tests/03_randomizer/compatibility_user.phpt +++ b/ext/random/tests/03_randomizer/compatibility_user.phpt @@ -1,80 +1,43 @@ --TEST-- -Random: Randomizer: Compatibility: user +Random: Randomizer: Native engines can be wrapped without changing their sequence --FILE-- engine->generate(); - } -}); -for ($i = 0; $i < 1000; $i++) { - $native = $native_randomizer->nextInt(); - $user = $user_randomizer->nextInt(); - if ($native !== $user) { - die("failure Mt19937 i: {$i} native: {$native} user: {$user}"); - } -} +require __DIR__ . "/../engines.inc"; -try { - $native_randomizer = new \Random\Randomizer(new \Random\Engine\PcgOneseq128XslRr64(1234)); - $user_randomizer = new \Random\Randomizer(new class () implements \Random\Engine { - public function __construct(private $engine = new \Random\Engine\PcgOneseq128XslRr64(1234)) - { - } +$engines = []; +$engines[] = new Mt19937(1234); +$engines[] = new PcgOneseq128XslRr64(1234); +$engines[] = new Xoshiro256StarStar(1234); + +foreach ($engines as $engine) { + echo $engine::class, PHP_EOL; + + $native_randomizer = new Randomizer(clone $engine); + $user_randomizer = new Randomizer(new TestWrapperEngine(clone $engine)); + + for ($i = 0; $i < 10_000; $i++) { + $native = $native_randomizer->getInt(0, $i); + $user = $user_randomizer->getInt(0, $i); - public function generate(): string - { - return $this->engine->generate(); - } - }); - - for ($i = 0; $i < 1000; $i++) { - $native = $native_randomizer->nextInt(); - $user = $user_randomizer->nextInt(); if ($native !== $user) { - die("failure PcgOneseq128XslRr64 i: {$i} native: {$native} user: {$user}"); + die("failure: state differs at {$i}"); } } -} catch (\Random\RandomException $e) { - if ($e->getMessage() !== 'Generated value exceeds size of int') { - throw $e; - } -} - -try { - $native_randomizer = new \Random\Randomizer(new \Random\Engine\Xoshiro256StarStar(1234)); - $user_randomizer = new \Random\Randomizer(new class () implements \Random\Engine { - public function __construct(private $engine = new \Random\Engine\Xoshiro256StarStar(1234)) - { - } - - public function generate(): string - { - return $this->engine->generate(); - } - }); - - for ($i = 0; $i < 1000; $i++) { - $native = $native_randomizer->nextInt(); - $user = $user_randomizer->nextInt(); - if ($native !== $user) { - die("failure Xoshiro256StarStar i: {$i} native: {$native} user: {$user}"); - } - } -} catch (\Random\RandomException $e) { - if ($e->getMessage() !== 'Generated value exceeds size of int') { - throw $e; - } } die('success'); + ?> --EXPECT-- +Random\Engine\Mt19937 +Random\Engine\PcgOneseq128XslRr64 +Random\Engine\Xoshiro256StarStar success diff --git a/ext/random/tests/03_randomizer/construct_twice.phpt b/ext/random/tests/03_randomizer/construct_twice.phpt index ed2a667e2af..7128338ade9 100644 --- a/ext/random/tests/03_randomizer/construct_twice.phpt +++ b/ext/random/tests/03_randomizer/construct_twice.phpt @@ -1,46 +1,47 @@ --TEST-- -Random: Randomizer: Disallow manually calling __construct +Random: Randomizer: Calling __construct() fails due to readonly $engine property --FILE-- __construct(); -} catch (\Error $e) { - echo $e->getMessage() . PHP_EOL; -} - -try { - $r = new \Random\Randomizer(new \Random\Engine\Xoshiro256StarStar()); - $r->__construct(new \Random\Engine\PcgOneseq128XslRr64()); -} catch (\Error $e) { - echo $e->getMessage() . PHP_EOL; -} - -try { - $r = new \Random\Randomizer(new \UserEngine()); - $r->__construct(new \UserEngine()); -} catch (\Error $e) { - echo $e->getMessage() . PHP_EOL; -} - -try { - $r = new \Random\Randomizer(new \Random\Engine\Xoshiro256StarStar()); - $r->__construct(new \UserEngine()); -} catch (\Error $e) { + (new Randomizer())->__construct(); +} catch (Error $e) { echo $e->getMessage(), PHP_EOL; } -var_dump($r->engine::class); +try { + $randomizer = new Randomizer(new Xoshiro256StarStar()); + $randomizer->__construct(new PcgOneseq128XslRr64()); +} catch (Error $e) { + echo $e->getMessage(), PHP_EOL; +} + +try { + $randomizer = new Randomizer(new TestShaEngine("1234")); + $randomizer->__construct(new TestShaEngine("1234")); +} catch (Error $e) { + echo $e->getMessage(), PHP_EOL; +} + +try { + $randomizer = new Randomizer(new Xoshiro256StarStar()); + $randomizer->__construct(new TestShaEngine("1234")); +} catch (Error $e) { + echo $e->getMessage(), PHP_EOL; +} + +var_dump($randomizer->engine::class); die('success'); + ?> --EXPECT-- Cannot modify readonly property Random\Randomizer::$engine diff --git a/ext/random/tests/03_randomizer/engine_unsafe_biased.phpt b/ext/random/tests/03_randomizer/engine_unsafe_biased.phpt new file mode 100644 index 00000000000..09fbd85b54e --- /dev/null +++ b/ext/random/tests/03_randomizer/engine_unsafe_biased.phpt @@ -0,0 +1,58 @@ +--TEST-- +Random: Randomizer: Heavily biased engines are detected and rejected +--FILE-- +getInt(0, 1234)); +} catch (Random\BrokenRandomEngineError $e) { + echo $e->getMessage(), PHP_EOL; +} + +try { + var_dump(randomizer()->nextInt()); +} catch (Random\BrokenRandomEngineError $e) { + echo $e->getMessage(), PHP_EOL; +} + +try { + var_dump(bin2hex(randomizer()->getBytes(1))); +} catch (Random\BrokenRandomEngineError $e) { + echo $e->getMessage(), PHP_EOL; +} + +try { + var_dump(randomizer()->shuffleArray(range(1, 1234))); +} catch (Random\BrokenRandomEngineError $e) { + echo $e->getMessage(), PHP_EOL; +} + +try { + var_dump(randomizer()->shuffleBytes('foobar')); +} catch (Random\BrokenRandomEngineError $e) { + echo $e->getMessage(), PHP_EOL; +} + +?> +--EXPECTF-- +Failed to generate an acceptable random number in 50 attempts +int(%d) +string(2) "ff" +Failed to generate an acceptable random number in 50 attempts +Failed to generate an acceptable random number in 50 attempts diff --git a/ext/random/tests/03_randomizer/engine_unsafe_empty_string.phpt b/ext/random/tests/03_randomizer/engine_unsafe_empty_string.phpt new file mode 100644 index 00000000000..01bd293bc05 --- /dev/null +++ b/ext/random/tests/03_randomizer/engine_unsafe_empty_string.phpt @@ -0,0 +1,58 @@ +--TEST-- +Random: Randomizer: Engines returning an empty string are detected and rejected +--FILE-- +getInt(0, 1234)); +} catch (Random\BrokenRandomEngineError $e) { + echo $e->getMessage(), PHP_EOL; +} + +try { + var_dump(randomizer()->nextInt()); +} catch (Random\BrokenRandomEngineError $e) { + echo $e->getMessage(), PHP_EOL; +} + +try { + var_dump(bin2hex(randomizer()->getBytes(1))); +} catch (Random\BrokenRandomEngineError $e) { + echo $e->getMessage(), PHP_EOL; +} + +try { + var_dump(randomizer()->shuffleArray(range(1, 1234))); +} catch (Random\BrokenRandomEngineError $e) { + echo $e->getMessage(), PHP_EOL; +} + +try { + var_dump(randomizer()->shuffleBytes('foobar')); +} catch (Random\BrokenRandomEngineError $e) { + echo $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +A random engine must return a non-empty string +A random engine must return a non-empty string +A random engine must return a non-empty string +A random engine must return a non-empty string +A random engine must return a non-empty string diff --git a/ext/random/tests/03_randomizer/engine_unsafe_exits.phpt b/ext/random/tests/03_randomizer/engine_unsafe_exits.phpt new file mode 100644 index 00000000000..cb598872d97 --- /dev/null +++ b/ext/random/tests/03_randomizer/engine_unsafe_exits.phpt @@ -0,0 +1,23 @@ +--TEST-- +Random: Randomizer: Engines calling exit() are gracefully handled +--FILE-- +getBytes(1)); + +?> +--EXPECT-- +Exit diff --git a/ext/random/tests/03_randomizer/engine_unsafe_throws.phpt b/ext/random/tests/03_randomizer/engine_unsafe_throws.phpt new file mode 100644 index 00000000000..bfb679f81a2 --- /dev/null +++ b/ext/random/tests/03_randomizer/engine_unsafe_throws.phpt @@ -0,0 +1,28 @@ +--TEST-- +Random: Randomizer: Engines throwing an exception are gracefully handled +--FILE-- +getBytes(1)); + +?> +--EXPECTF-- +Fatal error: Uncaught Exception: Error in %s:%d +Stack trace: +#0 [internal function]: ThrowingEngine->generate() +#1 %s(%d): Random\Randomizer->getBytes(1) +#2 {main} + thrown in %s on line %d diff --git a/ext/random/tests/03_randomizer/get_bytes.phpt b/ext/random/tests/03_randomizer/get_bytes.phpt deleted file mode 100644 index c6b4a59475e..00000000000 --- a/ext/random/tests/03_randomizer/get_bytes.phpt +++ /dev/null @@ -1,40 +0,0 @@ ---TEST-- -Random: Randomizer: getBytes ---FILE-- -count > 5) { - die('overflow'); - } - - return match ($this->count++) { - 0 => 'H', - 1 => 'e', - 2 => 'll', - 3 => 'o', - 4 => 'abcdefghijklmnopqrstuvwxyz', // 208 bits - 5 => 'success', - default => \random_bytes(16), - }; - } - } -); - -echo $randomizer->getBytes(5) . PHP_EOL; // Hello - -echo $randomizer->getBytes(8) . PHP_EOL; // abcdefgh (64 bits) - -die($randomizer->getBytes(7)); - -?> ---EXPECTF-- -Hello -abcdefgh -success diff --git a/ext/random/tests/03_randomizer/get_int_user.phpt b/ext/random/tests/03_randomizer/get_int_user.phpt deleted file mode 100644 index d6c99531431..00000000000 --- a/ext/random/tests/03_randomizer/get_int_user.phpt +++ /dev/null @@ -1,22 +0,0 @@ ---TEST-- -Random: Randomizer: User Engine results are correctly expanded for getInt() ---FILE-- -count++]; - } - } -); - -var_dump(bin2hex(pack('V', $randomizer->getInt(0, 0xFFFFFF)))); - -?> ---EXPECT-- -string(8) "01020300" diff --git a/ext/random/tests/03_randomizer/methods/getBytes.phpt b/ext/random/tests/03_randomizer/methods/getBytes.phpt new file mode 100644 index 00000000000..a236e9746fa --- /dev/null +++ b/ext/random/tests/03_randomizer/methods/getBytes.phpt @@ -0,0 +1,47 @@ +--TEST-- +Random: Randomizer: getBytes(): Basic functionality +--FILE-- +getBytes($i)) !== $i) { + die("failure: incorrect string length at {$i}"); + } + } +} + +die('success'); + +?> +--EXPECT-- +Random\Engine\Mt19937 +Random\Engine\Mt19937 +Random\Engine\PcgOneseq128XslRr64 +Random\Engine\Xoshiro256StarStar +Random\Engine\Secure +Random\Engine\Test\TestShaEngine +success diff --git a/ext/random/tests/03_randomizer/methods/getBytes_expansion.phpt b/ext/random/tests/03_randomizer/methods/getBytes_expansion.phpt new file mode 100644 index 00000000000..750b7870e40 --- /dev/null +++ b/ext/random/tests/03_randomizer/methods/getBytes_expansion.phpt @@ -0,0 +1,43 @@ +--TEST-- +Random: Randomizer: getBytes(): Returned bytes are consistently expanded +--FILE-- +count++) { + 0 => 'H', + 1 => 'e', + 2 => 'll', + 3 => 'o', + 4 => 'abcdefghijklmnopqrstuvwxyz', + 5 => 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', + 6 => 'success', + default => throw new \Exception('Unhandled'), + }; + } +} + +$randomizer = new Randomizer(new TestEngine()); + +// 0-3: "Hello" - Insufficient bytes are concatenated. +var_dump($randomizer->getBytes(5)); + +// 4-5: "abcdefghABC" - Returned values are truncated to 64-bits for technical reasons, thus dropping i-z. +var_dump($randomizer->getBytes(11)); + +// 6: "success" +var_dump($randomizer->getBytes(7)); + +?> +--EXPECT-- +string(5) "Hello" +string(11) "abcdefghABC" +string(7) "success" diff --git a/ext/random/tests/03_randomizer/methods/getInt.phpt b/ext/random/tests/03_randomizer/methods/getInt.phpt new file mode 100644 index 00000000000..faa098ade13 --- /dev/null +++ b/ext/random/tests/03_randomizer/methods/getInt.phpt @@ -0,0 +1,54 @@ +--TEST-- +Random: Randomizer: getInt(): Basic functionality +--FILE-- +getInt(-$i, $i); + + if ($result > $i || $result < -$i) { + die("failure: out of range at {$i}"); + } + } + + // Test that extreme ranges do not throw. + for ($i = 0; $i < 10_000; $i++) { + $randomizer->getInt(PHP_INT_MIN, PHP_INT_MAX); + } +} + +die('success'); + +?> +--EXPECT-- +Random\Engine\Mt19937 +Random\Engine\Mt19937 +Random\Engine\PcgOneseq128XslRr64 +Random\Engine\Xoshiro256StarStar +Random\Engine\Secure +Random\Engine\Test\TestShaEngine +success diff --git a/ext/random/tests/03_randomizer/methods/getInt_expansion_32.phpt b/ext/random/tests/03_randomizer/methods/getInt_expansion_32.phpt new file mode 100644 index 00000000000..6e93208138c --- /dev/null +++ b/ext/random/tests/03_randomizer/methods/getInt_expansion_32.phpt @@ -0,0 +1,25 @@ +--TEST-- +Random: Randomizer: getInt(): Returned values with insufficient bits are correctly expanded +--FILE-- +count++]; + } +} + +$randomizer = new Randomizer(new ByteEngine()); + +var_dump(bin2hex(pack('V', $randomizer->getInt(0, 0x00FF_FFFF)))); + +?> +--EXPECT-- +string(8) "01020300" diff --git a/ext/random/tests/03_randomizer/methods/getInt_expansion_64.phpt b/ext/random/tests/03_randomizer/methods/getInt_expansion_64.phpt new file mode 100644 index 00000000000..87c6bfeafeb --- /dev/null +++ b/ext/random/tests/03_randomizer/methods/getInt_expansion_64.phpt @@ -0,0 +1,27 @@ +--TEST-- +Random: Randomizer: getInt(): Returned values with insufficient bits are correctly expanded (64 Bit) +--SKIPIF-- + +--FILE-- +count++]; + } +} + +$randomizer = new Randomizer(new ByteEngine()); + +var_dump(bin2hex(pack('P', $randomizer->getInt(0, 0x00FF_FFFF_FFFF_FFFF)))); + +?> +--EXPECT-- +string(16) "0102030405060700" diff --git a/ext/random/tests/03_randomizer/gh9415.phpt b/ext/random/tests/03_randomizer/methods/getInt_gh9415.phpt similarity index 100% rename from ext/random/tests/03_randomizer/gh9415.phpt rename to ext/random/tests/03_randomizer/methods/getInt_gh9415.phpt diff --git a/ext/random/tests/03_randomizer/methods/nextInt.phpt b/ext/random/tests/03_randomizer/methods/nextInt.phpt new file mode 100644 index 00000000000..fd4460cb811 --- /dev/null +++ b/ext/random/tests/03_randomizer/methods/nextInt.phpt @@ -0,0 +1,50 @@ +--TEST-- +Random: Randomizer: nextInt(): Basic functionality +--FILE-- +nextInt(); + } catch (\Random\RandomException $e) { + if ($e->getMessage() !== 'Generated value exceeds size of int' || PHP_INT_SIZE !== 4) { + throw $e; + } + } + } +} + +die('success'); + +?> +--EXPECT-- +Random\Engine\Mt19937 +Random\Engine\Mt19937 +Random\Engine\PcgOneseq128XslRr64 +Random\Engine\Xoshiro256StarStar +Random\Engine\Secure +Random\Engine\Test\TestShaEngine +success diff --git a/ext/random/tests/03_randomizer/methods/nextInt_64_engine_on_32_platform.phpt b/ext/random/tests/03_randomizer/methods/nextInt_64_engine_on_32_platform.phpt new file mode 100644 index 00000000000..2a408e566e0 --- /dev/null +++ b/ext/random/tests/03_randomizer/methods/nextInt_64_engine_on_32_platform.phpt @@ -0,0 +1,21 @@ +--TEST-- +Random: Randomizer: nextInt(): Throws for 64 bit engines on 32 bit platforms +--SKIPIF-- + +--FILE-- +nextInt()); +} catch (Random\RandomException $e) { + echo $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Generated value exceeds size of int diff --git a/ext/random/tests/03_randomizer/methods/pickArrayKeys.phpt b/ext/random/tests/03_randomizer/methods/pickArrayKeys.phpt new file mode 100644 index 00000000000..4178455898d --- /dev/null +++ b/ext/random/tests/03_randomizer/methods/pickArrayKeys.phpt @@ -0,0 +1,84 @@ +--TEST-- +Random: Randomizer: pickArrayKeys(): Basic functionality +--FILE-- +pickArrayKeys($array1, $i); + + if (array_unique($result) !== $result) { + die("failure: duplicates returned at {$i} for array1"); + } + + if (array_diff($result, array_keys($array1)) !== []) { + die("failure: non-keys returned at {$i} for array1"); + } + + $result = $randomizer->pickArrayKeys($array2, $i); + + if (array_unique($result) !== $result) { + die("failure: duplicates returned at {$i} for array2"); + } + + if (array_diff($result, array_keys($array2)) !== []) { + die("failure: non-keys returned at {$i} for array2"); + } + + $result = $randomizer->pickArrayKeys($array3, $i); + + if (array_unique($result) !== $result) { + die("failure: duplicates returned at {$i} for array3"); + } + + if (array_diff($result, array_keys($array3)) !== []) { + die("failure: non-keys returned at {$i} for array3"); + } + } +} + +die('success'); + +?> +--EXPECT-- +Random\Engine\Mt19937 +Random\Engine\Mt19937 +Random\Engine\PcgOneseq128XslRr64 +Random\Engine\Xoshiro256StarStar +Random\Engine\Secure +Random\Engine\Test\TestShaEngine +success diff --git a/ext/random/tests/03_randomizer/methods/pickArrayKeys_error.phpt b/ext/random/tests/03_randomizer/methods/pickArrayKeys_error.phpt new file mode 100644 index 00000000000..15526327b09 --- /dev/null +++ b/ext/random/tests/03_randomizer/methods/pickArrayKeys_error.phpt @@ -0,0 +1,49 @@ +--TEST-- +Random: Randomizer: pickArrayKeys(): Parameters are correctly validated +--FILE-- +pickArrayKeys("foo", 2)); +} catch (TypeError $e) { + echo $e->getMessage(), PHP_EOL; +} + +try { + var_dump(randomizer()->pickArrayKeys([], 0)); +} catch (ValueError $e) { + echo $e->getMessage(), PHP_EOL; +} + +try { + var_dump(randomizer()->pickArrayKeys(range(1, 3), 0)); +} catch (ValueError $e) { + echo $e->getMessage(), PHP_EOL; +} + +try { + var_dump(randomizer()->pickArrayKeys(range(1, 3), -1)); +} catch (ValueError $e) { + echo $e->getMessage(), PHP_EOL; +} + +try { + var_dump(randomizer()->pickArrayKeys(range(1, 3), 10)); +} catch (ValueError $e) { + echo $e->getMessage(), PHP_EOL; +} + +?> +--EXPECTF-- +Random\Randomizer::pickArrayKeys(): Argument #1 ($array) must be of type array, string given +Random\Randomizer::pickArrayKeys(): Argument #1 ($array) cannot be empty +Random\Randomizer::pickArrayKeys(): Argument #2 ($num) must be between 1 and the number of elements in argument #1 ($array) +Random\Randomizer::pickArrayKeys(): Argument #2 ($num) must be between 1 and the number of elements in argument #1 ($array) +Random\Randomizer::pickArrayKeys(): Argument #2 ($num) must be between 1 and the number of elements in argument #1 ($array) diff --git a/ext/random/tests/03_randomizer/methods/shuffleArray.phpt b/ext/random/tests/03_randomizer/methods/shuffleArray.phpt new file mode 100644 index 00000000000..c0f6d17fecd --- /dev/null +++ b/ext/random/tests/03_randomizer/methods/shuffleArray.phpt @@ -0,0 +1,53 @@ +--TEST-- +Random: Randomizer: shuffleArray(): Basic functionality +--FILE-- +shuffleArray($array); + + sort($result); + + if ($result !== $array) { + die("failure: not a permutation at {$i}"); + } + } +} + +die('success'); + +?> +--EXPECT-- +Random\Engine\Mt19937 +Random\Engine\Mt19937 +Random\Engine\PcgOneseq128XslRr64 +Random\Engine\Xoshiro256StarStar +Random\Engine\Secure +Random\Engine\Test\TestShaEngine +success diff --git a/ext/random/tests/03_randomizer/methods/shuffleBytes.phpt b/ext/random/tests/03_randomizer/methods/shuffleBytes.phpt new file mode 100644 index 00000000000..d16168d32fe --- /dev/null +++ b/ext/random/tests/03_randomizer/methods/shuffleBytes.phpt @@ -0,0 +1,60 @@ +--TEST-- +Random: Randomizer: shuffleBytes(): Basic functionality +--FILE-- +shuffleBytes($bytes); + + $result = sort_bytes($result); + + if ($result !== $bytes) { + die("failure: not a permutation at {$i}"); + } + } +} + +die('success'); + +?> +--EXPECT-- +Random\Engine\Mt19937 +Random\Engine\Mt19937 +Random\Engine\PcgOneseq128XslRr64 +Random\Engine\Xoshiro256StarStar +Random\Engine\Secure +Random\Engine\Test\TestShaEngine +success diff --git a/ext/random/tests/03_randomizer/nextint_error.phpt b/ext/random/tests/03_randomizer/nextint_error.phpt deleted file mode 100644 index 5f9f8bdc77c..00000000000 --- a/ext/random/tests/03_randomizer/nextint_error.phpt +++ /dev/null @@ -1,18 +0,0 @@ ---TEST-- -Random: Randomizer: nextInt() throws for too large values on 32 Bit ---SKIPIF-- - ---FILE-- -nextInt()); -} catch (\Random\RandomException $e) { - echo $e->getMessage(), PHP_EOL; -} - -?> ---EXPECT-- -Generated value exceeds size of int diff --git a/ext/random/tests/03_randomizer/pick_array_keys_error.phpt b/ext/random/tests/03_randomizer/pick_array_keys_error.phpt deleted file mode 100644 index 98abc82a550..00000000000 --- a/ext/random/tests/03_randomizer/pick_array_keys_error.phpt +++ /dev/null @@ -1,52 +0,0 @@ ---TEST-- -Random: Randomizer: pickArrayKeys error pattern ---FILE-- -pickArrayKeys([], 0)); -} catch (\ValueError $e) { - echo $e->getMessage() . "\n"; -} - -try { - var_dump((new \Random\Randomizer())->pickArrayKeys(range(1, 3), 0)); -} catch (\ValueError $e) { - echo $e->getMessage() . "\n"; -} - -try { - var_dump((new \Random\Randomizer())->pickArrayKeys(range(1, 3), -1)); -} catch (\ValueError $e) { - echo $e->getMessage() . "\n"; -} - -try { - var_dump((new \Random\Randomizer())->pickArrayKeys(range(1, 3), 10)); -} catch (\ValueError $e) { - echo $e->getMessage() . "\n"; -} - -var_dump((new \Random\Randomizer())->pickArrayKeys(range(1, 3), 3)); -var_dump((new \Random\Randomizer())->pickArrayKeys(range(1, 3), 2)); - -?> ---EXPECTF-- -Random\Randomizer::pickArrayKeys(): Argument #1 ($array) cannot be empty -Random\Randomizer::pickArrayKeys(): Argument #2 ($num) must be between 1 and the number of elements in argument #1 ($array) -Random\Randomizer::pickArrayKeys(): Argument #2 ($num) must be between 1 and the number of elements in argument #1 ($array) -Random\Randomizer::pickArrayKeys(): Argument #2 ($num) must be between 1 and the number of elements in argument #1 ($array) -array(3) { - [0]=> - int(%d) - [1]=> - int(%d) - [2]=> - int(%d) -} -array(2) { - [0]=> - int(%d) - [1]=> - int(%d) -} diff --git a/ext/random/tests/03_randomizer/readonly.phpt b/ext/random/tests/03_randomizer/readonly.phpt index f1f4a461714..f5cf7cc0dd5 100644 --- a/ext/random/tests/03_randomizer/readonly.phpt +++ b/ext/random/tests/03_randomizer/readonly.phpt @@ -1,43 +1,30 @@ --TEST-- -Random: Randomizer: readonly engine +Random: Randomizer: The engine property must be readonly --FILE-- engine; -if ($one->engine->generate() !== $one_ng_clone->generate()) { - die('invalid result'); -} +$randomizer = new Randomizer(new PcgOneseq128XslRr64(1234)); +$referenceRandomizer = new Randomizer(new PcgOneseq128XslRr64(1234)); try { - $one->engine = $one_ng_clone; -} catch (\Throwable $e) { - echo $e->getMessage() . PHP_EOL; + $randomizer->engine = new Xoshiro256StarStar(1234); +} catch (Throwable $e) { + echo $e->getMessage(), PHP_EOL; } -$two = new \Random\Randomizer( - new \Random\Engine\Secure() -); - -try { - $two_ng_clone = clone $two->engine; -} catch (\Throwable $e) { - echo $e->getMessage() . PHP_EOL; +for ($i = 0; $i < 10_000; $i++) { + if ($randomizer->getInt(0, $i) !== $referenceRandomizer->getInt(0, $i)) { + die("failure: state differs at {$i}"); + } } -try { - $two->engine = $one_ng_clone; -} catch (\Throwable $e) { - echo $e->getMessage() . PHP_EOL; -} +die('success'); -die('success') ?> --EXPECT-- Cannot modify readonly property Random\Randomizer::$engine -Trying to clone an uncloneable object of class Random\Engine\Secure -Cannot modify readonly property Random\Randomizer::$engine success diff --git a/ext/random/tests/03_randomizer/serialize.phpt b/ext/random/tests/03_randomizer/serialize.phpt index 37841a0db10..b02bb93c827 100644 --- a/ext/random/tests/03_randomizer/serialize.phpt +++ b/ext/random/tests/03_randomizer/serialize.phpt @@ -1,57 +1,50 @@ --TEST-- -Random: Randomizer: serialize +Random: Randomizer: Serialization of the Randomizer must preserve the sequence --FILE-- getInt(\PHP_INT_MIN, \PHP_INT_MAX); - try { - $randomizer2 = unserialize(serialize($randomizer)); - } catch (\Exception $e) { - echo $e->getMessage() . PHP_EOL; - continue; + echo $engine::class, PHP_EOL; + + $randomizer = new Randomizer($engine); + + for ($i = 0; $i < 10_000; $i++) { + $randomizer->getInt(0, $i); } - if ($randomizer->getInt(\PHP_INT_MIN, \PHP_INT_MAX) !== $randomizer2->getInt(\PHP_INT_MIN, \PHP_INT_MAX)) { - die($engine::class . ': failure.'); - } + $randomizer2 = unserialize(serialize($randomizer)); - echo $engine::class . ': success' . PHP_EOL; + for ($i = 0; $i < 10_000; $i++) { + if ($randomizer->getInt(0, $i) !== $randomizer2->getInt(0, $i)) { + $className = $engine::class; + + die("failure: state differs at {$i}"); + } + } } die('success'); ?> ---EXPECTF-- -Random\Engine\Mt19937: success -Random\Engine\Mt19937: success -Random\Engine\PcgOneseq128XslRr64: success -Random\Engine\Xoshiro256StarStar: success -Serialization of 'Random\Engine\Secure' is not allowed -Serialization of 'Random\Engine@anonymous' is not allowed -UserEngine: success +--EXPECT-- +Random\Engine\Mt19937 +Random\Engine\Mt19937 +Random\Engine\PcgOneseq128XslRr64 +Random\Engine\Xoshiro256StarStar +Random\Engine\Test\TestShaEngine success diff --git a/ext/random/tests/03_randomizer/serialize_disallowed.phpt b/ext/random/tests/03_randomizer/serialize_disallowed.phpt new file mode 100644 index 00000000000..06f8ea2cb09 --- /dev/null +++ b/ext/random/tests/03_randomizer/serialize_disallowed.phpt @@ -0,0 +1,17 @@ +--TEST-- +Random: Randomizer: Serialization of the Randomizer fails if the engine is not serializable +--FILE-- +getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Serialization of 'Random\Engine\Secure' is not allowed diff --git a/ext/random/tests/03_randomizer/user_exits.phpt b/ext/random/tests/03_randomizer/user_exits.phpt deleted file mode 100644 index 6d6f5636cbe..00000000000 --- a/ext/random/tests/03_randomizer/user_exits.phpt +++ /dev/null @@ -1,19 +0,0 @@ ---TEST-- -Random: Randomizer: User: Engine exits ---FILE-- -getBytes(1)); - -?> ---EXPECT-- -Exit diff --git a/ext/random/tests/03_randomizer/user_throws.phpt b/ext/random/tests/03_randomizer/user_throws.phpt deleted file mode 100644 index 41cd004b638..00000000000 --- a/ext/random/tests/03_randomizer/user_throws.phpt +++ /dev/null @@ -1,24 +0,0 @@ ---TEST-- -Random: Randomizer: User: Engine throws ---FILE-- -getBytes(1)); - -?> ---EXPECTF-- -Fatal error: Uncaught Exception: Error in %s:%d -Stack trace: -#0 [internal function]: Random\Engine@anonymous->generate() -#1 %s(%d): Random\Randomizer->getBytes(1) -#2 {main} - thrown in %s on line %d diff --git a/ext/random/tests/03_randomizer/user_unsafe.phpt b/ext/random/tests/03_randomizer/user_unsafe.phpt deleted file mode 100644 index 6e6a8151fdf..00000000000 --- a/ext/random/tests/03_randomizer/user_unsafe.phpt +++ /dev/null @@ -1,141 +0,0 @@ ---TEST-- -Random: Randomizer: User: Engine unsafe ---FILE-- -getInt(0, 123)); - } catch (Throwable $e) { - echo $e, PHP_EOL; - } - - echo PHP_EOL, "-------", PHP_EOL, PHP_EOL; - - try { - var_dump((new Randomizer(new $engine()))->nextInt()); - } catch (Throwable $e) { - echo $e, PHP_EOL; - } - - echo PHP_EOL, "-------", PHP_EOL, PHP_EOL; - - try { - var_dump(bin2hex((new Randomizer(new $engine()))->getBytes(1))); - } catch (Throwable $e) { - echo $e, PHP_EOL; - } - - echo PHP_EOL, "-------", PHP_EOL, PHP_EOL; - - try { - var_dump((new Randomizer(new $engine()))->shuffleArray(\range(1, 10))); - } catch (Throwable $e) { - echo $e, PHP_EOL; - } - - echo PHP_EOL, "-------", PHP_EOL, PHP_EOL; - - try { - var_dump((new Randomizer(new $engine()))->shuffleBytes('foobar')); - } catch (Throwable $e) { - echo $e, PHP_EOL; - } - - echo PHP_EOL, "=====================", PHP_EOL; -} - -?> ---EXPECTF-- -===================== -EmptyStringEngine -===================== - -Random\BrokenRandomEngineError: A random engine must return a non-empty string in %s:%d -Stack trace: -#0 %s(%d): Random\Randomizer->getInt(0, 123) -#1 {main} - -------- - -Random\BrokenRandomEngineError: A random engine must return a non-empty string in %s:%d -Stack trace: -#0 %s(%d): Random\Randomizer->nextInt() -#1 {main} - -------- - -Random\BrokenRandomEngineError: A random engine must return a non-empty string in %s:%d -Stack trace: -#0 %s(%d): Random\Randomizer->getBytes(1) -#1 {main} - -------- - -Random\BrokenRandomEngineError: A random engine must return a non-empty string in %s:%d -Stack trace: -#0 %s(%d): Random\Randomizer->shuffleArray(Array) -#1 {main} - -------- - -Random\BrokenRandomEngineError: A random engine must return a non-empty string in %s:%d -Stack trace: -#0 %s(%d): Random\Randomizer->shuffleBytes('foobar') -#1 {main} - -===================== -HeavilyBiasedEngine -===================== - -Random\BrokenRandomEngineError: Failed to generate an acceptable random number in 50 attempts in %s:%d -Stack trace: -#0 %s(%d): Random\Randomizer->getInt(0, 123) -#1 {main} - -------- - -int(%d) - -------- - -string(2) "ff" - -------- - -Random\BrokenRandomEngineError: Failed to generate an acceptable random number in 50 attempts in %s:%d -Stack trace: -#0 %s(%d): Random\Randomizer->shuffleArray(Array) -#1 {main} - -------- - -Random\BrokenRandomEngineError: Failed to generate an acceptable random number in 50 attempts in %s:%d -Stack trace: -#0 %s(%d): Random\Randomizer->shuffleBytes('foobar') -#1 {main} - -===================== diff --git a/ext/random/tests/engines.inc b/ext/random/tests/engines.inc new file mode 100644 index 00000000000..909f581d775 --- /dev/null +++ b/ext/random/tests/engines.inc @@ -0,0 +1,74 @@ +state = $state; + } else { + $this->state = random_bytes(20); + } + } + + public function generate(): string + { + $this->state = sha1($this->state, true); + + return substr($this->state, 0, 8); + } +} + +final class TestWrapperEngine implements Engine +{ + public function __construct(private readonly Engine $engine) + { + } + + public function generate(): string + { + return $this->engine->generate(); + } +} + +final class TestXoshiro128PlusPlusEngine implements Engine +{ + public function __construct( + private int $s0, + private int $s1, + private int $s2, + private int $s3 + ) { + } + + private static function rotl($x, $k) + { + return (($x << $k) | ($x >> (32 - $k))) & 0xFFFFFFFF; + } + + public function generate(): string + { + $result = (self::rotl(($this->s0 + $this->s3) & 0xFFFFFFFF, 7) + $this->s0) & 0xFFFFFFFF; + + $t = ($this->s1 << 9) & 0xFFFFFFFF; + + $this->s2 ^= $this->s0; + $this->s3 ^= $this->s1; + $this->s1 ^= $this->s2; + $this->s0 ^= $this->s3; + + $this->s2 ^= $t; + + $this->s3 = self::rotl($this->s3, 11); + + return pack('V', $result); + } +} + +?>