Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
strategy:
fail-fast: false
matrix:
php: [ "7.4", "8.0", "8.1", "8.2", "8.3", "8.4" ]
php: [ "7.4", "8.0", "8.1", "8.2", "8.3", "8.4", "8.5" ]

steps:
- uses: actions/checkout@v4
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ The command writes one JSON event per line, each prefixed with
`TWEAKPHP_STREAM:`:

```text
TWEAKPHP_STREAM:{"type":"started","pid":1234}
TWEAKPHP_STREAM:{"type":"statement.started","index":0,"line":2,"code":"echo \"First\";"}
TWEAKPHP_STREAM:{"type":"output","index":0,"data":"First"}
TWEAKPHP_STREAM:{"type":"statement.completed","index":0,"queries":[]}
Expand All @@ -131,6 +132,10 @@ TWEAKPHP_STREAM:{"type":"completed"}

Event types:

- `started`: the client started. It is emitted as the first line, before the
project's framework bootstraps, so callers can stop the process even when
bootstrapping hangs. It includes the client process id as `pid`; `pid` is
omitted when unavailable.
- `statement.started`: a statement started
- `output`: the statement produced output. Presenter output also includes an
`html` field containing the rendered HTML fragment; plain `echo` output only
Expand Down
9 changes: 9 additions & 0 deletions index.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,15 @@
echo 'TWEAKPHP_ERROR:'.json_encode($error, JSON_INVALID_UTF8_SUBSTITUTE).PHP_EOL;
};

if ($command === 'execute-stream') {
$started = ['type' => 'started'];
$pid = getmypid();
if ($pid !== false && $pid > 0) {
$started['pid'] = (int) $pid;
}
$writeStreamEvent($started);
}

$customLoader = Cli::getArgument('loader');
try {
QueryCollector::enable(filter_var(Cli::getArgument('query-log'), FILTER_VALIDATE_BOOL));
Expand Down
79 changes: 79 additions & 0 deletions tests/ExecuteStreamTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php

namespace TweakPHP\Client\Tests;

use PHPUnit\Framework\TestCase;

class ExecuteStreamTest extends TestCase
{
public function test_started_event_with_pid_is_the_first_stream_line()
{
$lines = $this->runClient('execute-stream', 'echo 1;');

$this->assertStringStartsWith('TWEAKPHP_STREAM:', $lines[0]);

$event = json_decode(substr($lines[0], strlen('TWEAKPHP_STREAM:')), true);
$this->assertSame(JSON_ERROR_NONE, json_last_error());
$this->assertSame('started', $event['type']);
$this->assertArrayHasKey('pid', $event);
$this->assertIsInt($event['pid']);
$this->assertGreaterThan(0, $event['pid']);
}

public function test_execute_stream_emits_the_full_event_sequence()
{
$lines = $this->runClient('execute-stream', 'echo 1;');

$types = [];
foreach ($lines as $line) {
$this->assertStringStartsWith('TWEAKPHP_STREAM:', $line);

$event = json_decode(substr($line, strlen('TWEAKPHP_STREAM:')), true);
$this->assertSame(JSON_ERROR_NONE, json_last_error());
$types[] = $event['type'];
}

$this->assertSame(
['started', 'statement.started', 'output', 'statement.completed', 'completed'],
$types
);
}

public function test_execute_does_not_emit_stream_events()
{
$lines = $this->runClient('execute', 'echo 1;');

$this->assertNotEmpty($lines);
$this->assertStringStartsWith('TWEAKPHP_RESULT:', $lines[0]);

foreach ($lines as $line) {
$this->assertStringNotContainsString('TWEAKPHP_STREAM:', $line);
}
}

private function runClient(string $command, string $code): array
{
$projectPath = sys_get_temp_dir().'/tweakphp-client-test-'.uniqid();
mkdir($projectPath);

try {
$executable = sprintf(
'%s %s %s %s %s 2>/dev/null',
escapeshellarg(PHP_BINARY),
escapeshellarg(__DIR__.'/../index.php'),
escapeshellarg($projectPath),
escapeshellarg($command),
escapeshellarg(base64_encode($code))
);

exec($executable, $lines, $exitCode);

$this->assertSame(0, $exitCode);
$this->assertNotEmpty($lines);

return $lines;
} finally {
@rmdir($projectPath);
}
}
}
Loading