Skip to content

Commit 298142c

Browse files
committed
Merge hydephp/realtime-compiler#27 into monorepo
1 parent c8116d4 commit 298142c

File tree

7 files changed

+238
-29
lines changed

7 files changed

+238
-29
lines changed

packages/realtime-compiler/.github/workflows/test.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,8 @@ jobs:
1616
- name: Install dependencies
1717
run: composer install
1818

19-
- name: Run tests
20-
run: vendor/bin/phpunit
19+
- name: Set up test runner
20+
run: php -r 'require_once __DIR__."/vendor/autoload.php"; \Hyde\RealtimeCompiler\Tests\Integration\IntegrationTestCase::setUpTestRunner();'
21+
22+
- name: Run tests with PHPUnit
23+
run: vendor/bin/phpunit --colors=always

packages/realtime-compiler/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
/composer.lock
44
/.phpunit.cache/
55
/.phpunit.result.cache
6+
/tests/runner

packages/realtime-compiler/composer.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@
3434
}
3535
},
3636
"require-dev": {
37-
"phpunit/phpunit": "^10.0"
37+
"phpunit/phpunit": "^10.0",
38+
"robiningelbrecht/phpunit-pretty-print": "^1.3",
39+
"ext-zip": "*"
3840
}
3941
}

packages/realtime-compiler/phpunit.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
bootstrap="vendor/autoload.php"
55
colors="true"
66
>
7+
<extensions>
8+
<bootstrap class="RobinIngelbrecht\PHPUnitPrettyPrint\PhpUnitExtension"/>
9+
</extensions>
710
<testsuites>
811
<testsuite name="Integration Test Suite">
912
<directory suffix="Test.php">./tests/Integration</directory>

packages/realtime-compiler/tests/Integration/IntegrationTest.php

Lines changed: 10 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,36 +2,20 @@
22

33
namespace Hyde\RealtimeCompiler\Tests\Integration;
44

