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/sanitizers.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ jobs:
- name: Set CGO flags
run: |
{
echo "CGO_CFLAGS=$CFLAGS -I${PWD}/watcher/target/include $(php-config --includes)"
echo "CGO_CFLAGS=$CFLAGS -I${PWD}/watcher/target/include -DFRANKENPHP_TEST $(php-config --includes)"
echo "CGO_LDFLAGS=$LDFLAGS $(php-config --ldflags) $(php-config --libs)"
} >> "$GITHUB_ENV"
- name: Run tests
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ jobs:
# TODO: remove this workaround when fixed upstream
run: sudo apt-get install --reinstall -y libbrotli-dev
- name: Set CGO flags
run: echo "CGO_CFLAGS=-I${PWD}/watcher/target/include $(php-config --includes)" >> "${GITHUB_ENV}"
run: echo "CGO_CFLAGS=-I${PWD}/watcher/target/include -DFRANKENPHP_TEST $(php-config --includes)" >> "${GITHUB_ENV}"
- name: Build
run: go build
- name: Build testcli binary
Expand Down Expand Up @@ -138,7 +138,7 @@ jobs:
echo "GEN_STUB_SCRIPT=${PWD}/php-${PHP_VERSION}/build/gen_stub.php" >> "${GITHUB_ENV}"
- name: Set CGO flags
run: |
echo "CGO_CFLAGS=$(php-config --includes)" >> "${GITHUB_ENV}"
echo "CGO_CFLAGS=-DFRANKENPHP_TEST $(php-config --includes)" >> "${GITHUB_ENV}"
echo "CGO_LDFLAGS=$(php-config --ldflags) $(php-config --libs)" >> "${GITHUB_ENV}"
- name: Install gotestsum
run: go install gotest.tools/gotestsum@latest
Expand Down Expand Up @@ -175,7 +175,7 @@ jobs:
- name: Set CGO flags
run: |
{
echo "CGO_CFLAGS=-I/opt/homebrew/include/ $(php-config --includes)"
echo "CGO_CFLAGS=-I/opt/homebrew/include/ -DFRANKENPHP_TEST $(php-config --includes)"
echo "CGO_LDFLAGS=-L/opt/homebrew/lib/ $(php-config --ldflags) $(php-config --libs)"
} >> "${GITHUB_ENV}"
- name: Build
Expand Down
49 changes: 49 additions & 0 deletions frankenphp.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@

#include "_cgo_export.h"
#include "frankenphp_arginfo.h"
#ifdef FRANKENPHP_TEST
/* The persistent_zval helpers are only compiled in when a consumer needs
* them. The step that lands the first real caller (background workers)
* will drop this guard. */
#include "zval.h"
#endif

#if defined(PHP_WIN32) && defined(ZTS)
ZEND_TSRMLS_CACHE_DEFINE()
Expand Down Expand Up @@ -712,12 +718,55 @@ PHP_FUNCTION(frankenphp_log) {
}
}

#ifdef FRANKENPHP_TEST
/* Test-only entry point that exercises zval.h end-to-end:
* validate -> persist (request -> persistent memory) ->
* to_request (persistent -> fresh request memory) -> free persistent copy.
* Compiled only when FRANKENPHP_TEST is defined; never registered
* in production builds. */
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(
arginfo_frankenphp_test_persist_roundtrip, 0, 1, IS_MIXED, 0)
ZEND_ARG_TYPE_INFO(0, value, IS_MIXED, 0)
ZEND_END_ARG_INFO()

PHP_FUNCTION(frankenphp_test_persist_roundtrip) {
zval *input;
ZEND_PARSE_PARAMETERS_START(1, 1)
Z_PARAM_ZVAL(input)
ZEND_PARSE_PARAMETERS_END();

if (!persistent_zval_validate(input)) {
zend_throw_exception(spl_ce_LogicException,
"persistent_zval: value type not supported "
"(only scalars, arrays, and enums are allowed)",
0);
RETURN_THROWS();
}

zval persistent;
persistent_zval_persist(&persistent, input);
persistent_zval_to_request(return_value, &persistent);
persistent_zval_free(&persistent);
}

static const zend_function_entry frankenphp_test_hook_functions[] = {
PHP_FE(frankenphp_test_persist_roundtrip,
arginfo_frankenphp_test_persist_roundtrip) PHP_FE_END};
#endif

PHP_MINIT_FUNCTION(frankenphp) {
register_frankenphp_symbols(module_number);
#ifndef PHP_WIN32
pthread_atfork(NULL, NULL, frankenphp_fork_child);
#endif

#ifdef FRANKENPHP_TEST
if (zend_register_functions(NULL, frankenphp_test_hook_functions, NULL,
MODULE_PERSISTENT) == FAILURE) {
return FAILURE;
}
#endif

zend_function *func;

// Override putenv
Expand Down
88 changes: 88 additions & 0 deletions testdata/persist-roundtrip.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<?php

// Exercises frankenphp_test_persist_roundtrip (only registered when
// FRANKENPHP_TEST_HOOKS is defined at build time). The script runs as a
// plain non-worker request; the Go harness asserts the combined output.

enum Status: string {
case Active = 'active';
case Paused = 'paused';
}

function same(mixed $actual, mixed $expected, string $label): void {
if ($actual !== $expected) {
echo "FAIL $label: expected ";
var_export($expected);
echo ' got ';
var_export($actual);
echo "\n";
return;
}
echo "OK $label\n";
}

$rt = 'frankenphp_test_persist_roundtrip';
if (!function_exists($rt)) {
echo "SKIP frankenphp_test_persist_roundtrip not registered\n";
return;
}

// Scalars.
same($rt(null), null, 'null');
same($rt(false), false, 'false');
same($rt(true), true, 'true');
same($rt(0), 0, 'int zero');
same($rt(42), 42, 'int');
same($rt(-1), -1, 'int negative');
same($rt(1.5), 1.5, 'float');
same($rt(''), '', 'empty string');
same($rt('hello'), 'hello', 'short string');

// Long (non-interned) string: forces the allocation path.
$long = str_repeat('x', 1024);
same($rt($long), $long, 'long string');

// Nested arrays, mixed keys.
$arr = [
'name' => 'alice',
'age' => 30,
'tags' => ['admin', 'editor'],
'meta' => ['created' => 1234567890, 'flags' => [true, false, null]],
0 => 'first',
1 => 'second',
];
same($rt($arr), $arr, 'nested array');

// Enum roundtrip: identity (===) must be preserved because the enum is
// re-resolved to the same singleton case on the read side.
same($rt(Status::Active), Status::Active, 'enum active');
same($rt(Status::Paused), Status::Paused, 'enum paused');

// Array containing an enum.
same(
$rt(['status' => Status::Active, 'count' => 7]),
['status' => Status::Active, 'count' => 7],
'array with enum',
);

// Invalid inputs throw LogicException.
try {
$rt(new stdClass());
echo "FAIL stdClass should throw\n";
} catch (\LogicException) {
echo "OK stdClass rejected\n";
}

try {
$rt(fopen('php://memory', 'r'));
echo "FAIL resource should throw\n";
} catch (\LogicException) {
echo "OK resource rejected\n";
}

try {
$rt(['ok' => 1, 'bad' => new stdClass()]);
echo "FAIL nested stdClass should throw\n";
} catch (\LogicException) {
echo "OK nested stdClass rejected\n";
}
Loading
Loading