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
46 changes: 33 additions & 13 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,16 @@ jobs:

# Prepare the environment and build the plugin
build:
name: Build
name: Build (${{ matrix.phpApi }})
runs-on: ubuntu-latest
# One artifact per PHP coverage API — see the `phpApi` comment in gradle.properties.
# One artifact per PHP coverage API — see the `phpApi` comment in gradle.properties. OpenIDE builds into build/openide.
strategy:
fail-fast: false
matrix:
phpApi: [ 252, 262 ]
include:
- { phpApi: 252, buildDir: build }
- { phpApi: 262, buildDir: build }
- { phpApi: openide, buildDir: build/openide }
steps:

# Free GitHub Actions Environment Disk Space
Expand Down Expand Up @@ -67,7 +71,7 @@ jobs:
id: artifact
shell: bash
run: |
cd ${{ github.workspace }}/build/distributions
cd ${{ github.workspace }}/${{ matrix.buildDir }}/distributions
FILENAME=`ls *.zip`
unzip "$FILENAME" -d content

Expand All @@ -78,17 +82,28 @@ jobs:
uses: actions/upload-artifact@v7
with:
name: ${{ steps.artifact.outputs.filename }}
path: ./build/distributions/content/*/*
path: ./${{ matrix.buildDir }}/distributions/content/*/*

# Run tests and upload a code coverage report
test:
name: Test
name: Test (${{ matrix.phpApi }}, ${{ matrix.os }})
needs: [ build ]
runs-on: ubuntu-latest
runs-on: ${{ matrix.os }}
# Windows is informational until its known failures are fixed (#120, #122).
continue-on-error: ${{ matrix.os == 'windows-latest' }}
strategy:
fail-fast: false
matrix:
os: [ ubuntu-latest, windows-latest ]
phpApi: [ 262, openide ]
include:
- { phpApi: 262, buildDir: build }
- { phpApi: openide, buildDir: build/openide }
steps:

# Free GitHub Actions Environment Disk Space
- name: Maximize Build Space
if: runner.os == 'Linux'
uses: jlumbroso/free-disk-space@v2.0.0
with:
tool-cache: false
Expand All @@ -113,18 +128,19 @@ jobs:

# Run tests
- name: Run Tests
run: ./gradlew check
run: ./gradlew check -PphpApi=${{ matrix.phpApi }}

# Collect Tests Result of failed tests
- name: Collect Tests Result
if: ${{ failure() }}
uses: actions/upload-artifact@v7
with:
name: tests-result
path: ${{ github.workspace }}/build/reports/tests
name: tests-result-${{ matrix.phpApi }}-${{ matrix.os }}
path: ${{ github.workspace }}/${{ matrix.buildDir }}/reports/tests

# Upload the Kover report to CodeCov
- name: Upload Code Coverage Report
if: ${{ matrix.phpApi == 262 && runner.os == 'Linux' }}
uses: codecov/codecov-action@v7
with:
files: ${{ github.workspace }}/build/reports/kover/report.xml
Expand Down Expand Up @@ -170,12 +186,16 @@ jobs:

# Run plugin structure verification along with IntelliJ Plugin Verifier
verify:
name: Verify plugin
name: Verify plugin (${{ matrix.phpApi }})
needs: [ build ]
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
phpApi: [ 252, 262 ]
include:
- { phpApi: 252, buildDir: build }
- { phpApi: 262, buildDir: build }
- { phpApi: openide, buildDir: build/openide }
steps:

# Free GitHub Actions Environment Disk Space
Expand Down Expand Up @@ -212,7 +232,7 @@ jobs:
uses: actions/upload-artifact@v7
with:
name: pluginVerifier-result-${{ matrix.phpApi }}
path: ${{ github.workspace }}/build/reports/pluginVerifier
path: ${{ github.workspace }}/${{ matrix.buildDir }}/reports/pluginVerifier

# Prepare a draft release for GitHub Releases page for the manual verification
# If accepted and published, the release workflow would be triggered
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@

## [Unreleased]

### Added

- An OpenIDE build using PHP for OpenIDE, with test discovery, run/debug/coverage configurations, Infection mutation
testing and run history. It supports the project's PHP interpreter profiles and keeps unconfirmed remote processes
from losing their temporary reports.

### Fixed

- List tests sharing a data provider in source order for a consistent default selection.
Expand Down
89 changes: 65 additions & 24 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Project Overview

IntelliJ IDEA Ultimate / PhpStorm plugin for **Testo** — a PHP testing framework.
IntelliJ Platform plugin for **Testo** — a PHP testing framework, with PhpStorm and OpenIDE builds.
Provides full IDE integration: test discovery, run/debug/coverage configurations, a channel-aware test console,
run history, code generation, inspections, and navigation.

