Skip to content

Commit 679e223

Browse files
committed
Merge branch 'release/0.8.4'
2 parents acd5cd4 + ab5dce1 commit 679e223

23 files changed

+3348
-1
lines changed

.version.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22
"strategy": "semver",
33
"major": 0,
44
"minor": 8,
5-
"patch": 3,
5+
"patch": 4,
66
"build": 0
77
}

src/Core/System/FakeRandom.php

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
<?php
2+
3+
namespace Neuron\Core\System;
4+
5+
/**
6+
* Fake random implementation for testing.
7+
*
8+
* Provides predictable, deterministic "random" values for testing.
9+
* Can be seeded with specific values or use a predictable sequence.
10+
*/
11+
class FakeRandom implements IRandom
12+
{
13+
private array $byteSequence = [];
14+
private array $intSequence = [];
15+
private array $uniqueIdSequence = [];
16+
private int $seed = 0;
17+
18+
/**
19+
* Set sequence of bytes to return from bytes() method
20+
*
21+
* @param array $sequence Array of byte strings
22+
* @return void
23+
*/
24+
public function setByteSequence( array $sequence ): void
25+
{
26+
$this->byteSequence = $sequence;
27+
}
28+
29+
/**
30+
* Set sequence of integers to return from int() method
31+
*
32+
* @param array $sequence Array of integers
33+
* @return void
34+
*/
35+
public function setIntSequence( array $sequence ): void
36+
{
37+
$this->intSequence = $sequence;
38+
}
39+
40+
/**
41+
* Set sequence of unique IDs to return from uniqueId() method
42+
*
43+
* @param array $sequence Array of unique ID strings
44+
* @return void
45+
*/
46+
public function setUniqueIdSequence( array $sequence ): void
47+
{
48+
$this->uniqueIdSequence = $sequence;
49+
}
50+
51+
/**
52+
* Set seed for predictable generation
53+
*
54+
* @param int $seed Seed value
55+
* @return void
56+
*/
57+
public function setSeed( int $seed ): void
58+
{
59+
$this->seed = $seed;
60+
}
61+
62+
/**
63+
* @inheritDoc
64+
*/
65+
public function bytes( int $length ): string
66+
{
67+
if( !empty( $this->byteSequence ) )
68+
{
69+
return array_shift( $this->byteSequence );
70+
}
71+
72+
// Generate predictable bytes based on seed
73+
$result = '';
74+
for( $i = 0; $i < $length; $i++ )
75+
{
76+
$result .= chr( ( $this->seed + $i ) % 256 );
77+
}
78+
79+
return $result;
80+
}
81+
82+
/**
83+
* @inheritDoc
84+
*/
85+
public function int( int $min, int $max ): int
86+
{
87+
if( !empty( $this->intSequence ) )
88+
{
89+
return array_shift( $this->intSequence );
90+
}
91+
92+
// Generate predictable int in range based on seed
93+
$range = $max - $min + 1;
94+
return $min + ( $this->seed % $range );
95+
}
96+
97+
/**
98+
* @inheritDoc
99+
*/
100+
public function uniqueId( string $prefix = '' ): string
101+
{
102+
if( !empty( $this->uniqueIdSequence ) )
103+
{
104+
return $prefix . array_shift( $this->uniqueIdSequence );
105+
}
106+
107+
// Generate predictable unique ID
108+
$this->seed++;
109+
return $prefix . sprintf( '%08x%05x', $this->seed, $this->seed );
110+
}
111+
112+
/**
113+
* @inheritDoc
114+
*/
115+
public function string( int $length, string $charset = 'hex' ): string
116+
{
117+
switch( $charset )
118+
{
119+
case 'hex':
120+
return substr( bin2hex( $this->bytes( $length ) ), 0, $length );
121+
122+
case 'base64':
123+
$bytes = $this->bytes( (int)ceil( $length * 3 / 4 ) );
124+
return substr( base64_encode( $bytes ), 0, $length );
125+
126+
case 'alphanumeric':
127+
$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
128+
return $this->stringFromCharset( $length, $chars );
129+
130+
case 'alpha':
131+
$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
132+
return $this->stringFromCharset( $length, $chars );
133+
134+
case 'numeric':
135+
$chars = '0123456789';
136+
return $this->stringFromCharset( $length, $chars );
137+
138+
default:
139+
throw new \InvalidArgumentException( "Unknown charset: $charset" );
140+
}
141+
}
142+
143+
/**
144+
* @inheritDoc
145+
*/
146+
public function float(): float
147+
{
148+
return ( $this->seed % 1000 ) / 1000.0;
149+
}
150+
151+
/**
152+
* @inheritDoc
153+
*/
154+
public function shuffle( array $array ): array
155+
{
156+
// Return predictable "shuffle" - just reverse the array
157+
return array_reverse( $array );
158+
}
159+
160+
/**
161+
* Generate string from character set using predictable pattern
162+
*
163+
* @param int $length Length of string
164+
* @param string $chars Available characters
165+
* @return string Predictable string
166+
*/
167+
private function stringFromCharset( int $length, string $chars ): string
168+
{
169+
$result = '';
170+
$charCount = strlen( $chars );
171+
172+
for( $i = 0; $i < $length; $i++ )
173+
{
174+
$result .= $chars[( $this->seed + $i ) % $charCount];
175+
}
176+
177+
return $result;
178+
}
179+
}

