diff --git a/ext/random/tests/02_engine/all_serialize_native.phpt b/ext/random/tests/02_engine/all_serialize_native.phpt index df56456d1338d..cadb208a66422 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 de3fe2f8292aa..9ae742314f5af 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); - } -} +require __DIR__ . "/../engines.inc"; -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 02be8ecb6c92f..cbc6e370b1733 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 11ac451431204..401541e7141b0 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 f4fa0a40b9898..ec31c358fd68e 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 a2f6378a71eb1..0000000000000 --- 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 168d01dbc42dd..364267faf6ba4 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 9740a494d6fe6..0000000000000 --- 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 0000000000000..a69c50d0cb622 --- /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 3b4251aecbe72..a4601b55ab04a 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); - 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}"); - } - } -} catch (\Random\RandomException $e) { - if ($e->getMessage() !== 'Generated value exceeds size of int') { - throw $e; - } -} +foreach ($engines as $engine) { + echo $engine::class, PHP_EOL; -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)) - { - } + $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 Xoshiro256StarStar 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; - } } 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 ed2a667e2af11..7128338ade973 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; + (new Randomizer())->__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; + $randomizer = new Randomizer(new Xoshiro256StarStar()); + $randomizer->__construct(new 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; + $randomizer = new Randomizer(new TestShaEngine("1234")); + $randomizer->__construct(new TestShaEngine("1234")); +} catch (Error $e) { + echo $e->getMessage(), PHP_EOL; } try { - $r = new \Random\Randomizer(new \Random\Engine\Xoshiro256StarStar()); - $r->__construct(new \UserEngine()); -} catch (\Error $e) { + $randomizer = new Randomizer(new Xoshiro256StarStar()); + $randomizer->__construct(new TestShaEngine("1234")); +} catch (Error $e) { echo $e->getMessage(), PHP_EOL; } -var_dump($r->engine::class); +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 0000000000000..09fbd85b54eb0 --- /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 0000000000000..01bd293bc0508 --- /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 0000000000000..cb598872d9773 --- /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 0000000000000..bfb679f81a201 --- /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 c6b4a59475e72..0000000000000 --- 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 d6c995314319c..0000000000000 --- 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 0000000000000..a236e9746fa19 --- /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 0000000000000..750b7870e4001 --- /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 0000000000000..faa098ade1364 --- /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 0000000000000..6e93208138c5f --- /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 0000000000000..87c6bfeafeb85 --- /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 0000000000000..fd4460cb811ce --- /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 0000000000000..2a408e566e0b4 --- /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 0000000000000..4178455898df4 --- /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 0000000000000..15526327b095c --- /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 0000000000000..c0f6d17fecddf --- /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 0000000000000..d16168d32fe2d --- /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 5f9f8bdc77cf8..0000000000000 --- 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 98abc82a55018..0000000000000 --- 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 f1f4a461714e4..f5cf7cc0dd5b0 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 37841a0db106f..b02bb93c827b3 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); - if ($randomizer->getInt(\PHP_INT_MIN, \PHP_INT_MAX) !== $randomizer2->getInt(\PHP_INT_MIN, \PHP_INT_MAX)) { - die($engine::class . ': failure.'); + for ($i = 0; $i < 10_000; $i++) { + $randomizer->getInt(0, $i); } - echo $engine::class . ': success' . PHP_EOL; + $randomizer2 = unserialize(serialize($randomizer)); + + 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 0000000000000..06f8ea2cb09ae --- /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 6d6f5636cbec4..0000000000000 --- 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 41cd004b63879..0000000000000 --- 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 6e6a8151fdf11..0000000000000 --- 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 0000000000000..909f581d775a1 --- /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); + } +} + +?>