5-
use InvalidArgumentException;
6-
use PHPUnit\Framework\TestCase;
7-
8-
class IntegrationTest extends TestCase
5+
class IntegrationTest extends IntegrationTestCase
96
{
10-
public static function setUpBeforeClass(): void
11-
{
12-
$monorepo = realpath(__DIR__.'/../../../../');
13-
14-
if ($monorepo && realpath(getcwd()) === $monorepo && file_exists($monorepo.'/hyde')) {
15-
throw new InvalidArgumentException('This test suite is not intended to be run from the monorepo.');
16-
}
17-
18-
if (! self::hasTestRunnerSetUp()) {
19-
self::setUpTestRunner();
20-
}
21-
}
22-
23-
public function testExample()
24-
{
25-
$this->assertTrue(true);
26-
}
27-
28-
private static function hasTestRunnerSetUp(): bool
7+
public function testWelcome()
298
{
30-
return false;
9+
$this->get('/')
10+
->assertStatus(200)
11+
->assertSeeText("You're running on HydePHP");
3112
}
3213

33-
private static function setUpTestRunner(): void
14+
public function test404()
3415
{
35-
// Set up the test runner
16+
$this->get('/non-existent-page')
17+
->assertStatus(404)
18+
->assertSeeText('RouteNotFoundException')
19+
->assertSeeText('Route [non-existent-page] not found.');
3620
}
3721
}
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
<?php
2+
3+
namespace Hyde\RealtimeCompiler\Tests\Integration;
4+
5+
use InvalidArgumentException;
6+
use PHPUnit\Framework\TestCase;
7+
use RuntimeException;
8+
use ZipArchive;
9+
10+
abstract class IntegrationTestCase extends TestCase
11+
{
12+
/** @var resource */
13+
protected static $server;
14+
15+
public static function setUpBeforeClass(): void
16+
{
17+
$monorepo = realpath(__DIR__.'/../../../../');
18+
19+
if ($monorepo && realpath(getcwd()) === $monorepo && file_exists($monorepo.'/hyde')) {
20+
throw new InvalidArgumentException('This test suite is not intended to be run from the monorepo.');
21+
}
22+
23+
if (! self::hasTestRunnerSetUp()) {
24+
self::setUpTestRunner();
25+
}
26+
27+
// Check that post 8080 is available
28+
$process = @fsockopen('localhost', 8080, $errno, $errstr, 1);
29+
30+
if ($process) {
31+
// Try to find the PID of the process using the port
32+
if (PHP_OS_FAMILY === 'Windows') {
33+
$raw = shell_exec('netstat -aon | findstr :8080');
34+
// Get the PID of the process (last column of the first line)
35+
preg_match('/\s+(\d+)$/', explode("\n", $raw)[0], $matches);
36+
$pid = trim($matches[1]);
37+
} else {
38+
$pid = shell_exec('lsof -t -i:8080');
39+
}
40+
41+
fclose($process);
42+
$throwInsteadOfKill = false;
43+
if ($throwInsteadOfKill) {
44+
throw new RuntimeException(sprintf('Port 8080 is already in use. (PID %s)', $pid));
45+
} else {
46+
// Kill the process using the port
47+
shell_exec(PHP_OS_FAMILY === 'Windows' ? "taskkill /F /PID $pid" : "kill -9 $pid");
48+
}
49+
}
50+
51+
// Start the server in a background process, keeping the task ID for later
52+
$null = PHP_OS_FAMILY === 'Windows' ? 'NUL' : '/dev/null';
53+
self::$server = proc_open("php hyde serve > $null", [], $pipes, realpath(__DIR__.'/../runner'));
54+
55+
// Wait for the server to start
56+
while (@fsockopen('localhost', 8080, $errno, $errstr, 1) === false) {
57+
if (proc_get_status(self::$server)['running'] === false) {
58+
break;
59+
}
60+
}
61+
62+
// Assert that the server was started successfully
63+
if (! self::$server) {
64+
throw new RuntimeException('Failed to start the test server.');
65+
}
66+
}
67+
68+
public function __destruct()
69+
{
70+
// Ensure the server is stopped, regardless of any errors
71+
if (self::$server) {
72+
proc_terminate(self::$server);
73+
}
74+
}
75+
76+
protected static function hasTestRunnerSetUp(): bool
77+
{
78+
return file_exists(__DIR__.'/../runner');
79+
}
80+
81+
public static function setUpTestRunner(): void
82+
{
83+
echo "\33[33mSetting up test runner...\33[0m This may take a while.\n";
84+
85+
$archive = 'https://github.com/hydephp/hyde/archive/refs/heads/master.zip';
86+
$target = __DIR__.'/../runner';
87+
88+
$raw = file_get_contents($archive);
89+
90+
if ($raw === false) {
91+
throw new RuntimeException('Failed to download test runner.');
92+
}
93+
94+
$zipPath = tempnam(sys_get_temp_dir(), 'hyde-master');
95+
96+
if ($zipPath === false) {
97+
throw new RuntimeException('Failed to create temporary file.');
98+
}
99+
100+
file_put_contents($zipPath, $raw);
101+
102+
$zip = new ZipArchive();
103+
104+
if ($zip->open($zipPath) !== true) {
105+
throw new RuntimeException('Failed to open zip archive.');
106+
}
107+
108+
// Get the name of the root directory in the zip file
109+
$rootDir = $zip->getNameIndex(0);
110+
111+
// Extract to a temporary directory
112+
$tempExtractPath = $target.'_temp';
113+
$zip->extractTo($tempExtractPath);
114+
115+
$zip->close();
116+
117+
// Move the contents of the extracted directory to the target directory
118+
rename($tempExtractPath.'/'.$rootDir, $target);
119+
120+
// Remove the temporary extraction directory
121+
rmdir($tempExtractPath);
122+
123+
unlink($zipPath);
124+
125+
$runner = realpath($target);
126+
127+
// Junction the package source of hyde/realtime-compiler to the test runner
128+
$branch = trim(shell_exec('git rev-parse --abbrev-ref HEAD') ?: 'master');
129+
shell_exec("cd $runner && composer config repositories.realtime-compiler path ../../");
130+
shell_exec("cd $runner && composer require --dev hyde/realtime-compiler:dev-$branch --no-progress > setup.log 2>&1");
131+
}
132+
133+
public function get(string $uri): TestResponse
134+
{
135+
return TestResponse::get($this, $uri);
136+
}
137+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?php
2+
3+
namespace Hyde\RealtimeCompiler\Tests\Integration;
4+
5+
use GuzzleHttp\Client;
6+
use Psr\Http\Message\ResponseInterface;
7+
8+
class TestResponse
9+
{
10+
protected ResponseInterface $response;
11+
protected IntegrationTestCase $test;
12+
13+
protected string $html;
14+
protected string $text;
15+
16+
public static function get(IntegrationTestCase $test, string $uri): static
17+
{
18+
$guzzle = new Client();
19+
20+
$response = $guzzle->get('http://localhost:8080'.$uri, ['http_errors' => false]);
21+
22+
return new static($test, $response);
23+
}
24+
25+
public function __construct(IntegrationTestCase $test, ResponseInterface $response)
26+
{
27+
$this->test = $test;
28+
$this->response = $response;
29+
30+
$this->parsePageData();
31+
}
32+
33+
protected function parsePageData(): void
34+
{
35+
$this->html = $this->response->getBody()->getContents();
36+
$this->text = $this->extractText($this->html);
37+
}
38+
39+
protected function extractText(string $html): string
40+
{
41+
// Strip script and style tags
42+
$html = preg_replace('/<style\b[^>]*>(.*?)<\/style>/is', '', $html);
43+
$html = preg_replace('/<script\b[^>]*>(.*?)<\/script>/is', '', $html);
44+
45+
$html = strip_tags($html);
46+
47+
$html = implode("\n", array_map('trim', explode("\n", $html)));
48+
49+
// Remove double spaces and double newlines
50+
$html = preg_replace('/\n{2,}/', "\n", $html);
51+
$html = preg_replace('/\s{2,}/', ' ', $html);
52+
53+
return trim($html);
54+
}
55+
56+
public function dd(): void
57+
{
58+
dd($this->html);
59+
}
60+
61+
public function ddText(): void
62+
{
63+
dd($this->text);
64+
}
65+
66+
public function assertStatus(int $code): static
67+
{
68+
$this->test->assertSame($code, $this->response->getStatusCode());
69+
70+
return $this;
71+
}
72+
73+
public function assertSeeText(string $text): static
74+
{
75+
$this->test->assertStringContainsString($text, $this->text);
76+
77+
return $this;
78+
}
79+
}

0 commit comments

Comments
 (0)