Skip to content

Commit 5ec9801

Browse files
committed
Fix screen version detection on macOS
1 parent 0d95edd commit 5ec9801

File tree

2 files changed

+156
-5
lines changed

2 files changed

+156
-5
lines changed

src/Console/Commands/Solo.php

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,16 +38,19 @@ protected function checkScreenVersion(): void
3838
$process = new Process(['screen', '-v']);
3939
$process->run();
4040

41-
if ($process->isSuccessful()) {
42-
preg_match('/Screen version ([\d.]+)/', $process->getOutput(), $matches);
41+
$screenOutput = trim($process->getOutput() . "\n" . $process->getErrorOutput());
4342

44-
if (!empty($matches[1]) && version_compare($matches[1], '5.0.0', '<')) {
43+
preg_match('/Screen version ([\d.]+)/', $screenOutput, $matches);
44+
45+
if (!empty($matches[1])) {
46+
if (version_compare($matches[1], '5.0.0', '<')) {
4547
Log::error("The installed version of `screen` ({$matches[1]}) is outdated. Please upgrade to 5.0.0 or greater for best compatibility with Solo.");
4648
}
47-
} else {
48-
Log::error('Unable to determine `screen` version. Make sure `screen` is installed.');
49+
50+
return;
4951
}
5052

53+
Log::error('Unable to determine `screen` version. Make sure `screen` is installed.');
5154
}
5255

5356
protected function usesScreenDriver(): bool

tests/Unit/SoloCommandTest.php

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
<?php
2+
3+
namespace SoloTerm\Solo\Tests\Unit;
4+
5+
use Illuminate\Support\Facades\Log;
6+
use PHPUnit\Framework\Attributes\Test;
7+
use Psr\Log\AbstractLogger;
8+
use SoloTerm\Solo\Console\Commands\Solo as SoloCommand;
9+
10+
class SoloCommandTest extends Base
11+
{
12+
private string|false $originalPath;
13+
14+
protected function setUp(): void
15+
{
16+
parent::setUp();
17+
18+
$this->originalPath = getenv('PATH');
19+
}
20+
21+
protected function tearDown(): void
22+
{
23+
if ($this->originalPath === false) {
24+
putenv('PATH');
25+
} else {
26+
putenv("PATH={$this->originalPath}");
27+
}
28+
29+
parent::tearDown();
30+
}
31+
32+
#[Test]
33+
public function screen_version_is_detected_from_error_output_even_when_screen_exits_non_zero(): void
34+
{
35+
$logger = $this->swapCapturingLogger();
36+
37+
$this->runWithFakeScreen(<<<'SH'
38+
#!/bin/sh
39+
echo "Screen version 4.9.0" >&2
40+
exit 1
41+
SH, function (): void {
42+
config()->set('solo.process_driver', 'screen');
43+
44+
$command = new class extends SoloCommand
45+
{
46+
public function runScreenVersionCheckForTest(): void
47+
{
48+
$this->checkScreenVersion();
49+
}
50+
};
51+
52+
$command->runScreenVersionCheckForTest();
53+
});
54+
55+
$this->assertSame([
56+
[
57+
'level' => 'error',
58+
'message' => 'The installed version of `screen` (4.9.0) is outdated. Please upgrade to 5.0.0 or greater for best compatibility with Solo.',
59+
],
60+
], $logger->records);
61+
}
62+
63+
#[Test]
64+
public function missing_screen_version_still_logs_detection_error(): void
65+
{
66+
$logger = $this->swapCapturingLogger();
67+
68+
$this->runWithFakeScreen(<<<'SH'
69+
#!/bin/sh
70+
exit 1
71+
SH, function (): void {
72+
config()->set('solo.process_driver', 'screen');
73+
74+
$command = new class extends SoloCommand
75+
{
76+
public function runScreenVersionCheckForTest(): void
77+
{
78+
$this->checkScreenVersion();
79+
}
80+
};
81+
82+
$command->runScreenVersionCheckForTest();
83+
});
84+
85+
$this->assertSame([
86+
[
87+
'level' => 'error',
88+
'message' => 'Unable to determine `screen` version. Make sure `screen` is installed.',
89+
],
90+
], $logger->records);
91+
}
92+
93+
/**
94+
* @param callable(): void $callback
95+
*/
96+
private function runWithFakeScreen(string $script, callable $callback): void
97+
{
98+
$directory = sys_get_temp_dir() . '/solo-screen-' . bin2hex(random_bytes(5));
99+
$screen = $directory . '/screen';
100+
101+
mkdir($directory);
102+
file_put_contents($screen, $script);
103+
chmod($screen, 0755);
104+
105+
$path = $this->originalPath === false ? $directory : $directory . PATH_SEPARATOR . $this->originalPath;
106+
107+
putenv("PATH={$path}");
108+
109+
try {
110+
$callback();
111+
} finally {
112+
if ($this->originalPath === false) {
113+
putenv('PATH');
114+
} else {
115+
putenv("PATH={$this->originalPath}");
116+
}
117+
118+
@unlink($screen);
119+
@rmdir($directory);
120+
}
121+
}
122+
123+
/**
124+
* @return object{records: array<int, array{level: string, message: string}>}
125+
*/
126+
private function swapCapturingLogger(): object
127+
{
128+
$logger = new class extends AbstractLogger
129+
{
130+
/**
131+
* @var array<int, array{level: string, message: string}>
132+
*/
133+
public array $records = [];
134+
135+
public function log($level, string|\Stringable $message, array $context = []): void
136+
{
137+
$this->records[] = [
138+
'level' => (string) $level,
139+
'message' => (string) $message,
140+
];
141+
}
142+
};
143+
144+
Log::swap($logger);
145+
146+
return $logger;
147+
}
148+
}

0 commit comments

Comments
 (0)