Skip to content

Commit 9e375d2

Browse files
committed
Unit tests pass
1 parent 8b51025 commit 9e375d2

File tree

3 files changed

+146
-151
lines changed

3 files changed

+146
-151
lines changed

packages/php-wasm/compile/build-assets/phpwasm-emscripten-library.js

Lines changed: 22 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ const LibraryExample = {
173173
let dataCallback;
174174
const filename = "proc_id_" + procopenCallId;
175175
const device = FS.createDevice("/dev", filename, function () {
176-
}, function (byte) {
176+
}, function (byte) {
177177
try {
178178
dataBuffer.push(byte);
179179
if (dataCallback) {
@@ -213,42 +213,29 @@ const LibraryExample = {
213213
timeout: 100
214214
});
215215

216-
if (PHPWASM.callback_pipes && procopenCallId in PHPWASM.callback_pipes) {
217-
PHPWASM.callback_pipes[procopenCallId].onData(function (data) {
218-
// console.log('writing data to stdin', { data })
219-
if (!data) return;
220-
const dataStr = new TextDecoder("utf-8").decode(data);
221-
// console.log({ dataStr });
222-
cp.stdin.write(dataStr);
223-
});
224-
} else {
225-
const stdinStream = SYSCALLS.getStreamFromFD(procopenCallId);
226-
console.log({stdinStream})
227-
}
228-
229-
// -1 is an extremely naive way of computing parentend
230-
// @TODO pass this in from PHP
216+
// Subtracting 1 is an extremely naive way of computing parentend
217+
// @TODO pass stdoutParentEnd and stdErrParentEnd from PHP
231218
const stdoutStream = SYSCALLS.getStreamFromFD(stdoutFd);
232219
cp.on("exit", function (data) {
233220
PHPWASM.proc_fds[stdoutFd - 1].exited = true;
234221
PHPWASM.proc_fds[stdoutFd - 1].emit("data");
235222
PHPWASM.proc_fds[stderrFd - 1].exited = true;
236223
PHPWASM.proc_fds[stderrFd - 1].emit("data");
237224
});
238-
// @TODO Node.js specific:
225+
226+
// @TODO Make it isomorphic, not Node.js specific:
239227
const EventEmitter = require('events');
240228
PHPWASM.proc_fds[stdoutFd - 1] = new EventEmitter();
241229
PHPWASM.proc_fds[stderrFd - 1] = new EventEmitter();
242230

243231
cp.stdout.on("data", function (data) {
244-
// console.log("Writing data", data.toString());
245232
PHPWASM.proc_fds[stdoutFd - 1].hasData = true;
246233
PHPWASM.proc_fds[stdoutFd - 1].emit("data");
247234
stdoutStream.stream_ops.write(stdoutStream, data, 0, data.length, 0);
248235
});
249236

250-
// -1 is an extremely naive way of computing parentend
251-
// @TODO pass this in from PHP
237+
// Subtracting 1 is an extremely naive way of computing parentend
238+
// @TODO pass stdErrParentEnd from PHP
252239
const stderrStream = SYSCALLS.getStreamFromFD(stderrFd);
253240
cp.stderr.on("data", function(data) {
254241
console.log("Writing error", data.toString());
@@ -258,21 +245,23 @@ const LibraryExample = {
258245
});
259246

260247
// Handle stdin descriptor
261-
// If it's a "pipe", it is listed in `callback_pipes` by now.
262-
// Let's listen to anything it outputs and pass it to the child process.
263248
if (PHPWASM.callback_pipes && procopenCallId in PHPWASM.callback_pipes) {
264-
PHPWASM.callback_pipes[procopenCallId].onData(function(data) {
265-
if (!data) return;
266-
const dataStr = new TextDecoder("utf-8").decode(data);
267-
cp.stdin.write(dataStr);
268-
});
249+
// It is a "pipe". By now it is listed in `callback_pipes`.
250+
// Let's listen to anything it outputs and pass it to the child process.
251+
PHPWASM.callback_pipes[procopenCallId].onData(function(data) {
252+
if (!data) return;
253+
const dataStr = new TextDecoder("utf-8").decode(data);
254+
cp.stdin.write(dataStr);
255+
});
269256
} else {
270-
// Otherwise, it's a file descriptor.
271-
// Let's pass the already read contents to the child process.
272-
const stdinStream = SYSCALLS.getStreamFromFD(procopenCallId);
273-
const dataStr = new TextDecoder("utf-8").decode(stdinStream.node.contents);
274-
cp.stdin.write(dataStr);
275-
}
257+
const stdinStream = SYSCALLS.getStreamFromFD(procopenCallId);
258+
if(stdinStream?.node?.contents) {
259+
// It is a file descriptor.
260+
// Let's pass the already read contents to the child process.
261+
const dataStr = new TextDecoder("utf-8").decode(stdinStream.node.contents);
262+
cp.stdin.write(dataStr);
263+
}
264+
}
276265
},
277266

278267
/**

packages/php-wasm/node/public/php_7_4.js

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5530,6 +5530,7 @@ function _js_create_input_device(procopenCallId) {
55305530
throw e;
55315531
}
55325532
});
5533+
55335534
const devicePath = "/dev/" + filename;
55345535
PHPWASM.callback_pipes[procopenCallId] = {
55355536
devicePath: devicePath,
@@ -5588,21 +5589,23 @@ function _js_open_process(command, procopenCallId, stdoutFd, stderrFd) {
55885589
});
55895590

55905591
// Handle stdin descriptor
5591-
// If it's a "pipe", it is listed in `callback_pipes` by now.
5592-
// Let's listen to anything it outputs and pass it to the child process.
55935592
if (PHPWASM.callback_pipes && procopenCallId in PHPWASM.callback_pipes) {
5593+
// It is a "pipe". By now it is listed in `callback_pipes`.
5594+
// Let's listen to anything it outputs and pass it to the child process.
55945595
PHPWASM.callback_pipes[procopenCallId].onData(function(data) {
55955596
if (!data) return;
55965597
const dataStr = new TextDecoder("utf-8").decode(data);
55975598
cp.stdin.write(dataStr);
55985599
});
55995600
} else {
5600-
// Otherwise, it's a file descriptor.
5601-
// Let's pass the already read contents to the child process.
56025601
const stdinStream = SYSCALLS.getStreamFromFD(procopenCallId);
5603-
const dataStr = new TextDecoder("utf-8").decode(stdinStream.node.contents);
5604-
cp.stdin.write(dataStr);
5605-
}
5602+
if(stdinStream?.node?.contents) {
5603+
// It is a file descriptor.
5604+
// Let's pass the already read contents to the child process.
5605+
const dataStr = new TextDecoder("utf-8").decode(stdinStream.node.contents);
5606+
cp.stdin.write(dataStr);
5607+
}
5608+
}
56065609

56075610

56085611
}

packages/php-wasm/node/src/test/php.spec.ts

Lines changed: 114 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -16,125 +16,128 @@ describe.each(['7.4'])('PHP %s', (phpVersion) => {
1616
php = await NodePHP.load(phpVersion);
1717
});
1818

19-
it.skip('proc_open() test with files', async () => {
20-
php.setPhpIniEntry('disable_functions', '');
21-
const result = await php.run({
22-
code: `<?php
23-
file_put_contents('/tmp/process_in', '');
24-
$res = proc_open(
25-
"echo WordPress",
26-
array(
27-
array("file","/tmp/process_in", "r"),
28-
array("file","/tmp/process_out", "w"),
29-
array("file","/tmp/process_err", "w"),
30-
),
31-
$pipes
32-
);
33-
34-
@file_get_contents("https://wordpress.org");
35-
sleep(1);
36-
37-
$stdout = file_get_contents("/tmp/process_out");
38-
$stderr = file_get_contents("/tmp/process_err");
39-
40-
echo 'stdout: ' . $stdout . "";
41-
echo 'stderr: ' . $stderr . PHP_EOL;
42-
`,
19+
describe('proc_open()', () => {
20+
it('echo – stdin=file (empty), stdout=file, stderr=file', async () => {
21+
php.setPhpIniEntry('disable_functions', '');
22+
const result = await php.run({
23+
code: `<?php
24+
file_put_contents('/tmp/process_in', '');
25+
$res = proc_open(
26+
"echo WordPress",
27+
array(
28+
array("file","/tmp/process_in", "r"),
29+
array("file","/tmp/process_out", "w"),
30+
array("file","/tmp/process_err", "w"),
31+
),
32+
$pipes
33+
);
34+
35+
// Yields back to JS event loop to capture and process the
36+
// child_process output. This is fine. Regular PHP scripts
37+
// typically wait for the child process to finish.
38+
sleep(1);
39+
40+
$stdout = file_get_contents("/tmp/process_out");
41+
$stderr = file_get_contents("/tmp/process_err");
42+
43+
echo 'stdout: ' . $stdout . "";
44+
echo 'stderr: ' . $stderr . PHP_EOL;
45+
`,
46+
});
47+
expect(result.text).toEqual('stdout: WordPress\nstderr: \n');
4348
});
44-
console.log(php.readFileAsText('/tmp/process_out'));
45-
expect(result.text).toEqual('stdout: WordPress\nstderr: \n');
46-
});
4749

48-
it.skip('proc_open() test with pipes', async () => {
49-
php.setPhpIniEntry('disable_functions', '');
50-
const result = await php.run({
51-
code: `<?php
52-
file_put_contents('/tmp/process_in', '');
53-
$res = proc_open(
54-
"echo WordPress",
55-
array(
56-
array("file","/tmp/process_in", "r"),
57-
array("pipe","w"),
58-
array("pipe","w"),
59-
),
60-
$pipes
61-
);
62-
63-
$stdout = stream_get_contents($pipes[1]);
64-
$stderr = stream_get_contents($pipes[2]);
65-
proc_close($res);
66-
67-
echo 'stdout: ' . $stdout . "";
68-
echo 'stderr: ' . $stderr . PHP_EOL;
69-
`,
50+
it('echo – stdin=file (empty), stdout=pipe, stderr=pipe', async () => {
51+
php.setPhpIniEntry('disable_functions', '');
52+
const result = await php.run({
53+
code: `<?php
54+
file_put_contents('/tmp/process_in', '');
55+
$res = proc_open(
56+
"echo WordPress",
57+
array(
58+
array("file","/tmp/process_in", "r"),
59+
array("pipe","w"),
60+
array("pipe","w"),
61+
),
62+
$pipes
63+
);
64+
65+
// stream_get_contents yields back to JS event loop internally.
66+
$stdout = stream_get_contents($pipes[1]);
67+
$stderr = stream_get_contents($pipes[2]);
68+
proc_close($res);
69+
70+
echo 'stdout: ' . $stdout . "";
71+
echo 'stderr: ' . $stderr . PHP_EOL;
72+
`,
73+
});
74+
expect(result.text).toEqual('stdout: WordPress\nstderr: \n');
7075
});
71-
expect(result.text).toEqual('stdout: WordPress\nstderr: \n');
72-
});
73-
74-
// It works! :o NICE!
75-
it('proc_open() test with stdin (pipe)', async () => {
76-
php.setPhpIniEntry('disable_functions', '');
77-
php.setPhpIniEntry('allow_url_fopen', '1');
78-
const result = await php.run({
79-
code: `<?php
80-
$res = proc_open(
81-
"cat",
82-
array(
83-
array("pipe","r"),
84-
array("file","/tmp/process_out", "w"),
85-
array("file","/tmp/process_err", "w"),
86-
),
87-
$pipes
88-
);
89-
fwrite($pipes[0], 'WordPress\n');
90-
91-
sleep(1);
9276

93-
// And this is synchronous, too. We can't process the child_process
94-
// output in between these calls. We need to somehow yield back to JS
95-
// after writing to stdin.
96-
$stdout = file_get_contents("/tmp/process_out");
97-
$stderr = file_get_contents("/tmp/process_err");
98-
proc_close($res);
99-
100-
echo 'stdout: ' . $stdout . "";
101-
echo 'stderr: ' . $stderr . PHP_EOL;
102-
`,
77+
it('cat – stdin=pipe, stdout=file, stderr=file', async () => {
78+
php.setPhpIniEntry('disable_functions', '');
79+
php.setPhpIniEntry('allow_url_fopen', '1');
80+
const result = await php.run({
81+
code: `<?php
82+
$res = proc_open(
83+
"cat",
84+
array(
85+
array("pipe","r"),
86+
array("file","/tmp/process_out", "w"),
87+
array("file","/tmp/process_err", "w"),
88+
),
89+
$pipes
90+
);
91+
fwrite($pipes[0], 'WordPress\n');
92+
93+
// Yields back to JS event loop to capture and process the
94+
// child_process output. This is fine. Regular PHP scripts
95+
// typically wait for the child process to finish.
96+
sleep(1);
97+
98+
$stdout = file_get_contents("/tmp/process_out");
99+
$stderr = file_get_contents("/tmp/process_err");
100+
proc_close($res);
101+
102+
echo 'stdout: ' . $stdout . "";
103+
echo 'stderr: ' . $stderr . PHP_EOL;
104+
`,
105+
});
106+
expect(result.text).toEqual('stdout: WordPress\nstderr: \n');
103107
});
104-
expect(result.text).toEqual('stdout: WordPress\nstderr: \n');
105-
});
106108

107-
it('proc_open() test with stdin (file)', async () => {
108-
php.setPhpIniEntry('disable_functions', '');
109-
php.setPhpIniEntry('allow_url_fopen', '1');
110-
const result = await php.run({
111-
code: `<?php
112-
file_put_contents('/tmp/process_in', 'WordPress\n');
113-
$res = proc_open(
114-
"cat",
115-
array(
116-
array("file","/tmp/process_in", "r"),
117-
array("file","/tmp/process_out", "w"),
118-
array("file","/tmp/process_err", "w"),
119-
),
120-
$pipes
121-
);
122-
123-
sleep(1);
124-
125-
// And this is synchronous, too. We can't process the child_process
126-
// output in between these calls. We need to somehow yield back to JS
127-
// after writing to stdin.
128-
$stdout = file_get_contents("/tmp/process_out");
129-
$stderr = file_get_contents("/tmp/process_err");
130-
proc_close($res);
109+
it('cat – stdin=file, stdout=file, stderr=file', async () => {
110+
php.setPhpIniEntry('disable_functions', '');
111+
php.setPhpIniEntry('allow_url_fopen', '1');
112+
const result = await php.run({
113+
code: `<?php
114+
file_put_contents('/tmp/process_in', 'WordPress\n');
115+
$res = proc_open(
116+
"cat",
117+
array(
118+
array("file","/tmp/process_in", "r"),
119+
array("file","/tmp/process_out", "w"),
120+
array("file","/tmp/process_err", "w"),
121+
),
122+
$pipes
123+
);
124+
125+
// Yields back to JS event loop to capture and process the
126+
// child_process output. This is fine. Regular PHP scripts
127+
// typically wait for the child process to finish.
128+
sleep(1);
129+
130+
$stdout = file_get_contents("/tmp/process_out");
131+
$stderr = file_get_contents("/tmp/process_err");
132+
proc_close($res);
133+
134+
echo 'stdout: ' . $stdout . "";
135+
echo 'stderr: ' . $stderr . PHP_EOL;
136+
`,
137+
});
131138

132-
echo 'stdout: ' . $stdout . "";
133-
echo 'stderr: ' . $stderr . PHP_EOL;
134-
`,
139+
expect(result.text).toEqual('stdout: WordPress\nstderr: \n');
135140
});
136-
137-
expect(result.text).toEqual('stdout: WordPress\nstderr: \n');
138141
});
139142

140143
describe('Filesystem', () => {

0 commit comments

Comments
 (0)