ci: enable the PHPUnit job, which this caller never switched on - #1130
Open
rubenvdlinde wants to merge 4 commits into
Open
ci: enable the PHPUnit job, which this caller never switched on#1130rubenvdlinde wants to merge 4 commits into
rubenvdlinde wants to merge 4 commits into
Conversation
`enable-phpunit` defaults to false in the shared quality workflow and this caller never set it, so openconnector's 2094 unit tests across 255 files under tests/Unit have never once run in CI. The failure mode is silent: the job still appears in the checks list, reports `skipped`, and renders with its matrix placeholders unexpanded, because GitHub evaluates a matrix job's `if:` before expanding the matrix. That is visually identical to a repo with no PHP tests at all. No matrix input is missing. `php-test-versions` and `nextcloud-test-refs` both carry non-empty defaults in the callee; pipelinq omits both and still expands to four legs. This single boolean is the entire fix. Verified locally against a real per-worktree `composer install` (never a symlinked or mounted vendor, which would silently autoload lib/ from the main checkout): 2094 tests, 7552 assertions, 0 failures. Positive control: throwing from EnvironmentService::create() turned 3 tests red with the trace pointing at the worktree's own lib/, proving the run measures this tree; reverted green.
rubenvdlinde
requested review from
Rem-Dam,
SudoThijn,
WilcoLouwerse,
bbrands02,
remko48 and
rjzondervan
as code owners
August 3, 2026 16:48
Contributor
Quality Report — ConductionNL/openconnector @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| composer | ✅ | ✅ 153/153 | |||
| npm | ✅ | ✅ 701/701 | |||
| PHPUnit | ❌ | ||||
| Newman | ❌ | ||||
| Playwright | ❌ |
Quality workflow — 2026-08-03 17:01 UTC
Download the full PDF report from the workflow artifacts.
…ture Enabling PHPUnit surfaced a hard fatal that killed the runner before it printed its banner — zero tests executed, all four matrix legs identical: Declaration of OC\\Files\\Node\\LazyFolder::listen($scope, $method, callable $callback) must be compatible with OC\\Hooks\\Emitter::listen(string $scope, string $method, callable $callback): void tests/stubs/OC/Hooks/Emitter.php declared `listen()` with parameter types and a `: void` return. Core declares it untyped. The stub is loaded by name, so inside a real Nextcloud tree it SHADOWS core's interface, and core's own implementors are then validated against it. The chain is LazyRoot extends LazyFolder implements IRootFolder, and IRootFolder extends \\OC\\Hooks\\Emitter — so compiling LazyRoot checks LazyFolder::listen() against whatever this file says, and fataled. This was invisible for as long as the PHPUnit job was switched off: a bare checkout has no core LazyFolder to conflict with, so the suite is green locally with either signature. Both methods now mirror core byte-for-byte (identical on stable31, stable32 and master), with a docblock explaining why they must stay untyped. Control, proven live in both directions against core's real classes: with the old stub the probe reproduces the CI fatal verbatim; with this one it loads LazyRoot cleanly. Suite still 2094 tests / 7552 assertions / 0 failures.
Contributor
Quality Report — ConductionNL/openconnector @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| composer | ✅ | ✅ 153/153 | |||
| npm | ✅ | ✅ 701/701 | |||
| PHPUnit | ❌ | ||||
| Newman | ❌ | ||||
| Playwright | ❌ |
Quality workflow — 2026-08-03 17:43 UTC
Download the full PDF report from the workflow artifacts.
The bootstrap has two modes — bare checkout (register vendored OCP + stubs) and
provisioned server (require lib/base.php and OC_App::loadApps()). Only the bare
mode had ever been exercised, because this repo's PHPUnit job was switched off.
The stub registration ran UNCONDITIONALLY, ahead of both modes. Since these
classes are loaded by name, a stub defined first shadows core's real class and
core is then checked against our stand-in. Two fatals, each before PHPUnit ran
a single test:
* OC\Hooks\Emitter (fixed in the previous commit) — typed listen(): void vs
core's untyped LazyFolder::listen(), fatal when compiling LazyRoot.
* Doctrine\DBAL\Query\Expression\ExpressionBuilder — the stub has no eq(),
so core's OC\AppConfig::loadConfig() died with 'Call to undefined method
...ExpressionBuilder::eq()' during boot.
The per-class class_exists() guards could not prevent either: they are
evaluated before lib/base.php is required, so core's classes are not yet
loadable, every probe returns false, and the stub always wins.
Detection now happens once, up front, and gates the vendored-OCP PSR-4 mapping
and the whole stub block. In a provisioned server core supplies the real
Doctrine and OC classes, and CI checks out OpenRegister as a sibling app, so
the stand-ins are unnecessary there as well as harmful. The boot block reuses
the same flag rather than recomputing the condition.
Bare-mode suite unchanged: 2094 tests, 7552 assertions, 0 failures.
Refines the previous commit. That one skipped the stubs when a server was
present; this one stops booting the server for the unit suite at all, which is
what the suite was designed for and what its own comments already claimed.
phpunit.xml declares exactly one testsuite — tests/Unit — and the stub wiring
in this file exists so that suite needs no server. Requiring lib/base.php anyway
was the root of both fatals, because the stand-ins are loaded by NAME and shadow
core's real classes, which core is then type-checked against.
Booting also swaps the OpenRegister stub for the real ObjectService. This repo
already documents, in the stub itself, that their find() signatures diverge
deliberately ('KNOWN, DELIBERATE REMAINING DRIFT'), because ~38 positional
willReturnCallback closures are written against the stub's parameter order.
Booting breaks all of them at once. Realigning that is a genuine, separate,
mechanical change — not something to smuggle into the commit that turns the CI
job on. It remains open debt and is unchanged by this PR.
tests/Integration still wants a live server, so the boot is preserved behind
OPENCONNECTOR_TEST_BOOTSTRAP_NEXTCLOUD=1 rather than deleted.
Control, both directions: with the variable unset the bootstrap completes and
lib/base.php is never required; with it set to 1 against a fake server tree the
marker inside base.php prints. Suite unchanged at 2094 tests / 7552 assertions
/ 0 failures.
Contributor
Quality Report — ConductionNL/openconnector @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| composer | ✅ | ✅ 153/153 | |||
| npm | ✅ | ✅ 701/701 | |||
| PHPUnit | ❌ | ||||
| Newman | ✅ | ||||
| Playwright | ❌ |
Quality workflow — 2026-08-03 18:45 UTC
Download the full PDF report from the workflow artifacts.
Contributor
Quality Report — ConductionNL/openconnector @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| composer | ✅ | ✅ 153/153 | |||
| npm | ✅ | ✅ 701/701 | |||
| PHPUnit | ❌ | ||||
| Newman | ✅ | ||||
| Playwright | ✅ |
Quality workflow — 2026-08-03 18:56 UTC
Download the full PDF report from the workflow artifacts.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
openconnector
s **2094 unit tests across 255 files** undertests/Unit` have never once run in CI.Cause
enable-phpunitdefaults tofalsein the sharedquality.yml, and this caller never set it. One boolean.The failure mode is silent. The job still appears in the checks list, reports
skipped, and renders as:GitHub evaluates a matrix job
sif:` before expanding the matrix, so the placeholders stay literal. That is visually identical to a repo that simply has no PHP tests.What this is not
It was initially suspected that these repos fail to supply
php-test-versions/nextcloud-test-refs, resolving the matrix empty. That theory is false. Both inputs carry non-empty defaults in the callee, and pipelinq omits both while still expanding to four legs:So no matrix wiring is needed here.
Confirmed openconnector was not merely gated by the other known cause — the callee also skips PHPUnit when
needs.php-quality.result == "failure". Across 20+ completeddevelopmentruns, openconnectorsphp-qualityandsecuritylegs were **all green** and PHPUnit was stillskipped`. The disabled input is the sole cause.Verification
Run locally against a real per-worktree
composer install— never a symlinked or mountedvendor/, which makes Composers classmap resolve$baseDirthrough the symlink and silently autoloadlib/` from the main checkout:Positive control (the point that matters — a green run proves nothing until it has been shown it can fail): throwing from
EnvironmentService::create()turned 3 tests red with the trace pointing at the worktrees ownlib/`, proving the run measures this tree. Reverted; green again.A first mutation attempt on
findBySlug()stayed green — not a wrong-tree artefact but genuinely weak coverage: only the null path is asserted, and the mutation returned null. Worth noting as real (small) coverage debt now that the suite is visible.Note
tests.ymlin this repo also declares a PHPUnit + coverage-gate job, but is deliberately disabled (branches: [never], see #1109) because it returnsstartup_failure. This PR does not touch it — it makes the shared pipeline run the suite, which is the path that actually works.Update — what un-gating the job actually surfaced
Turning the job on did not simply light up a green suite. It exposed two real defects that had been invisible for exactly as long as the gate was dark, both fatal before PHPUnit could finish, both identical across all four matrix legs.
1.
OC\Hooks\Emitterstub misdeclared core's interface. The stub declaredlisten(): voidwith typed parameters; core declares it untyped. Stubs are loaded by NAME, so inside a real Nextcloud tree ours shadowed core's, and core's own implementors were then checked against it:The chain is
LazyRoot extends LazyFolder implements IRootFolder, andIRootFolder extends \OC\Hooks\Emitter. Fixed by mirroring core byte-for-byte. Control, proven live in both directions against core's real classes: the old stub reproduces the fatal verbatim, the new one loadsLazyRootcleanly.2. The bootstrap booted Nextcloud for a unit-only suite. With the Emitter fatal gone the next one appeared immediately —
Call to undefined method Doctrine\DBAL\Query\Expression\ExpressionBuilder::eq(), thrown from core's ownOC\AppConfig::loadConfig()during boot, because our Doctrine stub shadowed the real class.The per-class
class_exists()guards cannot prevent this: they run beforelib/base.phpis required, so core's classes are not yet loadable, every probe answers false, and the stub always wins.phpunit.xmldeclares exactly one testsuite —tests/Unit— and the stub wiring exists so that suite needs no server. The boot is now opt-in behindOPENCONNECTOR_TEST_BOOTSTRAP_NEXTCLOUD=1, whichtests/Integrationcan set. Control: variable unset,lib/base.phpis never required; set to1against a fake server tree, a marker inside it prints.Known debt left visible, deliberately not fixed here
Booting the real server also swaps the OpenRegister stub for the real
ObjectService, whosefind()signature this repo's stub knowingly diverges from — see theKNOWN, DELIBERATE REMAINING DRIFTnote already intests/stubs/OCA/OpenRegister/Service/ObjectService.php. Production is:the stub omits
$_extendand$files. I confirmed this againstopenregister@origin/developmentrather than taking the comment's word for it. I attempted the realignment and measured the blast radius: threetests/Helpers/doubles plus ~29 positionalwillReturnCallbackclosures, matching the ~38 assertions the existing note predicts. That is a real, separate, mechanical change and it is filed as such — not smuggled into the commit that merely turns the job on, and not suppressed. It remains open and unchanged by this PR.