Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
299e4f6
ExecSolib strategy: make ddtrace.so directly executable
cataphract Mar 19, 2026
b8586b7
Load trampoline directly from file
cataphract Mar 20, 2026
4342179
DD_TRAMPOLINE_BIN was erroneously on the list of exported syms
cataphract Mar 20, 2026
9720a80
Remove asan from solib loader, do not export entrypoint
cataphract Mar 20, 2026
e3c4da7
upd libdatadog
cataphract Mar 20, 2026
82345e8
solib_bootstrap: fix x86-64 ld.so jump clobbering entry address
cataphract Mar 20, 2026
12c9823
Minor simplification
cataphract Mar 20, 2026
ee46093
Messing with DD_TRAMPOLINE_BIN for the nth time
cataphract Mar 20, 2026
ddf897e
Try to fix split config
cataphract Mar 20, 2026
fb2dff9
upd libdatadog
cataphract Mar 21, 2026
5d0650b
Merge remote-tracking branch 'origin/master' into glopes/exec-solib
cataphract Mar 21, 2026
57936b0
moar build fixes
cataphract Mar 21, 2026
b6a70af
chmod +x ddtrace.so
cataphract Mar 21, 2026
9293858
wip
cataphract Mar 22, 2026
658c99a
SSI execsolib impl
cataphract Mar 24, 2026
9af5f9b
wip
cataphract Mar 25, 2026
86db928
reaping telemetry fwder
cataphract Mar 25, 2026
3442a78
Merge remote-tracking branch 'origin/master' into glopes/exec-solib
cataphract Mar 27, 2026
c9a2b8e
fix(ssi): Handle Podman cgroupns=host cgroup path for container origi…
cataphract Mar 30, 2026
66e1102
wip
cataphract Apr 2, 2026
60ac0f3
update libdatadog
cataphract Apr 8, 2026
e3e9365
Merge remote-tracking branch 'origin/master' into glopes/exec-solib
cataphract Apr 8, 2026
da0f6aa
wip
cataphract Apr 9, 2026
59abc04
fix(ExecSolib): compile trampoline as PIE; abort on ET_EXEC
cataphract Apr 9, 2026
574e9ce
Empty commit
cataphract Apr 9, 2026
64cf62f
fix(sidecar): fall back to FdExec when ddtrace.so lacks execute permi…
cataphract Apr 9, 2026
6f89536
cataphract Apr 10, 2026
0c72d7e
fix(ExecSolib): avoid dlsym in ssi_entry.c; add libdd-shared-runtime …
cataphract Apr 10, 2026
76e7b47
fix(ExecSolib): stack alignment, init_array, direct symbol refs in ss…
cataphract Apr 10, 2026
4b0ec55
fix(ExecSolib): remove weak attrs and dlsym comments from ssi_entry.c
cataphract Apr 10, 2026
e1d3077
fix(ssi_entry): call init_array entries with correct (argc, argv, env…
cataphract Apr 10, 2026
21c0c9c
fix(ssi_entry): replace elf.h dependency with self-contained ELF types
cataphract Apr 10, 2026
eece5c4
fix(pecl): include libdatadog buildscript/ files in RUST_FILES
cataphract Apr 10, 2026
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
288 changes: 288 additions & 0 deletions .claude/ci/appsec-gradle-integration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,288 @@
# AppSec Gradle Integration Tests

## CI Jobs

**Source:** `.gitlab/generate-appsec.php` — generates the appsec-trigger
child pipeline; all job `script:` sections are defined inline in this
file.

| CI Job | Image | What it does |
|--------|-------|-------------|
| `appsec integration tests: [{target}]` | `docker:24.0.4-gbi-focal` | Gradle integration tests with C++ helper (release/zts variants) |
| `appsec integration tests (helper-rust): [{target}]` | same | Same tests with Rust helper (`-PuseHelperRust`); includes `debug` variant |
| `appsec integration tests (ssi): [{target}]` | same | SSI mode (PHP 8.3 only) |
| `helper-rust build and test` | same | `cargo fmt --check` + build + unit tests |
| `helper-rust code coverage` | same | Unit test coverage via `cargo-llvm-cov` |
| `helper-rust integration coverage` | same | Integration coverage collection (not needed locally) |

Runner: `docker-in-docker:amd64`
Matrix: PHP 7.0+ × release/debug/zts/musl/ssi (varies by job group)

**Important:** `testX.Y-debug` are not gradle targets that are run on CI. They
may, however, be useful for debugging.

CI passes `TERM=dumb` and `--scan -Pbuildscan` to Gradle. `TERM=dumb`
suppresses progress animations in CI logs; both flags are optional
locally.

## Prerequisites

- JDK 17+. If not available via your system package manager, use SDKMAN
(`sdk install java 17`) or download from
https://download.oracle.com/java/17/archive/.
- Docker daemon running

## Working Directory

All `./gradlew` commands run from:

```
appsec/tests/integration/
```

## Running Tests

### Full suite for one PHP target

```bash
./gradlew test8.3-debug --info
```

### Single test (fastest feedback loop)

```bash
./gradlew test8.3-debug --info \
--tests "com.datadog.appsec.php.integration.Apache2FpmTests.Pool environment"
```

The `--tests` filter accepts:
- Full method: `"com.datadog.appsec.php.integration.Apache2FpmTests.Pool environment"`
- Class only: `"*Apache2FpmTests*"` or `"com.datadog.appsec.php.integration.Apache2FpmTests"`
- Wildcard: `"*FpmTests*"`

### With helper-rust (instead of C++ helper)

```bash
./gradlew test8.3-debug -PuseHelperRust --info \
--tests "com.datadog.appsec.php.integration.Apache2FpmTests.Pool environment"
```

This builds the Rust helper via the `buildHelperRust` task (musl build, works on both glibc and musl targets) and stores the binary in the `php-helper-rust` Docker volume.

### C++ helper (default)

Omit `-PuseHelperRust` and `-PhelperBinary`. The C++ helper is built via the `buildAppsec-*` task.

## Image Tags

By default, Gradle resolves Docker images via pinned SHA256 digests in `gradle/tag_mappings.gradle`. To use floating tags (locally-built images or latest from Docker Hub):

```bash
./gradlew test8.3-debug -PfloatingImageTags --info
```

## Available Gradle Tasks

### Test tasks

Pattern: `test{version}-{variant}`

| Variant | Notes |
|---|---|
| `release` | Standard build |
| `debug` | Debug build (assertions enabled) |
| `release-zts` | Thread-safe build |
| `release-musl` | Alpine/musl (only `8.5-release-musl`) |
| `release-ssi` / `debug-ssi` | SSI mode (only PHP 8.3) |

Full list: `./gradlew tasks --group=Verification`

### Helper-rust tasks

| Task | Description |
|---|---|
| `buildHelperRust` | Build helper-rust with musl (universal binary). Output in `php-helper-rust` volume. |
| `testHelperRust` | `cargo fmt --check` + `cargo build --release` + `cargo test --release` (runs inside `php-deps` image) |
| `coverageHelperRust` | Unit test coverage via `cargo-llvm-cov`. Output: `php-helper-rust-coverage` volume. |
| `buildHelperRustWithCoverage` | Build with `-C instrument-coverage` for integration coverage collection. |
| `generateHelperRustIntegrationCoverage` | Merge `.profraw` files into lcov after integration run. |

### Build tasks

| Task | Description |
|---|---|
| `buildTracer-{v}-{var}` | Build ddtrace.so for given PHP version/variant |
| `buildAppsec-{v}-{var}` | Build ddappsec.so (C++ extension + helper) |
| `buildHelperRust` | Build Rust helper (musl, universal) |
| `buildLibddwaf` | Build libddwaf shared library |

### Other tasks

| Task | Description |
|---|---|
| `loadCaches` | Restore Docker volume caches from tarball |
| `saveCaches` | Save Docker volume caches to tarball |
| `clean` | Delete build directory and clean Docker volumes |
| `check` | Run all test tasks |

All tasks: `./gradlew tasks --all`

## Interactive Container (runMain)

Start a test container without running tests (for manual debugging):

```bash
./gradlew runMain8.3-release -PtestClass=com.datadog.appsec.php.integration.Apache2FpmTests
```

The `-PtestClass` property is required (the task is not created without it). Add `-PuseHelperRust` or `-PhelperBinary=...` as needed.

SSI variant:

```bash
./gradlew runMain8.3-release-ssi -PtestClass=com.datadog.appsec.php.integration.Apache2FpmTests
```

## Logs

After a test run, logs are in:

```
build/test-logs/<TestClassName>-<version>-<variant>/
```

For example:

```
build/test-logs/com.datadog.appsec.php.integration.Apache2FpmTests-8.3-debug/
├── access.log
├── appsec.log # PHP extension appsec log
├── error.log # Apache error log
├── helper.log # Helper process log (C++ or Rust)
├── php_error.log
├── php_fpm_error.log
└── sidecar.log
```

To distinguish which helper ran, check `helper.log`:
- Rust: starts with `[INFO] AppSec helper starting`
- C++: starts with `[info]` lines like `Started listening on abstract socket`

## Musl/Alpine Target

The `test8.5-release-musl` target uses an Alpine-based nginx+fpm image. Tests tagged with `@Tag("musl")` are included; untagged tests are excluded.

```bash
./gradlew test8.5-release-musl -PuseHelperRust --info
```

The `buildHelperRust` task already produces a musl-linked binary (built on Alpine with `cargo +nightly`, using LLVM libunwind). The `patchelf --remove-needed libc.musl-*` step makes it load on both musl and glibc systems.

## CI Job Mapping

| CI Job | Gradle Command |
|---|---|
| `appsec integration tests: [test8.3-release]` | `./gradlew test8.3-release` |
| `appsec integration tests (helper-rust): [test8.3-debug]` | `./gradlew test8.3-debug -PuseHelperRust` |
| `appsec integration tests (ssi): [test8.3-release-ssi]` | `./gradlew test8.3-release-ssi` |
| `helper-rust build and test` | `./gradlew testHelperRust` |
| `helper-rust code coverage` | `./gradlew coverageHelperRust` |
| `helper-rust integration coverage` | `./gradlew buildHelperRustWithCoverage` then integration test with `-PuseHelperRustCoverage` |

CI also passes `--scan -Pbuildscan` for Gradle build scans, which is optional locally.

## Docker Volumes

Gradle uses named Docker volumes for build artifacts and caches. Key volumes:

| Volume | Contents |
|---|---|
| `php-helper-rust` | `libddappsec-helper.so` (Rust helper binary) |
| `php-tracer-{v}-{var}` | Built `ddtrace.so` |
| `php-appsec-{v}-{var}` | Built `ddappsec.so` + C++ helper |
| `php-tracer-cargo-cache` | Cargo registry cache |
| `php-tracer-cargo-cache-git` | Cargo git cache |
| `php-appsec-boost-cache` | Boost build cache |
| `php-helper-rust-coverage` | Coverage-instrumented binary + profraw files |

To force a rebuild, remove the relevant volume:

```bash
docker volume rm php-helper-rust
```

To clean everything:

```bash
./gradlew clean
```

## Debugging

Attach a Java debugger to the test runner:

```bash
./gradlew test8.3-debug --tests "*Apache2FpmTests*" --debug-jvm
```

Enable PHP Xdebug in the test container:

```bash
./gradlew test8.3-debug --tests "*Apache2FpmTests*" -PXDEBUG=1
```

## Expected skips

A significant number of tests are skipped on any given target — this is
normal. Skip conditions are `@EnabledIf` guards in the test classes:

| Test class | Skips on | Reason |
|---|---|---|
| `FrankenphpClassicTests`, `FrankenphpWorkerTests` | anything except `8.4-zts` | Requires ZTS + PHP 8.4 |
| `Laravel8xTests` | anything except `7.4` (NTS) | Requires PHP 7.4 non-ZTS |
| `Symfony62Tests` | anything except `8.1` (NTS) | Requires PHP 8.1 non-ZTS |
| `RaspSqliTests` | no MySQL service | Requires a running MySQL |
| `SsiStableConfigTests` | non-SSI variants | Requires `-DSSI=true` |

On `test8.3-debug` expect ~67 skips out of ~300 tests; all are expected.

## Test report

After a run, the HTML report is at:

```
appsec/tests/integration/build/reports/tests/test8.3-debug/index.html
```

(Replace `test8.3-debug` with your target.) Open in a browser for a
structured pass/fail/skip breakdown.

# Debugging

Run gradle with `--debug-jvm`. This will stop for the debugger, indicating so in the output.
When you see the message, start jdb in a tmux session.

If you need to inspect sidecar/helper or PHP issues:

* Put a breakpoint in jdb that stops the test in the appropriate place (usually
just before a request is executed).
* Inside the test container (determine first its id), attach gdb to sidecar
(`pref -f dd-ipc-helper`) or to PHP (usually an apache or an FPM worker -- if
you're investigating code run during processes it will not be the master
process). sidecar requires as a first command `file /proc/<pid>/exe`).
* See [gdb.md](../gdb.md) for more information on how to run gdb. Always read
this file before attempting to use gdb.

## Gotchas

- The `test` task itself is disabled (`tasks['test'].enabled = false`). Use versioned tasks like `test8.3-debug`.
- Docker images are pulled from `docker.io/datadog/dd-appsec-php-ci`. Without `-PfloatingImageTags`, images are resolved by SHA256 digest from `gradle/tag_mappings.gradle`. If a digest is not locally available, Docker will pull it.
- The `buildHelperRust` task uses the `nginx-fpm-php-8.5-release-musl` image (Alpine with Rust nightly). This image must be available locally or pullable.
- On first run, Gradle downloads its wrapper, dependencies, and Docker images. Expect 5-10 minutes. Subsequent runs with warm caches take ~20-50 seconds for a single test.
- **c-ares DNS failure in Alpine containers.** Alpine's `curl` and `git` use
c-ares for DNS, which fails to resolve hosts when the DNS server includes
EDNS COOKIE options in responses (common with home routers). `wget` and
`getent` are unaffected (they use musl's native resolver). This breaks the
`buildAppsec-*-musl` task which needs to `git clone` cmake dependencies.
Fix: pass `--dns 8.8.8.8` to the Docker command.
- `--info` is recommended for seeing test output in the console. Without it, output goes only to the HTML report in `build/reports/tests/`.
Loading