Expand All @@ -21,9 +21,9 @@ Dependabot bumps these regularly — read the files rather than trusting this ta
| Component | Version / Value |
|----------------------|------------------------------------------|
| Language | Kotlin 2.4.20 |
| JVM Toolchain | Java 21 |
| IntelliJ Platform | 2025.2 / 2026.2 (IU — IDEA Ultimate) |
| Build range | 252–261.* and 262+ (two artifacts) |
| JVM Toolchain | Java 21 (PhpStorm), Java 25 (OpenIDE) |
| IntelliJ Platform | 2025.2 / 2026.2 (IU), OpenIDE 2026.2.1 |
| Build range | PhpStorm: 252–261.* and 262+; OpenIDE: 262.* |
| Plugin version | `2026.16` (`pluginVersion`) |
| Build system | Gradle wrapper 9.8.0 |
| IntelliJ Plugin SDK | `org.jetbrains.intellij.platform` 2.19.0 |
Expand All @@ -32,15 +32,15 @@ Dependabot bumps these regularly — read the files rather than trusting this ta
| Coverage | Kover 0.9.10 (XML report on `check`) |
| Test framework | JUnit 4.13.2, OpenTest4J 1.3.0 |

`platformPlugins` (marketplace deps, pinned to builds matching the target platform): `com.jetbrains.php`,
The PhpStorm `platformPlugins` (marketplace dependencies pinned to the target platform) are: `com.jetbrains.php`,
`phpstorm-remote-interpreter`, `php.codeception`, `php.behat`, `gherkin`, `xepozz.ide.introspector`
(+ `hackathon.indices.viewer` on 252 only — it has no 262 build).
`platformBundledModules`: `intellij.platform.coverage`, `intellij.spellchecker` (+ `intellij.platform.smRunner`,
`intellij.platform.testRunner`, `intellij.platform.ui.jcef` on 262, which split them out of the monolith).

### Two build variants (`phpApi`)
### PhpStorm build variants (`phpApi`)

The plugin ships as two artifacts because the platform since/until ranges and a few bundled modules differ across
The PhpStorm variant ships as two artifacts because the platform since/until ranges and a few bundled modules differ across
2025.2 and 2026.2 (jcef / smRunner / testRunner split out of the monolith on 262). So every platform-dependent property
in `gradle.properties` is declared twice with an API suffix and selected by `phpApi`:

Expand All @@ -51,7 +51,7 @@ in `gradle.properties` is declared twice with an API suffix and selected by `php

**The two artifacts no longer differ in source** — coverage runs on 100 % public platform API (`coverage/`, see
"Generated reports" / the `coverage/` tree), so the old `src/php252/kotlin` vs `src/php262/kotlin` typealias split is
gone. `phpApi` is a build selector for the platform version, compatibility range and platform modules. The core
gone. For PhpStorm, `phpApi` selects the platform version, compatibility range and platform modules. The core
uses its own `TestoCoverageDriver`; only the PhpStorm implementation maps it to the PHP plugin's coverage driver.

Source that is single but not version-agnostic is reached by reflection, never a direct symbol: `XDebuggerManager.newSessionBuilder` (`TestoDebugRunner`) exists only on 262, so a direct call compiles green locally on 262 and breaks the 252 build in CI. Guard any such 262-only platform symbol behind a reflective lookup with a 252 fallback, or compile both variants before calling it done.
Expand All @@ -70,6 +70,25 @@ verifies both variants via a matrix, and the Marketplace serves each IDE the bui
> Note: `gradleVersion` in `gradle.properties` (9.5.0) lags the wrapper (9.8.0) — the property only feeds the
> `wrapper` task, so running `./gradlew wrapper` would downgrade it. Bump the property when syncing.

### OpenIDE build (`phpApi=openide`)

`-PphpApi=openide` selects `src/openide` and `src/openideTest` instead of the PhpStorm implementation.
It compiles against OpenIDE 2026.2.1 and PHP for OpenIDE 0.9.4, using Java 25. The PHP dependency is resolved
from the OpenIDE plugin store. Both implementations run the shared contract and run-context tests; isolation
checks prevent dependencies between the two adapters.

```shell
./gradlew check buildPlugin verifyPlugin -PphpApi=openide
```

The OpenIDE build has its own output directory (`build/openide`) and ZIP name. It is excluded from the normal
`phpApis` publishing list and uses `OPENIDE_PUBLISH_TOKEN` when explicitly published to the OpenIDE plugin store.
Each variant's `clean` preserves the other variant's output; the OpenIDE distribution is retained because the
platform plugin unpacks it during configuration.

`verifyPlugin` stages the resolved PHP plugin in a local dependency repository and checks it in offline mode.
This verifies the same dependency artifact used for compilation, without looking for it in JetBrains Marketplace.

## Build & Run Commands

```bash
Expand All @@ -86,15 +105,17 @@ Ready-made IDE run configurations live in `.run/`: *Run Plugin*, *Run Tests*, *R
## Project Structure

