Skip to content

Commit

Permalink
allow passing through arguments to the command to be benchmarked...
Browse files Browse the repository at this point in the history
a little quirk is that options for the benchmark command itself need to be before options passed to the command to be benchmarked
  • Loading branch information
tanerkay committed Feb 18, 2025
1 parent f9be901 commit 1f11a3a
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 1 deletion.
60 changes: 59 additions & 1 deletion src/BenchmarksArtisanCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,17 @@
namespace ChristophRumpel\ArtisanBenchmark;

use Illuminate\Database\Events\QueryExecuted;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Number;
use Illuminate\Support\Str;
use Symfony\Component\Console\Exception\CommandNotFoundException;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\StringInput;

use function Laravel\Prompts\select;

Expand All @@ -22,6 +27,13 @@ trait BenchmarksArtisanCommand

protected ?int $tableToWatchBeginCount;

public function __construct()
{
parent::__construct();

$this->ignoreValidationErrors();
}

public function handle(): void
{
$commandToBenchmark = $this->argument('signature');
Expand All @@ -32,10 +44,56 @@ public function handle(): void
}

$this->startBenchmark();
$this->call($commandToBenchmark);
$this->call($commandToBenchmark, $this->getArgumentsToPassThough());
$this->endBenchmark();
}

protected function getArgumentsToPassThough(): array
{
$commandToBenchmark = $this->argument('signature');

if (! $commandToBenchmark) {
return [];
}

$command = Artisan::all()[$commandToBenchmark] ?? null;

if (! $command) {
throw new CommandNotFoundException(
\sprintf('Command "%s" does not exist.', $commandToBenchmark)
);
}

$booleanOptions = array_keys(array_filter(
$command->getDefinition()->getOptions(),
fn (InputOption $option) => ! $option->acceptValue(),
));

$tokens = collect((new StringInput($this->input))->getRawTokens())
->reject(fn (string $token): string => str_starts_with($token, '--tableToWatch='))
->map(function (string $token) use ($booleanOptions): string {
$optionName = Str::match('/^--([^=]+)=/', $token);

if (in_array($optionName, $booleanOptions)) {
return '--'.$optionName;
}

return $token;
})
->slice(1)
->toArray();

$input = new ArgvInput($tokens, $command->getDefinition());

return [
...$input->getArguments(),
...Arr::mapWithKeys(
$input->getOptions(),
fn (array|string|null $option, string $key): array => [Str::start($key, '--') => $option]
),
];
}

protected function startBenchmark(): void
{
$this->benchmarkStartTime = microtime(true);
Expand Down
28 changes: 28 additions & 0 deletions tests/Commands/TestWithArgumentsCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

namespace Tests\Commands;

use Illuminate\Console\Command;

class TestWithArgumentsCommand extends Command
{
protected $signature = '
with-arguments:test
{custom-argument}
{--custom-option}
{--C|custom-option-with-shortcut}
{--custom-option-with-value=}
{--custom-option-array=*}
';

public function handle(): void
{
$this->line('Custom argument: '.$this->argument('custom-argument'));
$this->line('Custom option: '.$this->option('custom-option'));
$this->line('Custom option with shortcut: '.$this->option('custom-option-with-shortcut'));
$this->line('Custom option with value: '.$this->option('custom-option-with-value'));
$this->line('Custom option array: '.implode(', ', $this->option('custom-option-array')));
}
}
83 changes: 83 additions & 0 deletions tests/Feature/ArtisanBenchmarkCommandTest.php
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
<?php

use ChristophRumpel\ArtisanBenchmark\Console\ArtisanBenchmarkCommand;
use Illuminate\Contracts\Console\Kernel;
use Illuminate\Support\Facades\Artisan;
use Symfony\Component\Console\Exception\CommandNotFoundException;
use Symfony\Component\Console\Input\StringInput;
use Symfony\Component\Console\Output\BufferedOutput;
use Tests\Commands\TestImportCommand;
use Tests\Commands\TestWithArgumentsCommand;

test('it can benchmark an artisan command with explicit signature', function () {
// Act
Expand All @@ -17,6 +21,85 @@
->toContain('SQL: 0');
});

test('it can benchmark an artisan command with arguments passed through', function () {
// Arrange
app(Kernel::class)->registerCommand(new TestWithArgumentsCommand);

// Act
Artisan::call(
ArtisanBenchmarkCommand::class,
[
'signature' => 'with-arguments:test',
'custom-argument' => true,
'--custom-option' => true,
'--custom-option-with-shortcut' => true,
'--custom-option-with-value' => 1,
'--custom-option-array' => [1, 2],
]
);

$output = Artisan::output();

// Assert
expect($output)
->toContain('Custom argument: 1')
->toContain('Custom option: 1')
->toContain('Custom option with shortcut: 1')
->toContain('Custom option with value: 1')
->toContain('Custom option array: 1, 2');
});

test('it can benchmark an artisan command with arguments passed through as string', function () {
// Arrange
app(Kernel::class)->registerCommand(new TestWithArgumentsCommand);

$input = new StringInput('
benchmark
with-arguments:test
1
--custom-option
-C
--custom-option-with-value=1
--custom-option-array=1
--custom-option-array=2
');

// Act
Artisan::handle($input, $output = new BufferedOutput);

// Assert
expect($output->fetch())
->toContain('Custom argument: 1')
->toContain('Custom option: 1')
->toContain('Custom option with shortcut: 1')
->toContain('Custom option with value: 1')
->toContain('Custom option array: 1, 2');
});

test('it can benchmark an artisan command with arguments mixed with tableToWatch option', function () {
// Arrange
$this->loadLaravelMigrations();

app(Kernel::class)->registerCommand(new TestWithArgumentsCommand);

$input = new StringInput('
benchmark
--tableToWatch=users
with-arguments:test
1
--custom-option
');

// Act
Artisan::handle($input, $output = new BufferedOutput);

// Assert
expect($output->fetch())
->toContain('Custom argument: 1')
->toContain('Custom option: 1')
->toContain('ROWS: 0');
});

test('it asks for command selected from a list', function () {
// Act & Assert
$this->artisan(ArtisanBenchmarkCommand::class)
Expand Down
3 changes: 3 additions & 0 deletions tests/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
namespace Tests;

use ChristophRumpel\ArtisanBenchmark\ArtisanBenchmarkServiceProvider;
use Illuminate\Foundation\Testing\DatabaseTransactions;

abstract class TestCase extends \Orchestra\Testbench\TestCase
{
use DatabaseTransactions;

protected function getPackageProviders($app): array
{
return [
Expand Down

0 comments on commit 1f11a3a

Please sign in to comment.