1
0
mirror of https://github.com/laravel/valet.git synced 2026-02-05 08:30:07 +01:00

Add status command (#1329)

* Build the foundation of a status command

* Apply fixes from StyleCI

* Wip testing status command

* Apply fixes from StyleCI

* Fix status test

* Apply fixes from StyleCI

* Fix race condition in creating test config file

* Apply fixes from StyleCI

* Reset container for each test

* Differentiate response code based on success or failure of status command

* Apply fixes from StyleCI

* Add the ability to test if a Brew service is running

* Apply fixes from StyleCI

* Check for more services running in status command

* Apply fixes from StyleCI

* Test Status

* Apply fixes from StyleCI

* Drop Yoast from base application test case

Co-authored-by: StyleCI Bot <bot@styleci.io>
This commit is contained in:
Matt Stauffer
2023-01-05 16:40:27 -05:00
committed by GitHub
parent a79128200b
commit 0c57391eed
9 changed files with 290 additions and 15 deletions

View File

@@ -198,7 +198,7 @@ public function prune(): void
*/
public function read(): array
{
return json_decode($this->files->get($this->path()), true);
return json_decode($this->files->get($this->path()), true, 512, JSON_THROW_ON_ERROR);
}
/**

138
cli/Valet/Status.php Normal file
View File

@@ -0,0 +1,138 @@
<?php
namespace Valet;
class Status
{
public $brewServicesUserOutput;
public $brewServicesSudoOutput;
/**
* Create a new Status instance.
*/
public function __construct(public Configuration $config, public Brew $brew, public CommandLine $cli, public Filesystem $files)
{
}
/**
* Check the status of the entire Valet ecosystem and return a status boolean
* and a set of individual checks and their respective booleans as well.
*/
public function check(): array
{
$isValid = true;
$output = collect($this->checks())->map(function (array $check) use (&$isValid) {
if (! $thisIsValid = $check['check']()) {
$isValid = false;
}
return ['description' => $check['description'], 'success' => $thisIsValid ? 'True' : 'False'];
});
return [
'success' => $isValid,
'output' => $output->all(),
];
}
/**
* Define a list of checks to test the health of the Valet ecosystem of tools and configs.
*/
public function checks(): array
{
return [
[
'description' => 'Is Valet installed?',
'check' => function () {
return is_dir(VALET_HOME_PATH) && file_exists($this->config->path());
},
],
[
'description' => 'Is Valet config valid?',
'check' => function () {
try {
$this->config->read();
return true;
} catch (\JsonException $e) {
return false;
}
},
],
[
'description' => 'Is Homebrew installed?',
'check' => function () {
return $this->cli->run('which brew') !== '';
},
],
[
'description' => 'Is DnsMasq installed?',
'check' => function () {
return $this->brew->installed('dnsmasq');
},
],
[
'description' => 'Is Dnsmasq running?',
'check' => function () {
return $this->isBrewServiceRunning('dnsmasq');
},
],
[
'description' => 'Is Nginx installed?',
'check' => function () {
return $this->brew->installed('nginx');
},
],
[
'description' => 'Is Nginx running?',
'check' => function () {
return $this->isBrewServiceRunning('nginx');
},
],
[
'description' => 'Is PHP installed?',
'check' => function () {
return $this->brew->hasInstalledPhp();
},
],
[
'description' => 'Is PHP running?',
'check' => function () {
return $this->isBrewServiceRunning('php', exactMatch: false);
},
],
[
'description' => 'Is valet.sock present?',
'check' => function () {
return $this->files->exists(VALET_HOME_PATH.'/valet.sock');
},
],
];
}
public function isBrewServiceRunning(string $name, bool $exactMatch = true): bool
{
if (! $this->brewServicesUserOutput) {
$this->brewServicesUserOutput = json_decode($this->cli->runAsUser('brew services info --all --json'), false);
}
if (! $this->brewServicesSudoOutput) {
$this->brewServicesSudoOutput = json_decode($this->cli->run('brew services info --all --json'), false);
}
foreach ([$this->brewServicesUserOutput, $this->brewServicesSudoOutput] as $output) {
foreach ($output as $service) {
if ($service->running === true) {
if ($exactMatch && $service->name == $name) {
return true;
} elseif (! $exactMatch && str_contains($service->name, $name)) {
return true;
}
}
}
}
return false;
}
}

