mirror of
https://github.com/NNTmux/newznab-tmux.git
synced 2026-08-28 17:01:16 +00:00
Update to latest tmux pane handling
This commit is contained in:
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace Tests\Feature\Console;
|
||||
|
||||
use Illuminate\Contracts\Console\Kernel;
|
||||
use Illuminate\Process\PendingProcess;
|
||||
use Illuminate\Support\Facades\Process;
|
||||
use PDO;
|
||||
use Tests\TestCase;
|
||||
@@ -88,7 +89,7 @@ class TmuxHealthCheckCommandTest extends TestCase
|
||||
->expectsOutputToContain("Tmux session 'test-session' does not exist.")
|
||||
->assertExitCode(0);
|
||||
|
||||
Process::assertRanTimes("tmux list-sessions 2>/dev/null | grep -q '^test-session:'", 1);
|
||||
Process::assertRanTimes(fn (PendingProcess $process): bool => $this->isTmuxCommand($process, 'has-session'), 1);
|
||||
Process::assertNotRan('which tmux 2>/dev/null');
|
||||
}
|
||||
|
||||
@@ -101,7 +102,7 @@ class TmuxHealthCheckCommandTest extends TestCase
|
||||
->expectsOutputToContain("Tmux session 'test-session' does not exist.")
|
||||
->assertExitCode(1);
|
||||
|
||||
Process::assertRanTimes("tmux list-sessions 2>/dev/null | grep -q '^test-session:'", 1);
|
||||
Process::assertRanTimes(fn (PendingProcess $process): bool => $this->isTmuxCommand($process, 'has-session'), 1);
|
||||
Process::assertNotRan('which tmux 2>/dev/null');
|
||||
}
|
||||
|
||||
@@ -115,25 +116,50 @@ class TmuxHealthCheckCommandTest extends TestCase
|
||||
->expectsOutputToContain("Tmux session 'test-session' restarted successfully.")
|
||||
->assertExitCode(0);
|
||||
|
||||
Process::assertRanTimes("tmux list-sessions 2>/dev/null | grep -q '^test-session:'", 4);
|
||||
Process::assertRanTimes(fn (PendingProcess $process): bool => $this->isTmuxCommand($process, 'has-session'), 4);
|
||||
Process::assertRan('which tmux 2>/dev/null');
|
||||
Process::assertRan(fn ($process): bool => str_contains($process->command, 'new-session -d -s test-session'));
|
||||
Process::assertRan(fn (PendingProcess $process): bool => $this->isTmuxCommand($process, 'new-session'));
|
||||
}
|
||||
|
||||
private function fakeMissingSession(): void
|
||||
{
|
||||
Process::fake([
|
||||
"tmux list-sessions 2>/dev/null | grep -q '^test-session:'" => Process::result('', '', 1),
|
||||
]);
|
||||
Process::fake(fn () => Process::result('', '', 1));
|
||||
}
|
||||
|
||||
private function fakeMissingSessionWithSuccessfulStart(): void
|
||||
{
|
||||
Process::fake([
|
||||
"tmux list-sessions 2>/dev/null | grep -q '^test-session:'" => Process::result('', '', 1),
|
||||
'which tmux 2>/dev/null' => Process::result('/usr/bin/tmux'.PHP_EOL),
|
||||
'*' => Process::result(),
|
||||
]);
|
||||
$nextPaneId = 1;
|
||||
|
||||
Process::fake(function (PendingProcess $process) use (&$nextPaneId) {
|
||||
if ($process->command === 'which tmux 2>/dev/null') {
|
||||
return Process::result('/usr/bin/tmux'.PHP_EOL);
|
||||
}
|
||||
|
||||
if (! is_array($process->command)) {
|
||||
return Process::result();
|
||||
}
|
||||
|
||||
if (in_array('has-session', $process->command, true)) {
|
||||
return Process::result('', '', 1);
|
||||
}
|
||||
|
||||
if (in_array('new-session', $process->command, true)
|
||||
|| in_array('new-window', $process->command, true)
|
||||
|| in_array('split-window', $process->command, true)) {
|
||||
return Process::result('%'.$nextPaneId++."\n");
|
||||
}
|
||||
|
||||
if (in_array('list-panes', $process->command, true)) {
|
||||
return Process::result("%1\tmonitor\n");
|
||||
}
|
||||
|
||||
return Process::result();
|
||||
});
|
||||
}
|
||||
|
||||
private function isTmuxCommand(PendingProcess $process, string $command): bool
|
||||
{
|
||||
return is_array($process->command) && in_array($command, $process->command, true);
|
||||
}
|
||||
|
||||
private function setSetting(string $name, string $value): void
|
||||
|
||||
@@ -0,0 +1,297 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Enums\TmuxPaneRole;
|
||||
use App\Services\Tmux\TmuxLayoutBuilder;
|
||||
use App\Services\Tmux\TmuxPaneManager;
|
||||
use App\Services\Tmux\TmuxSessionManager;
|
||||
use App\Services\Tmux\TmuxTaskRunner;
|
||||
use Illuminate\Process\PendingProcess;
|
||||
use Illuminate\Support\Facades\Process;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use RuntimeException;
|
||||
use Tests\TestCase;
|
||||
|
||||
class TmuxPaneManagementTest extends TestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
Process::preventStrayProcesses();
|
||||
}
|
||||
|
||||
public function test_roles_resolve_to_stable_pane_ids(): void
|
||||
{
|
||||
Process::fake([
|
||||
'*' => Process::result("%12\tmonitor\n%27\tpost_movies\n"),
|
||||
]);
|
||||
|
||||
$manager = new TmuxPaneManager('test session');
|
||||
|
||||
$this->assertSame('%12', $manager->paneForRole(TmuxPaneRole::Monitor));
|
||||
$this->assertSame('%27', $manager->paneForRole(TmuxPaneRole::PostMovies));
|
||||
|
||||
Process::assertRanTimes(
|
||||
fn (PendingProcess $process): bool => is_array($process->command)
|
||||
&& in_array('list-panes', $process->command, true),
|
||||
1,
|
||||
);
|
||||
}
|
||||
|
||||
public function test_duplicate_roles_are_rejected(): void
|
||||
{
|
||||
Process::fake([
|
||||
'*' => Process::result("%12\tmonitor\n%27\tmonitor\n"),
|
||||
]);
|
||||
|
||||
$this->expectException(RuntimeException::class);
|
||||
$this->expectExceptionMessage("role 'monitor' is assigned more than once");
|
||||
|
||||
(new TmuxPaneManager('test-session'))->paneForRole(TmuxPaneRole::Monitor);
|
||||
}
|
||||
|
||||
public function test_missing_roles_are_rejected(): void
|
||||
{
|
||||
Process::fake([
|
||||
'*' => Process::result("%12\tmonitor\n"),
|
||||
]);
|
||||
|
||||
$this->expectException(RuntimeException::class);
|
||||
$this->expectExceptionMessage("role 'post_movies' was not found");
|
||||
|
||||
(new TmuxPaneManager('test-session'))->paneForRole(TmuxPaneRole::PostMovies);
|
||||
}
|
||||
|
||||
public function test_legacy_coordinate_is_tagged_during_role_resolution(): void
|
||||
{
|
||||
Process::fake(function (PendingProcess $process) {
|
||||
if (is_array($process->command) && in_array('list-panes', $process->command, true)) {
|
||||
return Process::result("%8\t\n");
|
||||
}
|
||||
|
||||
if (is_array($process->command) && in_array('display-message', $process->command, true)) {
|
||||
return Process::result("%8\n");
|
||||
}
|
||||
|
||||
return Process::result();
|
||||
});
|
||||
|
||||
$manager = new TmuxPaneManager('legacy-session');
|
||||
|
||||
$this->assertSame('%8', $manager->paneForRole(TmuxPaneRole::Monitor, '0.0'));
|
||||
|
||||
Process::assertRan(function (PendingProcess $process): bool {
|
||||
return $process->command === [
|
||||
'tmux',
|
||||
'set-option',
|
||||
'-p',
|
||||
'-t',
|
||||
'%8',
|
||||
'@nntmux_role',
|
||||
'monitor',
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
public function test_respawn_passes_the_command_as_one_argument(): void
|
||||
{
|
||||
Process::fake();
|
||||
|
||||
$command = <<<'SH'
|
||||
echo "$HOME" && printf '%s\n' 'quoted value'
|
||||
SH;
|
||||
|
||||
$this->assertTrue((new TmuxPaneManager('test session'))->respawnPane('%42', $command, kill: true));
|
||||
|
||||
Process::assertRan(function (PendingProcess $process) use ($command): bool {
|
||||
return $process->command === [
|
||||
'tmux',
|
||||
'respawn-pane',
|
||||
'-k',
|
||||
'-t',
|
||||
'%42',
|
||||
$command,
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
#[DataProvider('layoutRolesProvider')]
|
||||
public function test_layouts_tag_every_logical_pane(int $mode, array $expectedRoles): void
|
||||
{
|
||||
$nextPaneId = 1;
|
||||
$assignedRoles = [];
|
||||
$splitTargets = [];
|
||||
|
||||
Process::fake(function (PendingProcess $process) use (&$nextPaneId, &$assignedRoles, &$splitTargets) {
|
||||
$command = $process->command;
|
||||
if (! is_array($command)) {
|
||||
return Process::result();
|
||||
}
|
||||
|
||||
if (in_array('has-session', $command, true)) {
|
||||
return Process::result('', '', 1);
|
||||
}
|
||||
|
||||
if (in_array('new-session', $command, true)
|
||||
|| in_array('new-window', $command, true)
|
||||
|| in_array('split-window', $command, true)) {
|
||||
if (in_array('split-window', $command, true)) {
|
||||
$splitTargets[] = $command[array_search('-t', $command, true) + 1];
|
||||
}
|
||||
|
||||
return Process::result('%'.$nextPaneId++."\n");
|
||||
}
|
||||
|
||||
$roleOptionIndex = array_search('@nntmux_role', $command, true);
|
||||
if ($roleOptionIndex !== false) {
|
||||
$assignedRoles[] = $command[$roleOptionIndex + 1];
|
||||
}
|
||||
|
||||
return Process::result();
|
||||
});
|
||||
|
||||
$sessionManager = new TmuxSessionManager('test-session');
|
||||
$layoutBuilder = new class($sessionManager) extends TmuxLayoutBuilder
|
||||
{
|
||||
protected function createOptionalWindows(): void {}
|
||||
};
|
||||
|
||||
$this->assertTrue($layoutBuilder->buildLayout($mode), (string) $layoutBuilder->lastError());
|
||||
$this->assertEqualsCanonicalizing($expectedRoles, $assignedRoles);
|
||||
|
||||
foreach ($splitTargets as $target) {
|
||||
$this->assertStringStartsWith('%', $target);
|
||||
}
|
||||
}
|
||||
|
||||
public static function layoutRolesProvider(): array
|
||||
{
|
||||
return [
|
||||
'full' => [
|
||||
0,
|
||||
[
|
||||
'monitor',
|
||||
'binaries',
|
||||
'backfill',
|
||||
'releases',
|
||||
'fix_names',
|
||||
'remove_crap',
|
||||
'post_additional',
|
||||
'post_movies',
|
||||
'post_tv',
|
||||
'post_metadata',
|
||||
'irc_scraper',
|
||||
],
|
||||
],
|
||||
'basic' => [
|
||||
1,
|
||||
[
|
||||
'monitor',
|
||||
'releases',
|
||||
'fix_names',
|
||||
'remove_crap',
|
||||
'post_additional',
|
||||
'post_movies',
|
||||
'post_tv',
|
||||
'post_metadata',
|
||||
'irc_scraper',
|
||||
],
|
||||
],
|
||||
'stripped' => [
|
||||
2,
|
||||
[
|
||||
'monitor',
|
||||
'sequential',
|
||||
'fix_names',
|
||||
'post_metadata',
|
||||
'irc_scraper',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function test_partial_layout_is_removed_after_a_split_failure(): void
|
||||
{
|
||||
$sessionExists = false;
|
||||
$splitAttempts = 0;
|
||||
|
||||
Process::fake(function (PendingProcess $process) use (&$sessionExists, &$splitAttempts) {
|
||||
$command = $process->command;
|
||||
if (! is_array($command)) {
|
||||
return Process::result();
|
||||
}
|
||||
|
||||
if (in_array('has-session', $command, true)) {
|
||||
return Process::result('', '', $sessionExists ? 0 : 1);
|
||||
}
|
||||
|
||||
if (in_array('new-session', $command, true)) {
|
||||
$sessionExists = true;
|
||||
|
||||
return Process::result("%1\n");
|
||||
}
|
||||
|
||||
if (in_array('split-window', $command, true)) {
|
||||
$splitAttempts++;
|
||||
if ($splitAttempts === 2) {
|
||||
return Process::result('', 'split failed', 1);
|
||||
}
|
||||
|
||||
return Process::result("%2\n");
|
||||
}
|
||||
|
||||
if (in_array('kill-session', $command, true)) {
|
||||
$sessionExists = false;
|
||||
}
|
||||
|
||||
return Process::result();
|
||||
});
|
||||
|
||||
$sessionManager = new TmuxSessionManager('test-session');
|
||||
$layoutBuilder = new class($sessionManager) extends TmuxLayoutBuilder
|
||||
{
|
||||
protected function createOptionalWindows(): void {}
|
||||
};
|
||||
|
||||
$this->assertFalse($layoutBuilder->buildLayout(0));
|
||||
$this->assertSame('split failed', $layoutBuilder->lastError());
|
||||
$this->assertFalse($sessionExists);
|
||||
|
||||
Process::assertRan(
|
||||
fn (PendingProcess $process): bool => is_array($process->command)
|
||||
&& in_array('kill-session', $process->command, true),
|
||||
);
|
||||
}
|
||||
|
||||
public function test_task_runner_resolves_a_role_before_respawning(): void
|
||||
{
|
||||
Process::fake(function (PendingProcess $process) {
|
||||
if (is_array($process->command) && in_array('list-panes', $process->command, true)) {
|
||||
return Process::result("%9\tpost_movies\n");
|
||||
}
|
||||
|
||||
return Process::result();
|
||||
});
|
||||
|
||||
$runner = new TmuxTaskRunner('test-session');
|
||||
|
||||
$this->assertTrue($runner->runTask('Movies', [
|
||||
'role' => TmuxPaneRole::PostMovies,
|
||||
'command' => 'php artisan example',
|
||||
]));
|
||||
|
||||
Process::assertRan(function (PendingProcess $process): bool {
|
||||
return $process->command === [
|
||||
'tmux',
|
||||
'respawn-pane',
|
||||
'-t',
|
||||
'%9',
|
||||
'php artisan example',
|
||||
];
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user