Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 
 
 

README.md

FrankenPHP worker-mode tests

run-worker-tests.sh is the automated check; the rest of this document is the manual procedure for working on the SAPI hooks by hand with an IDE.

Automated (what CI runs)

tests/frankenphp/run-worker-tests.sh

Builds the image, runs a worker with dbgp_listener.py standing in for an IDE, and asserts that breakpoints still fire after the worker has served requests with no debugger attached — without that, a single un-debugged request stops every later request from breaking. It also checks that the reported stack includes the calling function, and repeats the trigger on a fresh worker as a control.

Overrides: IMAGE, HTTP_PORT (default 8081), DBGP_PORT (default 9004, because an IDE usually holds 9003), POISON_REQUESTS, TRIGGER_REQUESTS, SKIP_BUILD=1 to reuse an image you already built.

The script exits non-zero with the listener transcript on failure. It is run by .github/workflows/frankenphp.yml on every push and pull request, and it was verified to fail (0/5 breakpoints) against the code from before the worker -mode fix, so a regression is caught rather than silently passing.

dbgp_listener.py can also be pointed at a different breakpoint for ad-hoc work — DBGP_BREAKPOINT="-t call -m workload" exercises the observer's begin handler instead of the statement handler that serves line breakpoints.

Manual FrankenPHP worker-mode test

Smoke-tests the SAPI activate/deactivate hooks added in src/debugger/frankenphp.c.

Build

From the repo root (the build context must include the whole tree):

docker build -f tests/frankenphp/Dockerfile -t php-debugger-frankenphp .

Run

Start your IDE listening on port 9003 for incoming DBGp connections.

  • PhpStorm: Run → Start Listening for PHP Debug Connections (the little phone icon in the toolbar should turn green).
  • VS Code: add a launch.json with "type": "php", "request": "launch", "port": 9003, "pathMappings": { "/app": "${workspaceFolder}/tests/frankenphp/app" }, then F5.

Then start the container. Bind-mount the app dir so PhpStorm can match incoming breakpoint paths to your local files:

# macOS / Windows: host.docker.internal works out of the box.
docker run --rm -p 8080:80 \
  -v "$PWD/tests/frankenphp/app:/app" \
  php-debugger-frankenphp

# Linux: add the host-gateway alias.
docker run --rm -p 8080:80 \
  --add-host=host.docker.internal:host-gateway \
  -v "$PWD/tests/frankenphp/app:/app" \
  php-debugger-frankenphp

# Override host/port from the CLI:
docker run --rm -p 8080:80 \
  -e XDEBUG_CLIENT_HOST=192.168.1.42 \
  -e XDEBUG_CLIENT_PORT=9003 \
  -v "$PWD/tests/frankenphp/app:/app" \
  php-debugger-frankenphp

The XDEBUG_CLIENT_HOST / XDEBUG_CLIENT_PORT env vars are applied at container startup by entrypoint.sh (PHP INI has no native env-var fallback syntax).

One-time PhpStorm setup (path mapping)

Without a path mapping, PhpStorm receives breakpoint_set for /app/index.php but doesn't know it corresponds to tests/frankenphp/app/index.php in your project. The IDE will warn:

It may be caused by path mappings misconfiguration or not synchronized local and remote projects.

Configure it once:

  1. Settings → PHP → Servers → +
  2. Name: localhost (any name; it just needs to exist)
  3. Host: localhost · Port: 8080 · Debugger: Xdebug
  4. Use path mappings
  5. In the file tree, find tests/frankenphp/app and set the Absolute path on the server to /app.
  6. Apply / OK.

Quick connection check that bypasses path mapping

To prove the DBGp connection itself is working before fighting path mappings, toggle Run → Break at first line in PHP scripts and hit:

curl -b 'XDEBUG_SESSION=PHPSTORM' http://localhost:8080/

PhpStorm should pause on the opening <?php regardless of any path config. If even that doesn't fire, the connection isn't being made — see the Troubleshooting section.

What to verify manually

The script already covers the basics: a request without a trigger must not pause, a request with one must, and repeated debug requests against the same worker must all pause. What is left needs either a real IDE or a clean shutdown, so it stays a hand check.

1. Breakpoint added between requests (the xdebug_dbgp_poll_pending test)

Open tests/frankenphp/app/index.php in your IDE, then:

  1. Set a breakpoint on line 4 ($pid = getmypid();).

  2. curl -b 'XDEBUG_SESSION=PHPSTORM' http://localhost:8080/ → pauses on line 4. Resume (F9).

  3. Move the breakpoint to line 5 ($now = ...) while the worker is idle. The IDE sends breakpoint_remove + breakpoint_set to the socket of the (now idle) worker thread — those commands sit queued.

  4. curl -b 'XDEBUG_SESSION=PHPSTORM' http://localhost:8080/.

    The next sapi_module.activate calls xdebug_dbgp_poll_pending(), which drains the queued commands and registers the new breakpoint. PhpStorm pauses on line 5.

    Without the poll, the IDE's queued breakpoint_set is silently ignored and the new breakpoint is never honored.

2. Logs

docker exec $(docker ps -qf ancestor=php-debugger-frankenphp) \
  tail -f /tmp/xdebug.log

Per debug request expect to see:

[Step Debug] INFO: Connecting to configured address/port: host.docker.internal:9003.
[Step Debug] INFO: Connected to debugging client: ...
... (DBGp protocol messages) ...
Log closed at ...

A clean Log closed between requests confirms the per-request deactivate hook tore the session down before the next activate set up a fresh one.

Cleanup test

docker stop <container> triggers PHP MSHUTDOWN, which calls xdebug_frankenphp_mshutdown() to restore the original sapi_module.activate / deactivate pointers. Clean exit (no segfault in the container logs) means the restore worked.

Troubleshooting

Symptom Cause Fix
curl returns nothing / 308 redirect to https:// FrankenPHP using its default Caddyfile, not ours Confirm tests/frankenphp/Caddyfile was copied to /etc/frankenphp/Caddyfile (not /etc/caddy/Caddyfile)
Failed loading Zend extension 'php_debugger.so' .so not in versioned ext dir Confirm COPY --from=build /usr/local/lib/php/extensions/ ... (whole dir, not flattened)
make: *** No rule to make target '/Users/.../xdebug.c' during build Host build artifacts (Makefile, *.dep) leaked into context Repo-root .dockerignore should exclude **/*.dep, **/*.lo, Makefile, etc. — check it exists
IDE warns about path mappings Local file path ≠ container file path Configure Settings → PHP → Servers as described above; bind-mount -v "$PWD/tests/frankenphp/app:/app"
Connecting to ... :0 in xdebug log Tried to use ${VAR:default} in PHP INI, which doesn't expand Already fixed via entrypoint.sh; pass real XDEBUG_CLIENT_HOST / _PORT env vars at docker run
Creating socket ... error in xdebug log IDE not reachable from container Linux: add --add-host=host.docker.internal:host-gateway. Or set XDEBUG_CLIENT_HOST to the host IP visible from the container. Verify the IDE is actually listening on the chosen port.