src/Core/System/FrozenClock.php

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
<?php
2+
3+
namespace Neuron\Core\System;
4+
5+
/**
6+
* Frozen clock implementation for testing.
7+
*
8+
* Provides a fixed point in time that doesn't change until explicitly set.
9+
* Perfect for testing time-dependent logic deterministically.
10+
*/
11+
class FrozenClock implements IClock
12+
{
13+
private int $frozenTime;
14+
private float $frozenMicrotime;
15+
16+
/**
17+
* Create a frozen clock at the specified time
18+
*
19+
* @param int|null $timestamp Unix timestamp to freeze at (null = current time)
20+
* @param float|null $microtime Microtime to freeze at (null = current microtime)
21+
*/
22+
public function __construct( ?int $timestamp = null, ?float $microtime = null )
23+
{
24+
$this->frozenTime = $timestamp ?? time();
25+
$this->frozenMicrotime = $microtime ?? microtime( true );
26+
}
27+
28+
/**
29+
* Set the frozen time to a new value
30+
*
31+
* @param int $timestamp Unix timestamp
32+
* @param float|null $microtime Microtime (null = use timestamp as float)
33+
* @return void
34+
*/
35+
public function setTime( int $timestamp, ?float $microtime = null ): void
36+
{
37+
$this->frozenTime = $timestamp;
38+
$this->frozenMicrotime = $microtime ?? (float)$timestamp;
39+
}
40+
41+
/**
42+
* Advance the frozen time by specified seconds
43+
*
44+
* @param int $seconds Number of seconds to advance
45+
* @return void
46+
*/
47+
public function advance( int $seconds ): void
48+
{
49+
$this->frozenTime += $seconds;
50+
$this->frozenMicrotime += (float)$seconds;
51+
}
52+
53+
/**
54+
* Advance the frozen time by specified microseconds
55+
*
56+
* @param int $microseconds Number of microseconds to advance
57+
* @return void
58+
*/
59+
public function advanceMicroseconds( int $microseconds ): void
60+
{
61+
$seconds = $microseconds / 1000000;
62+
$this->frozenTime += (int)$seconds;
63+
$this->frozenMicrotime += $seconds;
64+
}
65+
66+
/**
67+
* @inheritDoc
68+
*/
69+
public function time(): int
70+
{
71+
return $this->frozenTime;
72+
}
73+
74+
/**
75+
* @inheritDoc
76+
*/
77+
public function microtime( bool $asFloat = false ): string|float
78+
{
79+
if( $asFloat )
80+
{
81+
return $this->frozenMicrotime;
82+
}
83+
84+
// Format as "msec sec" string like real microtime()
85+
$sec = floor( $this->frozenMicrotime );
86+
$msec = $this->frozenMicrotime - $sec;
87+
88+
return sprintf( "%.6f %d", $msec, (int)$sec );
89+
}
90+
91+
/**
92+
* @inheritDoc
93+
*/
94+
public function date( string $format, ?int $timestamp = null ): string
95+
{
96+
$timestamp = $timestamp ?? $this->frozenTime;
97+
return date( $format, $timestamp );
98+
}
99+
100+
/**
101+
* @inheritDoc
102+
*/
103+
public function now(): \DateTimeImmutable
104+
{
105+
return ( new \DateTimeImmutable() )->setTimestamp( $this->frozenTime );
106+
}
107+
108+
/**
109+
* @inheritDoc
110+
* Note: Does NOT actually sleep, just advances the frozen time
111+
*/
112+
public function sleep( int $seconds ): void
113+
{
114+
$this->advance( $seconds );
115+
}
116+
117+
/**
118+
* @inheritDoc
119+
* Note: Does NOT actually sleep, just advances the frozen time
120+
*/
121+
public function usleep( int $microseconds ): void
122+
{
123+
$this->advanceMicroseconds( $microseconds );
124+
}
125+
}

0 commit comments

Comments
 (0)