1
0
mirror of https://github.com/php/php-src.git synced 2026-04-11 10:03:18 +02:00

Merge branch 'PHP-8.2'

* PHP-8.2:
  Unify structure for ext/random's randomizer tests (#9410)
This commit is contained in:
Tim Düsterhus
2022-08-31 19:23:06 +02:00
38 changed files with 976 additions and 634 deletions

View File

@@ -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

View File

@@ -4,18 +4,11 @@ Random: Engine: Serialization of user engines must preserve the sequence
<?php
use Random\Engine;
use Random\Engine\Test\TestShaEngine;
final class User64 implements Engine
{
private int $count = 0;
require __DIR__ . "/../engines.inc";
public function generate(): string
{
return pack('P*', ++$this->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

View File

@@ -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}");
}
}

View File

@@ -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

View File

@@ -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;

View File

@@ -1,81 +0,0 @@
--TEST--
Random: Randomizer: basic
--FILE--
<?php
$engines = [];
$engines[] = new Random\Engine\Mt19937(\random_int(\PHP_INT_MIN, \PHP_INT_MAX), MT_RAND_MT19937);
$engines[] = new Random\Engine\Mt19937(\random_int(\PHP_INT_MIN, \PHP_INT_MAX), MT_RAND_PHP);
$engines[] = new Random\Engine\PcgOneseq128XslRr64(\random_int(\PHP_INT_MIN, \PHP_INT_MAX));
$engines[] = new Random\Engine\Xoshiro256StarStar(\random_int(\PHP_INT_MIN, \PHP_INT_MAX));
$engines[] = new Random\Engine\Secure();
$engines[] = new class () implements Random\Engine {
public function generate(): string
{
return \random_bytes(16);
}
};
class UserEngine implements Random\Engine
{
public function generate(): string
{
return \random_bytes(16);
}
}
$engines[] = new UserEngine();
foreach ($engines as $engine) {
$randomizer = new Random\Randomizer($engine);
// nextInt
for ($i = 0; $i < 1000; $i++) {
try {
$randomizer->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

View File

@@ -1,8 +1,11 @@
--TEST--
Random: Randomizer: pickArrayKeys
Random: Randomizer: The Mt19937 engine and pickArrayKeys are consistent with array_rand()
--FILE--
<?php
use Random\Engine\Mt19937;
use Random\Randomizer;
$map = ['foo' => 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

View File

@@ -1,24 +0,0 @@
--TEST--
Random: Randomizer: Compatibility: Mt19937
--FILE--
<?php
$randomizer = new \Random\Randomizer(new \Random\Engine\Mt19937(1234, \MT_RAND_PHP));
\mt_srand(1234, \MT_RAND_PHP);
for ($i = 0; $i < 1000; $i++) {
if ($randomizer->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

View File

@@ -0,0 +1,49 @@
--TEST--
Random: Randomizer: The Mt19937 engine is a drop-in replacement for mt_rand()
--FILE--
<?php
use Random\Randomizer;
use Random\Engine\Mt19937;
echo "MT_RAND_PHP", PHP_EOL;
$randomizer = new Randomizer(new Mt19937(1234, MT_RAND_PHP));
mt_srand(1234, MT_RAND_PHP);
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()");
}
}
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

View File

@@ -1,80 +1,43 @@
--TEST--
Random: Randomizer: Compatibility: user
Random: Randomizer: Native engines can be wrapped without changing their sequence
--FILE--
<?php
$native_randomizer = new \Random\Randomizer(new \Random\Engine\Mt19937(1234));
$user_randomizer = new \Random\Randomizer(new class () implements \Random\Engine {
public function __construct(private $engine = new \Random\Engine\Mt19937(1234))
{
}
use Random\Engine;
use Random\Engine\Mt19937;
use Random\Engine\PcgOneseq128XslRr64;
use Random\Engine\Test\TestWrapperEngine;
use Random\Engine\Xoshiro256StarStar;
use Random\Randomizer;
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 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

View File

@@ -1,46 +1,47 @@
--TEST--
Random: Randomizer: Disallow manually calling __construct
Random: Randomizer: Calling __construct() fails due to readonly $engine property
--FILE--
<?php
final class UserEngine implements \Random\Engine
{
public function generate(): string
{
return \random_byte(4); /* 32-bit */
}
}
use Random\Engine;
use Random\Engine\PcgOneseq128XslRr64;
use Random\Engine\Test\TestShaEngine;
use Random\Engine\Xoshiro256StarStar;
use Random\Randomizer;
require __DIR__ . "/../engines.inc";
try {
(new \Random\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;
}
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

View File

@@ -0,0 +1,58 @@
--TEST--
Random: Randomizer: Heavily biased engines are detected and rejected
--FILE--
<?php
use Random\Engine;
use Random\Randomizer;
final class HeavilyBiasedEngine implements Engine
{
public function generate(): string
{
return str_repeat("\xff", PHP_INT_SIZE);
}
}
function randomizer(): Randomizer
{
return new Randomizer(new HeavilyBiasedEngine());
}
try {
var_dump(randomizer()->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

View File

@@ -0,0 +1,58 @@
--TEST--
Random: Randomizer: Engines returning an empty string are detected and rejected
--FILE--
<?php
use Random\Engine;
use Random\Randomizer;
final class EmptyStringEngine implements Engine
{
public function generate(): string
{
return '';
}
}
function randomizer(): Randomizer
{
return new Randomizer(new EmptyStringEngine());
}
try {
var_dump(randomizer()->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

View File

@@ -0,0 +1,23 @@
--TEST--
Random: Randomizer: Engines calling exit() are gracefully handled
--FILE--
<?php
use Random\Engine;
use Random\Randomizer;
final class ExitingEngine implements Engine
{
public function generate(): string
{
exit("Exit\n");
}
}
$randomizer = new Randomizer(new ExitingEngine());
var_dump($randomizer->getBytes(1));
?>
--EXPECT--
Exit

View File

@@ -0,0 +1,28 @@
--TEST--
Random: Randomizer: Engines throwing an exception are gracefully handled
--FILE--
<?php
use Random\Engine;
use Random\Randomizer;
final class ThrowingEngine implements Engine
{
public function generate(): string
{
throw new Exception('Error');
}
}
$randomizer = new Randomizer(new ThrowingEngine());
var_dump($randomizer->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

View File

@@ -1,40 +0,0 @@
--TEST--
Random: Randomizer: getBytes
--FILE--
<?php
$randomizer = new \Random\Randomizer (
new class () implements \Random\Engine
{
private int $count = 0;
public function generate(): string
{
if ($this->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

View File

@@ -1,22 +0,0 @@
--TEST--
Random: Randomizer: User Engine results are correctly expanded for getInt()
--FILE--
<?php
$randomizer = new \Random\Randomizer (
new class () implements \Random\Engine
{
public $count = 0;
public function generate(): string
{
return "\x01\x02\x03\x04\x05\x06\x07\x08"[$this->count++];
}
}
);
var_dump(bin2hex(pack('V', $randomizer->getInt(0, 0xFFFFFF))));
?>
--EXPECT--
string(8) "01020300"

View File

@@ -0,0 +1,47 @@
--TEST--
Random: Randomizer: getBytes(): Basic functionality
--FILE--
<?php
use Random\Engine;
use Random\Engine\Mt19937;
use Random\Engine\PcgOneseq128XslRr64;
use Random\Engine\Secure;
use Random\Engine\Test\TestShaEngine;
use Random\Engine\Xoshiro256StarStar;
use Random\Randomizer;
require __DIR__ . "/../../engines.inc";
$engines = [];
$engines[] = new Mt19937(null, MT_RAND_MT19937);
$engines[] = new Mt19937(null, MT_RAND_PHP);
$engines[] = new PcgOneseq128XslRr64();
$engines[] = new Xoshiro256StarStar();
$engines[] = new Secure();
$engines[] = new TestShaEngine();
foreach ($engines as $engine) {
echo $engine::class, PHP_EOL;
$randomizer = new Randomizer($engine);
// Using 10_000 is very slow.
for ($i = 1; $i < 1_000; $i++) {
if (\strlen($randomizer->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

View File

@@ -0,0 +1,43 @@
--TEST--
Random: Randomizer: getBytes(): Returned bytes are consistently expanded
--FILE--
<?php
use Random\Engine;
use Random\Randomizer;
final class TestEngine implements Engine
{
private int $count = 0;
public function generate(): string
{
return match ($this->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"

View File

@@ -0,0 +1,54 @@
--TEST--
Random: Randomizer: getInt(): Basic functionality
--FILE--
<?php
use Random\Engine;
use Random\Engine\Mt19937;
use Random\Engine\PcgOneseq128XslRr64;
use Random\Engine\Secure;
use Random\Engine\Test\TestShaEngine;
use Random\Engine\Xoshiro256StarStar;
use Random\Randomizer;
require __DIR__ . "/../../engines.inc";
$engines = [];
$engines[] = new Mt19937(null, MT_RAND_MT19937);
$engines[] = new Mt19937(null, MT_RAND_PHP);
$engines[] = new PcgOneseq128XslRr64();
$engines[] = new Xoshiro256StarStar();
$engines[] = new Secure();
$engines[] = new TestShaEngine();
foreach ($engines as $engine) {
echo $engine::class, PHP_EOL;
$randomizer = new Randomizer($engine);
// Basic range test.
for ($i = 0; $i < 10_000; $i++) {
$result = $randomizer->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

View File

@@ -0,0 +1,25 @@
--TEST--
Random: Randomizer: getInt(): Returned values with insufficient bits are correctly expanded
--FILE--
<?php
use Random\Engine;
use Random\Randomizer;
final class ByteEngine implements Engine
{
public $count = 0;
public function generate(): string
{
return "\x01\x02\x03\x04\x05\x06\x07\x08"[$this->count++];
}
}
$randomizer = new Randomizer(new ByteEngine());
var_dump(bin2hex(pack('V', $randomizer->getInt(0, 0x00FF_FFFF))));
?>
--EXPECT--
string(8) "01020300"

View File

@@ -0,0 +1,27 @@
--TEST--
Random: Randomizer: getInt(): Returned values with insufficient bits are correctly expanded (64 Bit)
--SKIPIF--
<?php if (PHP_INT_SIZE != 8) die("skip 64-bit only"); ?>
--FILE--
<?php
use Random\Engine;
use Random\Randomizer;
final class ByteEngine implements Engine
{
public $count = 0;
public function generate(): string
{
return "\x01\x02\x03\x04\x05\x06\x07\x08"[$this->count++];
}
}
$randomizer = new Randomizer(new ByteEngine());
var_dump(bin2hex(pack('P', $randomizer->getInt(0, 0x00FF_FFFF_FFFF_FFFF))));
?>
--EXPECT--
string(16) "0102030405060700"

View File

@@ -0,0 +1,50 @@
--TEST--
Random: Randomizer: nextInt(): Basic functionality
--FILE--
<?php
use Random\Engine;
use Random\Engine\Mt19937;
use Random\Engine\PcgOneseq128XslRr64;
use Random\Engine\Secure;
use Random\Engine\Test\TestShaEngine;
use Random\Engine\Xoshiro256StarStar;
use Random\Randomizer;
require __DIR__ . "/../../engines.inc";
$engines = [];
$engines[] = new Mt19937(null, MT_RAND_MT19937);
$engines[] = new Mt19937(null, MT_RAND_PHP);
$engines[] = new PcgOneseq128XslRr64();
$engines[] = new Xoshiro256StarStar();
$engines[] = new Secure();
$engines[] = new TestShaEngine();
foreach ($engines as $engine) {
echo $engine::class, PHP_EOL;
$randomizer = new Randomizer($engine);
for ($i = 0; $i < 10_000; $i++) {
try {
$randomizer->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

View File

@@ -0,0 +1,21 @@
--TEST--
Random: Randomizer: nextInt(): Throws for 64 bit engines on 32 bit platforms
--SKIPIF--
<?php if (PHP_INT_SIZE == 8) die("skip 32-bit only"); ?>
--FILE--
<?php
use Random\Randomizer;
use Random\Engine\Xoshiro256StarStar;
$randomizer = new Randomizer(new Xoshiro256StarStar());
try {
var_dump($randomizer->nextInt());
} catch (Random\RandomException $e) {
echo $e->getMessage(), PHP_EOL;
}
?>
--EXPECT--
Generated value exceeds size of int

View File

@@ -0,0 +1,84 @@
--TEST--
Random: Randomizer: pickArrayKeys(): Basic functionality
--FILE--
<?php
use Random\Engine;
use Random\Engine\Mt19937;
use Random\Engine\PcgOneseq128XslRr64;
use Random\Engine\Secure;
use Random\Engine\Test\TestShaEngine;
use Random\Engine\Xoshiro256StarStar;
use Random\Randomizer;
require __DIR__ . "/../../engines.inc";
$engines = [];
$engines[] = new Mt19937(null, MT_RAND_MT19937);
$engines[] = new Mt19937(null, MT_RAND_PHP);
$engines[] = new PcgOneseq128XslRr64();
$engines[] = new Xoshiro256StarStar();
$engines[] = new Secure();
$engines[] = new TestShaEngine();
$array1 = []; // list
$array2 = []; // associative array with only strings
$array3 = []; // mixed key array
for ($i = 0; $i < 500; $i++) {
$string = sha1((string)$i);
$array1[] = $i;
$array2[$string] = $i;
$array3[$string] = $i;
$array3[$i] = $string;
}
foreach ($engines as $engine) {
echo $engine::class, PHP_EOL;
$randomizer = new Randomizer($engine);
for ($i = 1; $i < 100; $i++) {
$result = $randomizer->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

View File

@@ -0,0 +1,49 @@
--TEST--
Random: Randomizer: pickArrayKeys(): Parameters are correctly validated
--FILE--
<?php
use Random\Randomizer;
function randomizer(): Randomizer
{
return new Randomizer();
}
try {
var_dump(randomizer()->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)

View File

@@ -0,0 +1,53 @@
--TEST--
Random: Randomizer: shuffleArray(): Basic functionality
--FILE--
<?php
use Random\Engine;
use Random\Engine\Mt19937;
use Random\Engine\PcgOneseq128XslRr64;
use Random\Engine\Secure;
use Random\Engine\Test\TestShaEngine;
use Random\Engine\Xoshiro256StarStar;
use Random\Randomizer;
require __DIR__ . "/../../engines.inc";
$engines = [];
$engines[] = new Mt19937(null, MT_RAND_MT19937);
$engines[] = new Mt19937(null, MT_RAND_PHP);
$engines[] = new PcgOneseq128XslRr64();
$engines[] = new Xoshiro256StarStar();
$engines[] = new Secure();
$engines[] = new TestShaEngine();
foreach ($engines as $engine) {
echo $engine::class, PHP_EOL;
$randomizer = new Randomizer($engine);
// This test is slow, test all numbers smaller than 50 and then in steps of 677 (which is prime).
for ($i = 1; $i < 5_000; $i += ($i < 50 ? 1 : 677)) {
$array = range(1, $i);
$result = $randomizer->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

View File

@@ -0,0 +1,60 @@
--TEST--
Random: Randomizer: shuffleBytes(): Basic functionality
--FILE--
<?php
use Random\Engine;
use Random\Engine\Mt19937;
use Random\Engine\PcgOneseq128XslRr64;
use Random\Engine\Secure;
use Random\Engine\Test\TestShaEngine;
use Random\Engine\Xoshiro256StarStar;
use Random\Randomizer;
require __DIR__ . "/../../engines.inc";
$engines = [];
$engines[] = new Mt19937(null, MT_RAND_MT19937);
$engines[] = new Mt19937(null, MT_RAND_PHP);
$engines[] = new PcgOneseq128XslRr64();
$engines[] = new Xoshiro256StarStar();
$engines[] = new Secure();
$engines[] = new TestShaEngine();
function sort_bytes(string $bytes): string
{
$bytes = str_split($bytes);
sort($bytes);
return implode('', $bytes);
}
foreach ($engines as $engine) {
echo $engine::class, PHP_EOL;
$randomizer = new Randomizer($engine);
// This test is slow, test all numbers smaller than 50 and then in steps of 677 (which is prime).
for ($i = 1; $i < 5_000; $i += ($i < 50 ? 1 : 677)) {
$bytes = sort_bytes(random_bytes($i));
$result = $randomizer->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

View File

@@ -1,18 +0,0 @@
--TEST--
Random: Randomizer: nextInt() throws for too large values on 32 Bit
--SKIPIF--
<?php if (PHP_INT_SIZE == 8) die("skip 32-bit only"); ?>
--FILE--
<?php
$randomizer = new \Random\Randomizer(new \Random\Engine\Xoshiro256StarStar());
try {
var_dump($randomizer->nextInt());
} catch (\Random\RandomException $e) {
echo $e->getMessage(), PHP_EOL;
}
?>
--EXPECT--
Generated value exceeds size of int

View File

@@ -1,52 +0,0 @@
--TEST--
Random: Randomizer: pickArrayKeys error pattern
--FILE--
<?php
try {
var_dump((new \Random\Randomizer())->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)
}

View File

@@ -1,43 +1,30 @@
--TEST--
Random: Randomizer: readonly engine
Random: Randomizer: The engine property must be readonly
--FILE--
<?php
$one = new \Random\Randomizer(
new \Random\Engine\PcgOneseq128XslRr64(1234)
);
use Random\Engine\PcgOneseq128XslRr64;
use Random\Engine\Xoshiro256StarStar;
use Random\Randomizer;
$one_ng_clone = clone $one->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

View File

@@ -1,57 +1,50 @@
--TEST--
Random: Randomizer: serialize
Random: Randomizer: Serialization of the Randomizer must preserve the sequence
--FILE--
<?php
use Random\Engine;
use Random\Engine\Mt19937;
use Random\Engine\PcgOneseq128XslRr64;
use Random\Engine\Test\TestShaEngine;
use Random\Engine\Xoshiro256StarStar;
use Random\Randomizer;
require __DIR__ . "/../engines.inc";
$engines = [];
$engines[] = new \Random\Engine\Mt19937(\random_int(\PHP_INT_MIN, \PHP_INT_MAX), MT_RAND_MT19937);
$engines[] = new \Random\Engine\Mt19937(\random_int(\PHP_INT_MIN, \PHP_INT_MAX), MT_RAND_PHP);
$engines[] = new \Random\Engine\PcgOneseq128XslRr64(\random_int(\PHP_INT_MIN, \PHP_INT_MAX));
$engines[] = new \Random\Engine\Xoshiro256StarStar(\random_int(\PHP_INT_MIN, \PHP_INT_MAX));
$engines[] = new \Random\Engine\Secure();
$generate = \random_bytes(16);
$engines[] = new class () implements Random\Engine {
public function generate(): string
{
global $generate;
return $generate;
}
};
class UserEngine implements Random\Engine
{
public function generate(): string
{
global $generate;
return $generate;
}
}
$engines[] = new UserEngine();
$engines[] = new Mt19937(1234, MT_RAND_MT19937);
$engines[] = new Mt19937(1234, MT_RAND_PHP);
$engines[] = new PcgOneseq128XslRr64(1234);
$engines[] = new Xoshiro256StarStar(1234);
$engines[] = new TestShaEngine("1234");
foreach ($engines as $engine) {
$randomizer = new Random\Randomizer($engine);
$randomizer->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

View File

@@ -0,0 +1,17 @@
--TEST--
Random: Randomizer: Serialization of the Randomizer fails if the engine is not serializable
--FILE--
<?php
use Random\Engine\Secure;
use Random\Randomizer;
try {
serialize(new Randomizer(new Secure()));
} catch (Exception $e) {
echo $e->getMessage(), PHP_EOL;
}
?>
--EXPECT--
Serialization of 'Random\Engine\Secure' is not allowed

View File

@@ -1,19 +0,0 @@
--TEST--
Random: Randomizer: User: Engine exits
--FILE--
<?php
$randomizer = (new \Random\Randomizer(
new class () implements \Random\Engine {
public function generate(): string
{
exit("Exit\n");
}
}
));
var_dump($randomizer->getBytes(1));
?>
--EXPECT--
Exit

View File

@@ -1,24 +0,0 @@
--TEST--
Random: Randomizer: User: Engine throws
--FILE--
<?php
$randomizer = (new \Random\Randomizer(
new class () implements \Random\Engine {
public function generate(): string
{
throw new Exception('Error');
}
}
));
var_dump($randomizer->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

View File

@@ -1,141 +0,0 @@
--TEST--
Random: Randomizer: User: Engine unsafe
--FILE--
<?php
use Random\Randomizer;
final class EmptyStringEngine implements \Random\Engine {
public function generate(): string
{
return '';
}
}
final class HeavilyBiasedEngine implements \Random\Engine {
public function generate(): string
{
return \str_repeat("\xff", PHP_INT_SIZE);
}
}
echo "=====================", PHP_EOL;
foreach ([
EmptyStringEngine::class,
HeavilyBiasedEngine::class,
] as $engine) {
echo $engine, PHP_EOL, "=====================", PHP_EOL, PHP_EOL;
try {
var_dump((new Randomizer(new $engine()))->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}
=====================

View File

@@ -0,0 +1,74 @@
<?php
namespace Random\Engine\Test;
use Random\Engine;
final class TestShaEngine implements Engine
{
private string $state;
public function __construct(?string $state = null)
{
if ($state !== null) {
$this->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);
}
}
?>