View File

@@ -31,7 +31,12 @@ public function onEveryRun(): void
*/
public function pruneMissingDirectories(): void
{
Configuration::prune();
try {
Configuration::prune();
} catch (\JsonException $e) {
warning('Invalid confiuration file at '.Configuration::path().'.');
exit;
}
}
/**

View File

@@ -2,6 +2,7 @@
use Illuminate\Container\Container;
use Silly\Application;
use Silly\Command\Command;
use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
use Symfony\Component\Console\Input\InputInterface;
@@ -61,6 +62,29 @@ function (ConsoleCommandEvent $event) {
output(PHP_EOL.'<info>Valet installed successfully!</info>');
})->descriptions('Install the Valet services');
/**
* Output the status of Valet and its installed services and config.
*/
$app->command('status', function (OutputInterface $output) {
info('Checking status...');
$status = Status::check();
if ($status['success']) {
info("\nValet status: Healthy\n");
} else {
warning("\nValet status: Error\n");
}
table(['Check', 'Success?'], $status['output']);
if ($status['success']) {
return Command::SUCCESS;
} else {
return Command::FAILURE;
}
})->descriptions('Output the status of Valet and its installed services and config.');
/**
* Most commands are available only if valet is installed.
*/

View File

@@ -59,6 +59,9 @@ class PhpFpm extends Facade
class Site extends Facade
{
}
class Status extends Facade
{
}
class Upgrader extends Facade
{
}

View File