The plugin keeps its shared logic in `src/main` and the PhpStorm implementation in `src/phpstorm`.
The latter is compiled into both existing builds, selected with `-PphpApi=252` or `-PphpApi=262`.
That implementation is selected with `-PphpApi=252` or `-PphpApi=262`. The OpenIDE implementation lives in
`src/openide` and is selected with `-PphpApi=openide`.

The shared code accesses PHP syntax, symbols and interpreter paths through `php/TestoPhp.kt`.
Run selections and command arguments are represented by the platform-neutral types in `launch/`.
Infection uses `TestoToolEnvironment` to prepare a process and manage its output, cancellation and resources.
Classes extending the PHP plugin's APIs, along with their registrations, live in the PhpStorm source set.
Classes using a PHP plugin's APIs, along with their registrations, live in the corresponding implementation source set.

Shared tests belong in `src/test`; implementation-specific tests belong in `src/phpstormTest`.
`CoreIsolationTest` checks that shared sources do not depend on the PHP implementation.
Shared tests belong in `src/test`; implementation-specific tests belong in `src/phpstormTest` or `src/openideTest`.
`CoreIsolationTest` checks that shared sources do not depend on a PHP implementation and that the implementations
do not depend on each other.
The contract tests and snapshots cover run contexts, commands, navigation, saved configurations and reruns.

```
Expand Down Expand Up @@ -289,14 +310,29 @@ src/phpstorm/resources/META-INF/
└── testo-php-coverage.xml # the coverage program runner

src/phpstormTest/kotlin/… # implementation tests and behavior snapshots

src/openide/kotlin/com/github/xepozz/testo/openide/
├── OpenIdeTestoPhp.kt # the shared PHP contract on PHP for OpenIDE's public API
├── OpenIdeToolEnvironment.kt # captured interpreter and managed tool processes
├── OpenIdeConsole.kt # console properties and path translation
├── actions/ # Generate | Test Method
├── coverage/ # coverage and mutation runners
└── tests/ # framework registration, locator, inspections and run configurations

src/openide/resources/META-INF/
├── testo-php.xml # PHP for OpenIDE dependency and variant registrations
└── testo-php-coverage.xml # coverage program runner

src/openideTest/kotlin/… # implementation tests and shared test-project setup
```

## Architecture

### Extension points

The main descriptor includes `src/phpstorm/resources/META-INF/testo-php.xml` for PHP-specific registrations.
The optional coverage descriptor includes `testo-php-coverage.xml` from the same implementation.
The main descriptor includes `META-INF/testo-php.xml` from the selected implementation
(`src/phpstorm/resources` or `src/openide/resources`). The optional coverage descriptor includes
`testo-php-coverage.xml` from the same implementation.

