diff --git a/src/ChildProcess.php b/src/ChildProcess.php index 171210f..37cff65 100644 --- a/src/ChildProcess.php +++ b/src/ChildProcess.php @@ -3,8 +3,9 @@ namespace Native\Laravel; use Native\Laravel\Client\Client; +use Native\Laravel\Contracts\ChildProcess as ChildProcessContract; -class ChildProcess +class ChildProcess implements ChildProcessContract { public readonly int $pid; diff --git a/src/Contracts/ChildProcess.php b/src/Contracts/ChildProcess.php new file mode 100644 index 0000000..9859e3e --- /dev/null +++ b/src/Contracts/ChildProcess.php @@ -0,0 +1,28 @@ +make(ChildProcessFake::class), function ($fake) { + static::swap($fake); + }); + } + protected static function getFacadeAccessor() { - self::clearResolvedInstance(Implement::class); + self::clearResolvedInstance(ChildProcessContract::class); - return Implement::class; + return ChildProcessContract::class; } } diff --git a/src/Fakes/ChildProcessFake.php b/src/Fakes/ChildProcessFake.php new file mode 100644 index 0000000..4e6add8 --- /dev/null +++ b/src/Fakes/ChildProcessFake.php @@ -0,0 +1,252 @@ + + */ + public array $gets = []; + + /** + * @var array + */ + public array $starts = []; + + /** + * @var array + */ + public array $phps = []; + + /** + * @var array + */ + public array $artisans = []; + + /** + * @var array + */ + public array $stops = []; + + /** + * @var array + */ + public array $restarts = []; + + /** + * @var array + */ + public array $messages = []; + + public function get(?string $alias = null): self + { + $this->gets[] = $alias; + + return $this; + } + + public function all(): array + { + return [$this]; + } + + public function start( + array|string $cmd, + string $alias, + ?string $cwd = null, + ?array $env = null, + bool $persistent = false + ): self { + $this->starts[] = [ + 'cmd' => $cmd, + 'alias' => $alias, + 'cwd' => $cwd, + 'env' => $env, + 'persistent' => $persistent, + ]; + + return $this; + } + + public function php( + array|string $cmd, + string $alias, + ?array $env = null, + ?bool $persistent = false + ): self { + $this->phps[] = [ + 'cmd' => $cmd, + 'alias' => $alias, + 'env' => $env, + 'persistent' => $persistent, + ]; + + return $this; + } + + public function artisan( + array|string $cmd, + string $alias, + ?array $env = null, + ?bool $persistent = false + ): self { + $this->artisans[] = [ + 'cmd' => $cmd, + 'alias' => $alias, + 'env' => $env, + 'persistent' => $persistent, + ]; + + return $this; + } + + public function stop(?string $alias = null): void + { + $this->stops[] = $alias; + } + + public function restart(?string $alias = null): self + { + $this->restarts[] = $alias; + + return $this; + } + + public function message(string $message, ?string $alias = null): self + { + $this->messages[] = [ + 'message' => $message, + 'alias' => $alias, + ]; + + return $this; + } + + /** + * @param string|Closure(string): bool $alias + */ + public function assertGet(string|Closure $alias): void + { + if (is_callable($alias) === false) { + PHPUnit::assertContains($alias, $this->gets); + + return; + } + + $hit = empty( + array_filter( + $this->gets, + fn (mixed $get) => $alias($get) === true + ) + ) === false; + + PHPUnit::assertTrue($hit); + } + + /** + * @param Closure(array|string $cmd, string $alias, ?string $cwd, ?array $env, bool $persistent): bool $callback + */ + public function assertStarted(Closure $callback): void + { + $hit = empty( + array_filter( + $this->starts, + fn (array $started) => $callback(...$started) === true + ) + ) === false; + + PHPUnit::assertTrue($hit); + } + + /** + * @param Closure(array|string $cmd, string $alias, ?array $env, ?bool $persistent): bool $callback + */ + public function assertPhp(Closure $callback): void + { + $hit = empty( + array_filter( + $this->phps, + fn (array $php) => $callback(...$php) === true + ) + ) === false; + + PHPUnit::assertTrue($hit); + } + + /** + * @param Closure(array|string $cmd, string $alias, ?array $env, ?bool $persistent): bool $callback + */ + public function assertArtisan(Closure $callback): void + { + $hit = empty( + array_filter( + $this->artisans, + fn (array $artisan) => $callback(...$artisan) === true + ) + ) === false; + + PHPUnit::assertTrue($hit); + } + + /** + * @param string|Closure(string): bool $alias + */ + public function assertStop(string|Closure $alias): void + { + if (is_callable($alias) === false) { + PHPUnit::assertContains($alias, $this->stops); + + return; + } + + $hit = empty( + array_filter( + $this->stops, + fn (mixed $stop) => $alias($stop) === true + ) + ) === false; + + PHPUnit::assertTrue($hit); + } + + /** + * @param string|Closure(string): bool $alias + */ + public function assertRestart(string|Closure $alias): void + { + if (is_callable($alias) === false) { + PHPUnit::assertContains($alias, $this->restarts); + + return; + } + + $hit = empty( + array_filter( + $this->restarts, + fn (mixed $restart) => $alias($restart) === true + ) + ) === false; + + PHPUnit::assertTrue($hit); + } + + /** + * @param Closure(string $message, string|null $alias): bool $callback + */ + public function assertMessage(Closure $callback): void + { + $hit = empty( + array_filter( + $this->messages, + fn (array $message) => $callback(...$message) === true + ) + ) === false; + + PHPUnit::assertTrue($hit); + } +} diff --git a/src/NativeServiceProvider.php b/src/NativeServiceProvider.php index dc2e258..a17d953 100644 --- a/src/NativeServiceProvider.php +++ b/src/NativeServiceProvider.php @@ -7,12 +7,14 @@ use Illuminate\Support\Arr; use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\DB; +use Native\Laravel\ChildProcess as ChildProcessImplementation; use Native\Laravel\Commands\FreshCommand; use Native\Laravel\Commands\LoadPHPConfigurationCommand; use Native\Laravel\Commands\LoadStartupConfigurationCommand; use Native\Laravel\Commands\MigrateCommand; use Native\Laravel\Commands\MinifyApplicationCommand; use Native\Laravel\Commands\SeedDatabaseCommand; +use Native\Laravel\Contracts\ChildProcess as ChildProcessContract; use Native\Laravel\Contracts\WindowManager as WindowManagerContract; use Native\Laravel\Events\EventWatcher; use Native\Laravel\Exceptions\Handler; @@ -54,6 +56,10 @@ public function packageRegistered() return $app->make(WindowManagerImplementation::class); }); + $this->app->bind(ChildProcessContract::class, function (Foundation $app) { + return $app->make(ChildProcessImplementation::class); + }); + if (config('nativephp-internal.running')) { $this->app->singleton( \Illuminate\Contracts\Debug\ExceptionHandler::class, diff --git a/tests/Fakes/FakeChildProcessTest.php b/tests/Fakes/FakeChildProcessTest.php new file mode 100644 index 0000000..8fece8a --- /dev/null +++ b/tests/Fakes/FakeChildProcessTest.php @@ -0,0 +1,219 @@ +toBeInstanceOf(ChildProcessFake::class); +}); + +it('asserts get using string', function () { + swap(ChildProcessContract::class, $fake = app(ChildProcessFake::class)); + + $fake->get('testA'); + $fake->get('testB'); + + $fake->assertGet('testA'); + $fake->assertGet('testB'); + + try { + $fake->assertGet('testC'); + } catch (AssertionFailedError) { + return; + } + + $this->fail('Expected assertion to fail'); +}); + +it('asserts get using callable', function () { + swap(ChildProcessContract::class, $fake = app(ChildProcessFake::class)); + + $fake->get('testA'); + $fake->get('testB'); + + $fake->assertGet(fn (string $alias) => $alias === 'testA'); + $fake->assertGet(fn (string $alias) => $alias === 'testB'); + + try { + $fake->assertGet(fn (string $alias) => $alias === 'testC'); + } catch (AssertionFailedError) { + return; + } + + $this->fail('Expected assertion to fail'); +}); + +it('asserts started using callable', function () { + swap(ChildProcessContract::class, $fake = app(ChildProcessFake::class)); + + $fake->start('cmdA', 'aliasA', 'cwdA', ['envA'], true); + $fake->start('cmdB', 'aliasB', 'cwdB', ['envB'], false); + + $fake->assertStarted(fn ($cmd, $alias, $cwd, $env, $persistent) => $alias === 'aliasA' && + $cmd === 'cmdA' && + $cwd === 'cwdA' && + $env === ['envA'] && + $persistent === true); + + $fake->assertStarted(fn ($cmd, $alias, $cwd, $env, $persistent) => $alias === 'aliasB' && + $cmd === 'cmdB' && + $cwd === 'cwdB' && + $env === ['envB'] && + $persistent === false); + + try { + $fake->assertStarted(fn ($cmd, $alias, $cwd, $env, $persistent) => $alias === 'aliasC'); + } catch (AssertionFailedError) { + return; + } + + $this->fail('Expected assertion to fail'); +}); + +it('asserts php using callable', function () { + swap(ChildProcessContract::class, $fake = app(ChildProcessFake::class)); + + $fake->php('cmdA', 'aliasA', ['envA'], true); + $fake->php('cmdB', 'aliasB', ['envB'], false); + + $fake->assertPhp(fn ($cmd, $alias, $env, $persistent) => $alias === 'aliasA' && + $cmd === 'cmdA' && + $env === ['envA'] && + $persistent === true); + + $fake->assertPhp(fn ($cmd, $alias, $env, $persistent) => $alias === 'aliasB' && + $cmd === 'cmdB' && + $env === ['envB'] && + $persistent === false); + + try { + $fake->assertPhp(fn ($cmd, $alias, $env, $persistent) => $alias === 'aliasC'); + } catch (AssertionFailedError) { + return; + } + + $this->fail('Expected assertion to fail'); +}); + +it('asserts artisan using callable', function () { + swap(ChildProcessContract::class, $fake = app(ChildProcessFake::class)); + + $fake->artisan('cmdA', 'aliasA', ['envA'], true); + $fake->artisan('cmdB', 'aliasB', ['envB'], false); + + $fake->assertArtisan(fn ($cmd, $alias, $env, $persistent) => $alias === 'aliasA' && + $cmd === 'cmdA' && + $env === ['envA'] && + $persistent === true); + + $fake->assertArtisan(fn ($cmd, $alias, $env, $persistent) => $alias === 'aliasB' && + $cmd === 'cmdB' && + $env === ['envB'] && + $persistent === false); + + try { + $fake->assertArtisan(fn ($cmd, $alias, $env, $persistent) => $alias === 'aliasC'); + } catch (AssertionFailedError) { + return; + } + + $this->fail('Expected assertion to fail'); +}); + +it('asserts stop using string', function () { + swap(ChildProcessContract::class, $fake = app(ChildProcessFake::class)); + + $fake->stop('testA'); + $fake->stop('testB'); + + $fake->assertStop('testA'); + $fake->assertStop('testB'); + + try { + $fake->assertStop('testC'); + } catch (AssertionFailedError) { + return; + } + + $this->fail('Expected assertion to fail'); +}); + +it('asserts stop using callable', function () { + swap(ChildProcessContract::class, $fake = app(ChildProcessFake::class)); + + $fake->stop('testA'); + $fake->stop('testB'); + + $fake->assertStop(fn (string $alias) => $alias === 'testA'); + $fake->assertStop(fn (string $alias) => $alias === 'testB'); + + try { + $fake->assertStop(fn (string $alias) => $alias === 'testC'); + } catch (AssertionFailedError) { + return; + } + + $this->fail('Expected assertion to fail'); +}); + +it('asserts restart using string', function () { + swap(ChildProcessContract::class, $fake = app(ChildProcessFake::class)); + + $fake->restart('testA'); + $fake->restart('testB'); + + $fake->assertRestart('testA'); + $fake->assertRestart('testB'); + + try { + $fake->assertRestart('testC'); + } catch (AssertionFailedError) { + return; + } + + $this->fail('Expected assertion to fail'); +}); + +it('asserts restart using callable', function () { + swap(ChildProcessContract::class, $fake = app(ChildProcessFake::class)); + + $fake->restart('testA'); + $fake->restart('testB'); + + $fake->assertRestart(fn (string $alias) => $alias === 'testA'); + $fake->assertRestart(fn (string $alias) => $alias === 'testB'); + + try { + $fake->assertRestart(fn (string $alias) => $alias === 'testC'); + } catch (AssertionFailedError) { + return; + } + + $this->fail('Expected assertion to fail'); +}); + +it('asserts message using callable', function () { + swap(ChildProcessContract::class, $fake = app(ChildProcessFake::class)); + + $fake->message('messageA', 'aliasA'); + $fake->message('messageB', 'aliasB'); + + $fake->assertMessage(fn (string $message, string $alias) => $message === 'messageA' && $alias === 'aliasA'); + $fake->assertMessage(fn (string $message, string $alias) => $message === 'messageB' && $alias === 'aliasB'); + + try { + $fake->assertMessage(fn (string $message, string $alias) => $message === 'messageC'); + } catch (AssertionFailedError) { + return; + } + + $this->fail('Expected assertion to fail'); +}); + +