Skip to content

Commit d72f8de

Browse files
committed
More tests and type fixes
1 parent 9149764 commit d72f8de

File tree

9 files changed

+122
-39
lines changed

9 files changed

+122
-39
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Just raw PHP! It is magic! This is **not** the classical meaning of async like p
88
[![codecov](https://codecov.io/gh/terremoth/php-async/graph/badge.svg?token=W37V5EDERQ)](https://codecov.io/gh/terremoth/php-async)
99
[![Test Coverage](https://api.codeclimate.com/v1/badges/c6420e5f6ab01e70eed7/test_coverage)](https://codeclimate.com/github/terremoth/php-async/test_coverage)
1010
-->
11+
[![Code Coverage](https://qlty.sh/gh/terremoth/projects/php-async/coverage.svg)](https://qlty.sh/gh/terremoth/projects/php-async)
1112
[![Psalm type coverage](https://shepherd.dev/github/terremoth/php-async/coverage.svg)](https://shepherd.dev/github/terremoth/php-async)
1213
[![Psalm level](https://shepherd.dev/github/terremoth/php-async/level.svg)](https://shepherd.dev/github/terremoth/php-async)
1314
[![Test Run Status](https://github.com/terremoth/php-async/actions/workflows/workflow.yml/badge.svg?branch=main)](https://github.com/terremoth/php-async/actions/workflows/workflow.yml)

composer.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,10 @@
3131
"autoload-dev": {
3232
"psr-4": {
3333
"\\Tests\\": "tests/"
34-
}
34+
},
35+
"files": [
36+
"src/Terremoth/Async/script_functions.php"
37+
]
3538
},
3639
"authors": [
3740
{

demo-file.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Hello users! At: 2026-01-15T09:46:38+00:00

src/Terremoth/Async/Process.php

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use Closure;
66
use Exception;
77
use Laravel\SerializableClosure\SerializableClosure;
8+
use Shmop;
89

910
class Process
1011
{
@@ -22,12 +23,11 @@ public function __construct(private int $shmopKey = 0)
2223
*/
2324
public function send(Closure $asyncFunction): void
2425
{
25-
$dirSlash = DIRECTORY_SEPARATOR;
2626
$serialized = serialize(new SerializableClosure($asyncFunction));
2727
$length = mb_strlen($serialized);
2828

29-
set_error_handler(function (int $errno, string $error) {
30-
throw new Exception($error);
29+
set_error_handler(function (int $errorNumber, string $error) {
30+
throw new Exception($error . ' - ' . $errorNumber);
3131
});
3232

3333
try {
@@ -60,15 +60,37 @@ public function send(Closure $asyncFunction): void
6060
restore_error_handler();
6161
}
6262

63-
$bytesWritten = shmop_write($shmopInstance, $serialized, 0);
63+
$bytesWritten = $this->writeToShmop($shmopInstance, $serialized, 0);
6464

65-
if ($bytesWritten < $length) {
66-
throw new Exception('Error: Could not write the entire data to shared memory with length: ' .
67-
$length . '. Bytes written: ' . $bytesWritten . PHP_EOL);
65+
if ((bool)$bytesWritten === false) {
66+
throw new Exception(
67+
'shmop_write failed when writing to shared memory with key: ' . $this->shmopKey
68+
);
6869
}
6970

70-
$fileWithPath = __DIR__ . $dirSlash . 'background_processor.php';
71-
$file = new PhpFile($fileWithPath, [(string)$this->shmopKey]);
71+
if ($bytesWritten !== $length) {
72+
throw new Exception(
73+
'Could not write all bytes to shared memory. Expected: '
74+
. $length . ', Written: ' . (int)$bytesWritten
75+
);
76+
}
77+
78+
$file = new PhpFile(
79+
__DIR__ . DIRECTORY_SEPARATOR . 'background_processor.php',
80+
[(string) $this->shmopKey]
81+
);
82+
7283
$file->run();
7384
}
85+
86+
/**
87+
* @param Shmop $shmopInstance
88+
* @param string $data
89+
* @param int $offset
90+
* @return int|false
91+
*/
92+
protected function writeToShmop(Shmop $shmopInstance, string $data, int $offset): int|false
93+
{
94+
return shmop_write($shmopInstance, $data, $offset);
95+
}
7496
}

src/Terremoth/Async/background_processor.php

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<?php
22

3+
namespace Terremoth\Async;
4+
35
ini_set('display_errors', 'on');
46
ini_set('display_startup_errors', 1);
57
ini_set('error_log', 'php-async-errors-' . date('YmdH') . '.log');
@@ -11,7 +13,7 @@
1113
use Laravel\SerializableClosure\SerializableClosure;
1214

1315
if (!isset($argv[1])) {
14-
error('Shmop Key not provided');
16+
custom_error('Shmop Key not provided');
1517
exit(1);
1618
}
1719

@@ -20,14 +22,14 @@
2022
$shmopInstance = shmop_open($key, 'a', 0, 0);
2123

2224
if (!$shmopInstance) {
23-
error('Could not open Shmop');
25+
custom_error('Could not open Shmop');
2426
exit(1);
2527
}
2628

2729
$length = shmop_size($shmopInstance);
2830

2931
if ($length === 0) {
30-
error('Shmop length cannot be zero!');
32+
custom_error('Shmop length cannot be zero!');
3133
exit(1);
3234
}
3335

src/Terremoth/Async/script_functions.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
<?php
22

3-
function error(string $error): void
3+
namespace Terremoth\Async;
4+
5+
function custom_error(string $error): void
46
{
57
$error = 'Error: ' . $error;
68
fwrite(STDERR, $error);

tests/Terremoth/AsyncTest/PhpFileTest.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
*/
1313
class PhpFileTest extends TestCase
1414
{
15-
private string $tmpDir;
15+
private string $tmpDir = '';
1616

1717
protected function setUp(): void
1818
{
@@ -23,8 +23,9 @@ protected function setUp(): void
2323

2424
protected function tearDown(): void
2525
{
26-
array_map('unlink', glob($this->tmpDir . '/*'));
27-
rmdir($this->tmpDir);
26+
foreach (glob($this->tmpDir . '/*') as $file) {
27+
unlink($file);
28+
}
2829
}
2930

3031
private function makeFile(string $name, string $content): string

tests/Terremoth/AsyncTest/ProcessTest.php

Lines changed: 11 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,10 @@ public function testSendActuallyWritesSerializedClosureToShmop(): void
6565

6666
$raw = shmop_read($shmop, 0, $size);
6767
$data = rtrim($raw, "\0");
68-
$this->assertIsString($data);
69-
7068
$unserialized = unserialize($data);
7169
$this->assertInstanceOf(SerializableClosure::class, $unserialized);
7270

73-
$func = $unserialized->getClosure();
74-
$this->assertIsCallable($func);
71+
$unserialized->getClosure();
7572

7673
// shmop_delete($shmop);
7774
// shmop_close($shmop); // deprecated
@@ -90,32 +87,26 @@ public function testSendThrowsWhenShmopCannotBeCreated(): void
9087
}
9188

9289
/**
93-
* @throws RandomException
90+
* @throws RandomException|Exception
9491
*/
9592
public function testSendThrowsWhenNotAllBytesAreWritten(): void
9693
{
9794
$key = ftok(__FILE__, 'b') ?: random_int(1, 1000000);
9895

9996
$process = $this->getMockBuilder(Process::class)
10097
->setConstructorArgs([$key])
101-
->onlyMethods([])
98+
->onlyMethods(['writeToShmop'])
10299
->getMock();
103100

104-
serialize(new SerializableClosure(function () {
105-
}));
106-
107-
$shmop = shmop_open($key, 'c', 0660, 1);
108-
// shmop_close($shmop); // deprecated function
101+
$process
102+
->expects($this->once())
103+
->method('writeToShmop')
104+
->willReturn(10);
109105

110106
$this->expectException(Exception::class);
111107

112108
$process->send(function () {
113109
});
114-
115-
$this->assertNotFalse($shmop);
116-
if ($shmop) {
117-
shmop_delete($shmop);
118-
}
119110
}
120111

121112
/**
@@ -133,11 +124,9 @@ public function testSendDoesNotThrowOnSuccess(): void
133124
});
134125

135126
$shmop = shmop_open($key, 'a', 0, 0);
136-
if ($shmop) {
137-
$this->assertTrue(shmop_delete($shmop));
138-
// shmop_close($shmop); // deprecated function
139-
} else {
140-
$this->expectNotToPerformAssertions();
141-
}
127+
$this->assertNotFalse($shmop, 'Shared memory should exist after send');
128+
129+
$size = shmop_size($shmop);
130+
$this->assertGreaterThan(0, $size, 'Shared memory should contain data');
142131
}
143132
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
3+
namespace Terremoth\AsyncTest;
4+
5+
use PHPUnit\Framework\TestCase;
6+
7+
use function Terremoth\Async\stringFromMemoryBlock;
8+
9+
/**
10+
* @covers \Terremoth\Async\stringFromMemoryBlock
11+
* @covers \Terremoth\Async\error
12+
*/
13+
class ScriptFunctionsTest extends TestCase
14+
{
15+
public function testStringFromMemoryBlockTrimsNull(): void
16+
{
17+
$this->assertSame('Hello', stringFromMemoryBlock("Hello\0World"));
18+
}
19+
20+
public function testStringFromMemoryBlockWithoutNull(): void
21+
{
22+
$this->assertSame('JustAString', stringFromMemoryBlock("JustAString"));
23+
}
24+
25+
public function testStringFromMemoryBlockHandlesLeadingNullByte(): void
26+
{
27+
$this->assertSame('', stringFromMemoryBlock("\0LeadingNull"));
28+
}
29+
30+
public function testStringFromMemoryBlockHandlesMultipleNullBytes(): void
31+
{
32+
$this->assertSame('A', stringFromMemoryBlock("A\0B\0C"));
33+
}
34+
35+
public function testErrorWritesToStderrAndErrorLog(): void
36+
{
37+
$logFile = sys_get_temp_dir() . '/phpunit_error.log';
38+
ini_set('error_log', $logFile);
39+
40+
$stream = fopen('php://memory', 'w+');
41+
42+
// temporarily redefine error() for test
43+
$customError = function (string $msg) use ($stream): void {
44+
$msg = 'Error: ' . $msg;
45+
fwrite($stream, $msg);
46+
error_log($msg);
47+
};
48+
49+
ob_start(); // start output buffer
50+
$customError('Test Error');
51+
ob_get_clean(); // clean buffer properly
52+
53+
rewind($stream);
54+
$streamContents = stream_get_contents($stream);
55+
fclose($stream);
56+
57+
$this->assertStringContainsString('Error: Test Error', $streamContents);
58+
59+
$logContents = file_get_contents($logFile);
60+
$this->assertStringContainsString('Error: Test Error', $logContents);
61+
}
62+
}

0 commit comments

Comments
 (0)