|
| 1 | +# AppSec Gradle Integration Tests |
| 2 | + |
| 3 | +## CI Jobs |
| 4 | + |
| 5 | +**Source:** `.gitlab/generate-appsec.php` — generates the appsec-trigger |
| 6 | +child pipeline; all job `script:` sections are defined inline in this |
| 7 | +file. |
| 8 | + |
| 9 | +| CI Job | Image | What it does | |
| 10 | +|--------|-------|-------------| |
| 11 | +| `appsec integration tests: [{target}]` | `docker:24.0.4-gbi-focal` | Gradle integration tests with C++ helper (release/zts variants) | |
| 12 | +| `appsec integration tests (helper-rust): [{target}]` | same | Same tests with Rust helper (`-PuseHelperRust`); includes `debug` variant | |
| 13 | +| `appsec integration tests (ssi): [{target}]` | same | SSI mode (PHP 8.3 only) | |
| 14 | +| `helper-rust build and test` | same | `cargo fmt --check` + build + unit tests | |
| 15 | +| `helper-rust code coverage` | same | Unit test coverage via `cargo-llvm-cov` | |
| 16 | +| `helper-rust integration coverage` | same | Integration coverage collection (not needed locally) | |
| 17 | + |
| 18 | +Runner: `docker-in-docker:amd64` |
| 19 | +Matrix: PHP 7.0--8.5 × release/debug/zts/musl/ssi (varies by job group) |
| 20 | + |
| 21 | +**Important:** `testX.Y-debug` are not gradle targets that are run on CI. They |
| 22 | +may, however, be useful for debugging. |
| 23 | + |
| 24 | +CI passes `TERM=dumb` and `--scan -Pbuildscan` to Gradle. `TERM=dumb` |
| 25 | +suppresses progress animations in CI logs; both flags are optional |
| 26 | +locally. |
| 27 | + |
| 28 | +## Prerequisites |
| 29 | + |
| 30 | +- JDK 17+. If not available via your system package manager, use SDKMAN |
| 31 | + (`sdk install java 17`) or download from |
| 32 | + https://download.oracle.com/java/17/archive/. |
| 33 | +- Docker daemon running |
| 34 | + |
| 35 | +## Working Directory |
| 36 | + |
| 37 | +All `./gradlew` commands run from: |
| 38 | + |
| 39 | +``` |
| 40 | +appsec/tests/integration/ |
| 41 | +``` |
| 42 | + |
| 43 | +## Running Tests |
| 44 | + |
| 45 | +### Full suite for one PHP target |
| 46 | + |
| 47 | +```bash |
| 48 | +./gradlew test8.3-debug --info |
| 49 | +``` |
| 50 | + |
| 51 | +### Single test (fastest feedback loop) |
| 52 | + |
| 53 | +```bash |
| 54 | +./gradlew test8.3-debug --info \ |
| 55 | + --tests "com.datadog.appsec.php.integration.Apache2FpmTests.Pool environment" |
| 56 | +``` |
| 57 | + |
| 58 | +The `--tests` filter accepts: |
| 59 | +- Full method: `"com.datadog.appsec.php.integration.Apache2FpmTests.Pool environment"` |
| 60 | +- Class only: `"*Apache2FpmTests*"` or `"com.datadog.appsec.php.integration.Apache2FpmTests"` |
| 61 | +- Wildcard: `"*FpmTests*"` |
| 62 | + |
| 63 | +### With helper-rust (instead of C++ helper) |
| 64 | + |
| 65 | +```bash |
| 66 | +./gradlew test8.3-debug -PuseHelperRust --info \ |
| 67 | + --tests "com.datadog.appsec.php.integration.Apache2FpmTests.Pool environment" |
| 68 | +``` |
| 69 | + |
| 70 | +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. |
| 71 | + |
| 72 | +### C++ helper (default) |
| 73 | + |
| 74 | +Omit `-PuseHelperRust` and `-PhelperBinary`. The C++ helper is built via the `buildAppsec-*` task. |
| 75 | + |
| 76 | +## Image Tags |
| 77 | + |
| 78 | +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): |
| 79 | + |
| 80 | +```bash |
| 81 | +./gradlew test8.3-debug -PfloatingImageTags --info |
| 82 | +``` |
| 83 | + |
| 84 | +## Available Gradle Tasks |
| 85 | + |
| 86 | +### Test tasks |
| 87 | + |
| 88 | +Pattern: `test{version}-{variant}` |
| 89 | + |
| 90 | +| Variant | Notes | |
| 91 | +|---|---| |
| 92 | +| `release` | Standard build | |
| 93 | +| `debug` | Debug build (assertions enabled) | |
| 94 | +| `release-zts` | Thread-safe build | |
| 95 | +| `release-musl` | Alpine/musl (only `8.5-release-musl`) | |
| 96 | +| `release-ssi` / `debug-ssi` | SSI mode (only PHP 8.3) | |
| 97 | + |
| 98 | +Full list: `./gradlew tasks --group=Verification` |
| 99 | + |
| 100 | +### Helper-rust tasks |
| 101 | + |
| 102 | +| Task | Description | |
| 103 | +|---|---| |
| 104 | +| `buildHelperRust` | Build helper-rust with musl (universal binary). Output in `php-helper-rust` volume. | |
| 105 | +| `testHelperRust` | `cargo fmt --check` + `cargo build --release` + `cargo test --release` (runs inside `php-deps` image) | |
| 106 | +| `coverageHelperRust` | Unit test coverage via `cargo-llvm-cov`. Output: `php-helper-rust-coverage` volume. | |
| 107 | +| `buildHelperRustWithCoverage` | Build with `-C instrument-coverage` for integration coverage collection. | |
| 108 | +| `generateHelperRustIntegrationCoverage` | Merge `.profraw` files into lcov after integration run. | |
| 109 | + |
| 110 | +### Build tasks |
| 111 | + |
| 112 | +| Task | Description | |
| 113 | +|---|---| |
| 114 | +| `buildTracer-{v}-{var}` | Build ddtrace.so for given PHP version/variant | |
| 115 | +| `buildAppsec-{v}-{var}` | Build ddappsec.so (C++ extension + helper) | |
| 116 | +| `buildHelperRust` | Build Rust helper (musl, universal) | |
| 117 | +| `buildLibddwaf` | Build libddwaf shared library | |
| 118 | + |
| 119 | +### Other tasks |
| 120 | + |
| 121 | +| Task | Description | |
| 122 | +|---|---| |
| 123 | +| `loadCaches` | Restore Docker volume caches from tarball | |
| 124 | +| `saveCaches` | Save Docker volume caches to tarball | |
| 125 | +| `clean` | Delete build directory and clean Docker volumes | |
| 126 | +| `check` | Run all test tasks | |
| 127 | + |
| 128 | +All tasks: `./gradlew tasks --all` |
| 129 | + |
| 130 | +## Interactive Container (runMain) |
| 131 | + |
| 132 | +Start a test container without running tests (for manual debugging): |
| 133 | + |
| 134 | +```bash |
| 135 | +./gradlew runMain8.3-release -PtestClass=com.datadog.appsec.php.integration.Apache2FpmTests |
| 136 | +``` |
| 137 | + |
| 138 | +The `-PtestClass` property is required (the task is not created without it). Add `-PuseHelperRust` or `-PhelperBinary=...` as needed. |
| 139 | + |
| 140 | +SSI variant: |
| 141 | + |
| 142 | +```bash |
| 143 | +./gradlew runMain8.3-release-ssi -PtestClass=com.datadog.appsec.php.integration.Apache2FpmTests |
| 144 | +``` |
| 145 | + |
| 146 | +## Logs |
| 147 | + |
| 148 | +After a test run, logs are in: |
| 149 | + |
| 150 | +``` |
| 151 | +build/test-logs/<TestClassName>-<version>-<variant>/ |
| 152 | +``` |
| 153 | + |
| 154 | +For example: |
| 155 | + |
| 156 | +``` |
| 157 | +build/test-logs/com.datadog.appsec.php.integration.Apache2FpmTests-8.3-debug/ |
| 158 | +├── access.log |
| 159 | +├── appsec.log # PHP extension appsec log |
| 160 | +├── error.log # Apache error log |
| 161 | +├── helper.log # Helper process log (C++ or Rust) |
| 162 | +├── php_error.log |
| 163 | +├── php_fpm_error.log |
| 164 | +└── sidecar.log |
| 165 | +``` |
| 166 | + |
| 167 | +To distinguish which helper ran, check `helper.log`: |
| 168 | +- Rust: starts with `[INFO] AppSec helper starting` |
| 169 | +- C++: starts with `[info]` lines like `Started listening on abstract socket` |
| 170 | + |
| 171 | +## Musl/Alpine Target |
| 172 | + |
| 173 | +The `test8.5-release-musl` target uses an Alpine-based nginx+fpm image. Tests tagged with `@Tag("musl")` are included; untagged tests are excluded. |
| 174 | + |
| 175 | +```bash |
| 176 | +./gradlew test8.5-release-musl -PuseHelperRust --info |
| 177 | +``` |
| 178 | + |
| 179 | +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. |
| 180 | + |
| 181 | +## CI Job Mapping |
| 182 | + |
| 183 | +| CI Job | Gradle Command | |
| 184 | +|---|---| |
| 185 | +| `appsec integration tests: [test8.3-release]` | `./gradlew test8.3-release` | |
| 186 | +| `appsec integration tests (helper-rust): [test8.3-debug]` | `./gradlew test8.3-debug -PuseHelperRust` | |
| 187 | +| `appsec integration tests (ssi): [test8.3-release-ssi]` | `./gradlew test8.3-release-ssi` | |
| 188 | +| `helper-rust build and test` | `./gradlew testHelperRust` | |
| 189 | +| `helper-rust code coverage` | `./gradlew coverageHelperRust` | |
| 190 | +| `helper-rust integration coverage` | `./gradlew buildHelperRustWithCoverage` then integration test with `-PuseHelperRustCoverage` | |
| 191 | + |
| 192 | +CI also passes `--scan -Pbuildscan` for Gradle build scans, which is optional locally. |
| 193 | + |
| 194 | +## Docker Volumes |
| 195 | + |
| 196 | +Gradle uses named Docker volumes for build artifacts and caches. Key volumes: |
| 197 | + |
| 198 | +| Volume | Contents | |
| 199 | +|---|---| |
| 200 | +| `php-helper-rust` | `libddappsec-helper.so` (Rust helper binary) | |
| 201 | +| `php-tracer-{v}-{var}` | Built `ddtrace.so` | |
| 202 | +| `php-appsec-{v}-{var}` | Built `ddappsec.so` + C++ helper | |
| 203 | +| `php-tracer-cargo-cache` | Cargo registry cache | |
| 204 | +| `php-tracer-cargo-cache-git` | Cargo git cache | |
| 205 | +| `php-appsec-boost-cache` | Boost build cache | |
| 206 | +| `php-helper-rust-coverage` | Coverage-instrumented binary + profraw files | |
| 207 | + |
| 208 | +To force a rebuild, remove the relevant volume: |
| 209 | + |
| 210 | +```bash |
| 211 | +docker volume rm php-helper-rust |
| 212 | +``` |
| 213 | + |
| 214 | +To clean everything: |
| 215 | + |
| 216 | +```bash |
| 217 | +./gradlew clean |
| 218 | +``` |
| 219 | + |
| 220 | +## Debugging |
| 221 | + |
| 222 | +Attach a Java debugger to the test runner: |
| 223 | + |
| 224 | +```bash |
| 225 | +./gradlew test8.3-debug --tests "*Apache2FpmTests*" --debug-jvm |
| 226 | +``` |
| 227 | + |
| 228 | +Enable PHP Xdebug in the test container: |
| 229 | + |
| 230 | +```bash |
| 231 | +./gradlew test8.3-debug --tests "*Apache2FpmTests*" -PXDEBUG=1 |
| 232 | +``` |
| 233 | + |
| 234 | +## Expected skips |
| 235 | + |
| 236 | +A significant number of tests are skipped on any given target — this is |
| 237 | +normal. Skip conditions are `@EnabledIf` guards in the test classes: |
| 238 | + |
| 239 | +| Test class | Skips on | Reason | |
| 240 | +|---|---|---| |
| 241 | +| `FrankenphpClassicTests`, `FrankenphpWorkerTests` | anything except `8.4-zts` | Requires ZTS + PHP 8.4 | |
| 242 | +| `Laravel8xTests` | anything except `7.4` (NTS) | Requires PHP 7.4 non-ZTS | |
| 243 | +| `Symfony62Tests` | anything except `8.1` (NTS) | Requires PHP 8.1 non-ZTS | |
| 244 | +| `RaspSqliTests` | no MySQL service | Requires a running MySQL | |
| 245 | +| `SsiStableConfigTests` | non-SSI variants | Requires `-DSSI=true` | |
| 246 | + |
| 247 | +On `test8.3-debug` expect ~67 skips out of ~300 tests; all are expected. |
| 248 | + |
| 249 | +## Test report |
| 250 | + |
| 251 | +After a run, the HTML report is at: |
| 252 | + |
| 253 | +``` |
| 254 | +appsec/tests/integration/build/reports/tests/test8.3-debug/index.html |
| 255 | +``` |
| 256 | + |
| 257 | +(Replace `test8.3-debug` with your target.) Open in a browser for a |
| 258 | +structured pass/fail/skip breakdown. |
| 259 | + |
| 260 | +# Debugging |
| 261 | + |
| 262 | +Run gradle with `--debug-jvm`. This will stop for the debugger, indicating so in the output. |
| 263 | +When you see the message, start jdb in a tmux session. |
| 264 | + |
| 265 | +If you need to inspect sidecar/helper or PHP issues: |
| 266 | + |
| 267 | +* Put a breakpoint in jdb that stops the test in the appropriate place (usually |
| 268 | + just before a request is executed). |
| 269 | +* Inside the test container (determine first its id), attach gdb to sidecar |
| 270 | + (`pref -f dd-ipc-helper`) or to PHP (usually an apache or an FPM worker -- if |
| 271 | + you're investigating code run during processes it will not be the master |
| 272 | + process). sidecar requires as a first command `file /proc/<pid>/exe`). |
| 273 | +* See [gdb.md](../gdb.md) for more information on how to run gdb. Always read |
| 274 | + this file before attempting to use gdb. |
| 275 | + |
| 276 | +## Gotchas |
| 277 | + |
| 278 | +- The `test` task itself is disabled (`tasks['test'].enabled = false`). Use versioned tasks like `test8.3-debug`. |
| 279 | +- 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. |
| 280 | +- 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. |
| 281 | +- 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. |
| 282 | +- **c-ares DNS failure in Alpine containers.** Alpine's `curl` and `git` use |
| 283 | + c-ares for DNS, which fails to resolve hosts when the DNS server includes |
| 284 | + EDNS COOKIE options in responses (common with home routers). `wget` and |
| 285 | + `getent` are unaffected (they use musl's native resolver). This breaks the |
| 286 | + `buildAppsec-*-musl` task which needs to `git clone` cmake dependencies. |
| 287 | + Fix: pass `--dns 8.8.8.8` to the Docker command. |
| 288 | +- `--info` is recommended for seeing test output in the console. Without it, output goes only to the HTML report in `build/reports/tests/`. |
0 commit comments