`com.intellij` namespace: `fileType` (maps the `testo`/`testo.php`/`testo.bat` binaries onto PHP),
`runLineMarkerContributor` (order="first"), `configurationType`, `runConfigurationProducer`,
Expand All @@ -321,8 +357,9 @@ replacement for the platform `Rerun`, a `Tools | Testo` menu (channel-icon previ

### Dependencies

`com.intellij.modules.platform`, `com.jetbrains.php` (hard), `com.intellij.modules.coverage` (optional).
Requires IDEA Ultimate or PhpStorm — the plugin cannot load without PHP support.
Every build depends on `com.intellij.modules.platform`, with optional `com.intellij.modules.coverage` support.
The PhpStorm implementation requires `com.jetbrains.php`; the OpenIDE implementation requires `ru.openide.openphp`.
Each build must be installed with its corresponding PHP plugin.

### The Testo CLI contract

Expand Down Expand Up @@ -710,7 +747,7 @@ JUnit 4, two flavours — prefer the first when the logic allows it:
When adding behaviour, pull the pure logic into a top-level function (as `testoDisplayName` was) so it can be
tested without the platform fixture.

Run the checks for both supported platform variants:
Run the checks for both PhpStorm platform variants:

```shell
./gradlew check buildPlugin verifyPlugin -PphpApi=252
Expand All @@ -721,24 +758,28 @@ The test task canonicalizes its temporary directory so VFS and filesystem paths

## Constraints & Important Notes

- **Platform:** IntelliJ IDEA Ultimate or PhpStorm (`com.jetbrains.php` is a hard dependency of the PhpStorm implementation).
- **Platform:** IntelliJ IDEA Ultimate / PhpStorm with `com.jetbrains.php`, or OpenIDE with `ru.openide.openphp`,
selected by the build variant.
- **Keep the core independent of PHP implementations.** Code in `src/main/kotlin` and `src/test/kotlin` uses the
contracts in `php/`; PHP-plugin classes and their registrations belong in `src/phpstorm*`.
`CoreIsolationTest` enforces this boundary, including references in comments.
- **Min IDE version:** 2025.2 (build 252+), shipped as two artifacts — see "Two build variants (`phpApi`)"
contracts in `php/`; PHP-plugin classes and their registrations belong in `src/phpstorm*` or `src/openide*`.
`CoreIsolationTest` enforces these boundaries and prevents references between implementations, including comments.
- **Min IDE version:** 2025.2 (build 252+) for PhpStorm; 2026.2 (build 262) for OpenIDE.
- **Kotlin stdlib is NOT bundled** (`kotlin.stdlib.default.dependency = false`) — uses the IDE's own
- **Gradle Configuration Cache** and **Build Cache** are enabled
- **Code and comments language:** English. Comments should explain *why* (platform quirks, race conditions),
matching the density already present in `tests/console/` and `tests/actions/`.
- **Plugin description** is extracted from `README.md` between `<!-- Plugin description -->` markers at build
time — the build fails if the markers go missing
- **Release channel** is derived from the pre-release label in `pluginVersion` (e.g. `-alpha.3` → `alpha`)
- **Signing & publishing** need `CERTIFICATE_CHAIN`, `PRIVATE_KEY`, `PRIVATE_KEY_PASSWORD`, `PUBLISH_TOKEN`
- **Signing & publishing** use `CERTIFICATE_CHAIN`, `PRIVATE_KEY`, `PRIVATE_KEY_PASSWORD` and `PUBLISH_TOKEN`
for JetBrains Marketplace; the OpenIDE store uses `OPENIDE_PUBLISH_TOKEN`.

## CI/CD

- **build.yml** (push to `main`, all PRs): `buildPlugin` → `check` (Kover XML → Codecov) → Qodana →
`verifyPlugin` → release draft. Runs on `ubuntu-latest`, Java 21 (Zulu), free-disk-space step first.
`verifyPlugin` → release draft. Build and verify run for 252, 262 and `openide`; tests for 262 and `openide` on
Ubuntu and Windows, where Windows does not fail the run until its known failures are fixed (#120, #122).
Gradle provisions the OpenIDE variant's Java 25 toolchain itself.
- **release.yml** (on GitHub release): publish to JetBrains Marketplace, patch the changelog, open a PR back.
- **run-ui-tests.yml** (manual): UI tests on Ubuntu / Windows / macOS via robot-server.

Expand All @@ -748,7 +789,7 @@ The test task canonicalizes its temporary directory so VFS and filesystem paths
- i18n strings in `messages/TestoBundle.properties`, accessed via `TestoBundle`
- Icons follow IntelliJ conventions: SVG with a `_dark` variant
- Register shared extension points in `plugin.xml` (coverage-only ones in `coverage.xml`). PHP-specific registrations
belong in `src/phpstorm/resources/META-INF/testo-php.xml` or `testo-php-coverage.xml`.
belong in the selected implementation's `META-INF/testo-php.xml` or `testo-php-coverage.xml`.
- Version follows SemVer; `pluginVersion` in `gradle.properties` is the single source of truth
- Notable user-visible changes go into `CHANGELOG.md` under `## [Unreleased]` (Keep a Changelog format) —
the release workflow consumes that section
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,21 @@ Testo PHP – is a modern PHP testing library
<kbd>Settings/Preferences</kbd> > <kbd>Plugins</kbd> > <kbd>⚙️</kbd> > <kbd>Install plugin from disk...</kbd>


## OpenIDE

Build the OpenIDE variant with `./gradlew buildPlugin -PphpApi=openide`. It uses PHP for OpenIDE 0.9.4 and
OpenIDE 2026.2.1. Install the ZIP from `build/openide/distributions/` through **Install Plugin from Disk**.
The PhpStorm builds remain selected by `-PphpApi=252` and `-PphpApi=262`.

Runs use the project's active PHP interpreter. Infection retains the interpreter selected for the original test run.
On macOS, Linux, Docker/Compose and WSL, that interpreter needs the `pcntl` and `posix` extensions so Testo can stop
Infection and its child processes. PHP for OpenIDE checks these requirements before starting Infection; ordinary
Testo test runs do not require those extensions.

If the connection to an interpreter is lost, Testo keeps the temporary reports and blocks rerun. Use **Stop** to
retry stopping the same process. After restarting the IDE, check the processes in that interpreter before starting
a new test run if the previous run could not be confirmed as stopped.

---
Plugin based on the [IntelliJ Platform Plugin Template][template].

Expand Down
Loading
Loading