@@ -1,8 +1,10 @@
<?php
use Illuminate\Container\Container;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Tester\ApplicationTester;
class BaseApplicationTestCase extends Yoast\PHPUnitPolyfills\TestCases\TestCase
class BaseApplicationTestCase extends TestCase
{
use UsesNullWriter;
@@ -25,6 +27,7 @@ public function tearDown(): void
public function prepTestConfig()
{
require_once __DIR__.'/../cli/includes/helpers.php';
Container::setInstance(new Container); // Reset app container from previous tests
if (Filesystem::isDir(VALET_HOME_PATH)) {
Filesystem::rmDirAndContents(VALET_HOME_PATH);

View File

@@ -1,6 +1,8 @@
<?php
use Valet\Brew;
use Valet\CommandLine;
use Valet\Filesystem;
use Valet\Nginx;
use Valet\Ngrok;
use Valet\Site as RealSite;
@@ -74,6 +76,59 @@ public function test_park_command()
$this->assertEquals('./tests/output', reset($paths));
}
public function test_status_command_succeeding()
{
[$app, $tester] = $this->appAndTester();
$brew = Mockery::mock(Brew::class);
$brew->shouldReceive('hasInstalledPhp')->andReturn(true);
$brew->shouldReceive('installed')->twice()->andReturn(true);
$cli = Mockery::mock(CommandLine::class);
$cli->shouldReceive('run')->once()->andReturn(true);
$cli->shouldReceive('runAsUser')->once()->with('brew services info --all --json')->andReturn('[{"name":"nginx","running":true}]');
$cli->shouldReceive('run')->once()->with('brew services info --all --json')->andReturn('[{"name":"nginx","running":true},{"name":"dnsmasq","running":true},{"name":"php","running":true}]');
$files = Mockery::mock(Filesystem::class.'[exists]');
$files->shouldReceive('exists')->once()->andReturn(true);
swap(Brew::class, $brew);
swap(CommandLine::class, $cli);
swap(Filesystem::class, $files);
$tester->run(['command' => 'status']);
// $tester->assertCommandIsSuccessful();
$this->assertStringNotContainsString('False', $tester->getDisplay());
}
public function test_status_command_failing()
{
[$app, $tester] = $this->appAndTester();
$brew = Mockery::mock(Brew::class);
$brew->shouldReceive('hasInstalledPhp')->andReturn(true);
$brew->shouldReceive('installed')->twice()->andReturn(true);
$cli = Mockery::mock(CommandLine::class);
$cli->shouldReceive('run')->once()->andReturn(true);
$cli->shouldReceive('runAsUser')->once()->with('brew services info --all --json')->andReturn('[{"name":"nginx","running":true}]');
$cli->shouldReceive('run')->once()->with('brew services info --all --json')->andReturn('[{"name":"nginx","running":true}]');
$files = Mockery::mock(Filesystem::class.'[exists]');
$files->shouldReceive('exists')->once()->andReturn(false);
swap(Brew::class, $brew);
swap(CommandLine::class, $cli);
swap(Filesystem::class, $files);
$tester->run(['command' => 'status']);
$this->assertNotEquals(0, $tester->getStatusCode());
$this->assertStringContainsString('False', $tester->getDisplay());
}
public function test_parked_command()
{
[$app, $tester] = $this->appAndTester();

View File

@@ -949,18 +949,6 @@ public function test_it_can_read_php_rc_version()
$this->assertEquals('php@8.0', $siteMock->phpRcVersion('site2'));
$this->assertEquals(null, $siteMock->phpRcVersion('site3')); // Site doesn't exists
}
public function test_it_appends_tld_to_domain()
{
$site = resolve(Site::class);
$this->assertEquals('symposium.test', $site->domain('symposium'));
}
public function test_it_doesnt_double_append_tld_to_domain()
{
$site = resolve(Site::class);
$this->assertEquals('symposium.test', $site->domain('symposium.test'));
}
}
class CommandLineFake extends CommandLine

59
tests/StatusTest.php Normal file
View File

@@ -0,0 +1,59 @@
<?php
use Illuminate\Container\Container;
use Valet\CommandLine;
use function Valet\resolve;
use Valet\Status;
use function Valet\swap;
use function Valet\user;
class StatusTest extends Yoast\PHPUnitPolyfills\TestCases\TestCase
{
use UsesNullWriter;
public function set_up()
{
$_SERVER['SUDO_USER'] = user();
Container::setInstance(new Container);
$this->setNullWriter();
}
public function tear_down()
{
Mockery::close();
}
public function test_status_can_be_resolved_from_container()
{
$this->assertInstanceOf(Status::class, resolve(Status::class));
}
public function test_it_checks_if_brew_services_are_running()
{
$cli = Mockery::mock(CommandLine::class);
$cli->shouldReceive('runAsUser')->once()->with('brew services info --all --json')->andReturn('[{"name":"nginx","running":true}]');
$cli->shouldReceive('run')->once()->with('brew services info --all --json')->andReturn('[{"name":"php","running":true}]');
swap(CommandLine::class, $cli);
$status = resolve(Status::class);
$this->assertTrue($status->isBrewServiceRunning('nginx'));
$this->assertTrue($status->isBrewServiceRunning('php'));
}
public function test_it_checks_imprecisely_if_brew_services_are_running()
{
$cli = Mockery::mock(CommandLine::class);
$cli->shouldReceive('runAsUser')->once()->with('brew services info --all --json')->andReturn('[{"name":"nginx","running":true}]');
$cli->shouldReceive('run')->once()->with('brew services info --all --json')->andReturn('[{"name":"php@8.1","running":true}]');
swap(CommandLine::class, $cli);
$status = resolve(Status::class);
$this->assertTrue($status->isBrewServiceRunning('nginx'));
$this->assertTrue($status->isBrewServiceRunning('php', exactMatch: false));
}
}