484 lines
11 KiB
PHP
484 lines
11 KiB
PHP
|
<?php
|
||
|
|
||
|
namespace Illuminate\Support;
|
||
|
|
||
|
use Carbon\CarbonInterval;
|
||
|
use DateInterval;
|
||
|
use Illuminate\Support\Traits\Macroable;
|
||
|
use PHPUnit\Framework\Assert as PHPUnit;
|
||
|
use RuntimeException;
|
||
|
|
||
|
class Sleep
|
||
|
{
|
||
|
use Macroable;
|
||
|
|
||
|
/**
|
||
|
* The fake sleep callbacks.
|
||
|
*
|
||
|
* @var array
|
||
|
*/
|
||
|
public static $fakeSleepCallbacks = [];
|
||
|
|
||
|
/**
|
||
|
* Keep Carbon's "now" in sync when sleeping.
|
||
|
*
|
||
|
* @var bool
|
||
|
*/
|
||
|
protected static $syncWithCarbon = false;
|
||
|
|
||
|
/**
|
||
|
* The total duration to sleep.
|
||
|
*
|
||
|
* @var \Carbon\CarbonInterval
|
||
|
*/
|
||
|
public $duration;
|
||
|
|
||
|
/**
|
||
|
* The pending duration to sleep.
|
||
|
*
|
||
|
* @var int|float|null
|
||
|
*/
|
||
|
protected $pending = null;
|
||
|
|
||
|
/**
|
||
|
* Indicates that all sleeping should be faked.
|
||
|
*
|
||
|
* @var bool
|
||
|
*/
|
||
|
protected static $fake = false;
|
||
|
|
||
|
/**
|
||
|
* The sequence of sleep durations encountered while faking.
|
||
|
*
|
||
|
* @var array<int, \Carbon\CarbonInterval>
|
||
|
*/
|
||
|
protected static $sequence = [];
|
||
|
|
||
|
/**
|
||
|
* Indicates if the instance should sleep.
|
||
|
*
|
||
|
* @var bool
|
||
|
*/
|
||
|
protected $shouldSleep = true;
|
||
|
|
||
|
/**
|
||
|
* Create a new class instance.
|
||
|
*
|
||
|
* @param int|float|\DateInterval $duration
|
||
|
* @return void
|
||
|
*/
|
||
|
public function __construct($duration)
|
||
|
{
|
||
|
$this->duration($duration);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sleep for the given duration.
|
||
|
*
|
||
|
* @param \DateInterval|int|float $duration
|
||
|
* @return static
|
||
|
*/
|
||
|
public static function for($duration)
|
||
|
{
|
||
|
return new static($duration);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sleep until the given timestamp.
|
||
|
*
|
||
|
* @param \DateTimeInterface|int|float|numeric-string $timestamp
|
||
|
* @return static
|
||
|
*/
|
||
|
public static function until($timestamp)
|
||
|
{
|
||
|
if (is_numeric($timestamp)) {
|
||
|
$timestamp = Carbon::createFromTimestamp($timestamp, date_default_timezone_get());
|
||
|
}
|
||
|
|
||
|
return new static(Carbon::now()->diff($timestamp));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sleep for the given number of microseconds.
|
||
|
*
|
||
|
* @param int $duration
|
||
|
* @return static
|
||
|
*/
|
||
|
public static function usleep($duration)
|
||
|
{
|
||
|
return (new static($duration))->microseconds();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sleep for the given number of seconds.
|
||
|
*
|
||
|
* @param int|float $duration
|
||
|
* @return static
|
||
|
*/
|
||
|
public static function sleep($duration)
|
||
|
{
|
||
|
return (new static($duration))->seconds();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sleep for the given duration. Replaces any previously defined duration.
|
||
|
*
|
||
|
* @param \DateInterval|int|float $duration
|
||
|
* @return $this
|
||
|
*/
|
||
|
protected function duration($duration)
|
||
|
{
|
||
|
if (! $duration instanceof DateInterval) {
|
||
|
$this->duration = CarbonInterval::microsecond(0);
|
||
|
|
||
|
$this->pending = $duration;
|
||
|
} else {
|
||
|
$duration = CarbonInterval::instance($duration);
|
||
|
|
||
|
if ($duration->totalMicroseconds < 0) {
|
||
|
$duration = CarbonInterval::seconds(0);
|
||
|
}
|
||
|
|
||
|
$this->duration = $duration;
|
||
|
$this->pending = null;
|
||
|
}
|
||
|
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sleep for the given number of minutes.
|
||
|
*
|
||
|
* @return $this
|
||
|
*/
|
||
|
public function minutes()
|
||
|
{
|
||
|
$this->duration->add('minutes', $this->pullPending());
|
||
|
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sleep for one minute.
|
||
|
*
|
||
|
* @return $this
|
||
|
*/
|
||
|
public function minute()
|
||
|
{
|
||
|
return $this->minutes();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sleep for the given number of seconds.
|
||
|
*
|
||
|
* @return $this
|
||
|
*/
|
||
|
public function seconds()
|
||
|
{
|
||
|
$this->duration->add('seconds', $this->pullPending());
|
||
|
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sleep for one second.
|
||
|
*
|
||
|
* @return $this
|
||
|
*/
|
||
|
public function second()
|
||
|
{
|
||
|
return $this->seconds();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sleep for the given number of milliseconds.
|
||
|
*
|
||
|
* @return $this
|
||
|
*/
|
||
|
public function milliseconds()
|
||
|
{
|
||
|
$this->duration->add('milliseconds', $this->pullPending());
|
||
|
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sleep for one millisecond.
|
||
|
*
|
||
|
* @return $this
|
||
|
*/
|
||
|
public function millisecond()
|
||
|
{
|
||
|
return $this->milliseconds();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sleep for the given number of microseconds.
|
||
|
*
|
||
|
* @return $this
|
||
|
*/
|
||
|
public function microseconds()
|
||
|
{
|
||
|
$this->duration->add('microseconds', $this->pullPending());
|
||
|
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sleep for on microsecond.
|
||
|
*
|
||
|
* @return $this
|
||
|
*/
|
||
|
public function microsecond()
|
||
|
{
|
||
|
return $this->microseconds();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Add additional time to sleep for.
|
||
|
*
|
||
|
* @param int|float $duration
|
||
|
* @return $this
|
||
|
*/
|
||
|
public function and($duration)
|
||
|
{
|
||
|
$this->pending = $duration;
|
||
|
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Handle the object's destruction.
|
||
|
*
|
||
|
* @return void
|
||
|
*/
|
||
|
public function __destruct()
|
||
|
{
|
||
|
if (! $this->shouldSleep) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if ($this->pending !== null) {
|
||
|
throw new RuntimeException('Unknown duration unit.');
|
||
|
}
|
||
|
|
||
|
if (static::$fake) {
|
||
|
static::$sequence[] = $this->duration;
|
||
|
|
||
|
if (static::$syncWithCarbon) {
|
||
|
Carbon::setTestNow(Carbon::now()->add($this->duration));
|
||
|
}
|
||
|
|
||
|
foreach (static::$fakeSleepCallbacks as $callback) {
|
||
|
$callback($this->duration);
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
$remaining = $this->duration->copy();
|
||
|
|
||
|
$seconds = (int) $remaining->totalSeconds;
|
||
|
|
||
|
if ($seconds > 0) {
|
||
|
sleep($seconds);
|
||
|
|
||
|
$remaining = $remaining->subSeconds($seconds);
|
||
|
}
|
||
|
|
||
|
$microseconds = (int) $remaining->totalMicroseconds;
|
||
|
|
||
|
if ($microseconds > 0) {
|
||
|
usleep($microseconds);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Resolve the pending duration.
|
||
|
*
|
||
|
* @return int|float
|
||
|
*/
|
||
|
protected function pullPending()
|
||
|
{
|
||
|
if ($this->pending === null) {
|
||
|
$this->shouldNotSleep();
|
||
|
|
||
|
throw new RuntimeException('No duration specified.');
|
||
|
}
|
||
|
|
||
|
if ($this->pending < 0) {
|
||
|
$this->pending = 0;
|
||
|
}
|
||
|
|
||
|
return tap($this->pending, function () {
|
||
|
$this->pending = null;
|
||
|
});
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Stay awake and capture any attempts to sleep.
|
||
|
*
|
||
|
* @param bool $value
|
||
|
* @param bool $syncWithCarbon
|
||
|
* @return void
|
||
|
*/
|
||
|
public static function fake($value = true, $syncWithCarbon = false)
|
||
|
{
|
||
|
static::$fake = $value;
|
||
|
|
||
|
static::$sequence = [];
|
||
|
static::$fakeSleepCallbacks = [];
|
||
|
static::$syncWithCarbon = $syncWithCarbon;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Assert a given amount of sleeping occurred a specific number of times.
|
||
|
*
|
||
|
* @param \Closure $expected
|
||
|
* @param int $times
|
||
|
* @return void
|
||
|
*/
|
||
|
public static function assertSlept($expected, $times = 1)
|
||
|
{
|
||
|
$count = collect(static::$sequence)->filter($expected)->count();
|
||
|
|
||
|
PHPUnit::assertSame(
|
||
|
$times,
|
||
|
$count,
|
||
|
"The expected sleep was found [{$count}] times instead of [{$times}]."
|
||
|
);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Assert sleeping occurred a given number of times.
|
||
|
*
|
||
|
* @param int $expected
|
||
|
* @return void
|
||
|
*/
|
||
|
public static function assertSleptTimes($expected)
|
||
|
{
|
||
|
PHPUnit::assertSame($expected, $count = count(static::$sequence), "Expected [{$expected}] sleeps but found [{$count}].");
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Assert the given sleep sequence was encountered.
|
||
|
*
|
||
|
* @param array $sequence
|
||
|
* @return void
|
||
|
*/
|
||
|
public static function assertSequence($sequence)
|
||
|
{
|
||
|
static::assertSleptTimes(count($sequence));
|
||
|
|
||
|
collect($sequence)
|
||
|
->zip(static::$sequence)
|
||
|
->eachSpread(function (?Sleep $expected, CarbonInterval $actual) {
|
||
|
if ($expected === null) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
PHPUnit::assertTrue(
|
||
|
$expected->shouldNotSleep()->duration->equalTo($actual),
|
||
|
vsprintf('Expected sleep duration of [%s] but actually slept for [%s].', [
|
||
|
$expected->duration->cascade()->forHumans([
|
||
|
'options' => 0,
|
||
|
'minimumUnit' => 'microsecond',
|
||
|
]),
|
||
|
$actual->cascade()->forHumans([
|
||
|
'options' => 0,
|
||
|
'minimumUnit' => 'microsecond',
|
||
|
]),
|
||
|
])
|
||
|
);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Assert that no sleeping occurred.
|
||
|
*
|
||
|
* @return void
|
||
|
*/
|
||
|
public static function assertNeverSlept()
|
||
|
{
|
||
|
return static::assertSleptTimes(0);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Assert that no sleeping occurred.
|
||
|
*
|
||
|
* @return void
|
||
|
*/
|
||
|
public static function assertInsomniac()
|
||
|
{
|
||
|
if (static::$sequence === []) {
|
||
|
PHPUnit::assertTrue(true);
|
||
|
}
|
||
|
|
||
|
foreach (static::$sequence as $duration) {
|
||
|
PHPUnit::assertSame(0, (int) $duration->totalMicroseconds, vsprintf('Unexpected sleep duration of [%s] found.', [
|
||
|
$duration->cascade()->forHumans([
|
||
|
'options' => 0,
|
||
|
'minimumUnit' => 'microsecond',
|
||
|
]),
|
||
|
]));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Indicate that the instance should not sleep.
|
||
|
*
|
||
|
* @return $this
|
||
|
*/
|
||
|
protected function shouldNotSleep()
|
||
|
{
|
||
|
$this->shouldSleep = false;
|
||
|
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Only sleep when the given condition is true.
|
||
|
*
|
||
|
* @param (\Closure($this): bool)|bool $condition
|
||
|
* @return $this
|
||
|
*/
|
||
|
public function when($condition)
|
||
|
{
|
||
|
$this->shouldSleep = (bool) value($condition, $this);
|
||
|
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Don't sleep when the given condition is true.
|
||
|
*
|
||
|
* @param (\Closure($this): bool)|bool $condition
|
||
|
* @return $this
|
||
|
*/
|
||
|
public function unless($condition)
|
||
|
{
|
||
|
return $this->when(! value($condition, $this));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Specify a callback that should be invoked when faking sleep within a test.
|
||
|
*
|
||
|
* @param callable $callback
|
||
|
* @return void
|
||
|
*/
|
||
|
public static function whenFakingSleep($callback)
|
||
|
{
|
||
|
static::$fakeSleepCallbacks[] = $callback;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Indicate that Carbon's "now" should be kept in sync when sleeping.
|
||
|
*
|
||
|
* @return void
|
||
|
*/
|
||
|
public static function syncWithCarbon($value = true)
|
||
|
{
|
||
|
static::$syncWithCarbon = $value;
|
||
|
}
|
||
|
}
|