Skip to content

ci: enable the PHPUnit job, which this caller never switched on - #1130

Open
rubenvdlinde wants to merge 4 commits into
developmentfrom
ci/enable-phpunit-gate
Open

ci: enable the PHPUnit job, which this caller never switched on#1130
rubenvdlinde wants to merge 4 commits into
developmentfrom
ci/enable-phpunit-gate

Conversation

@rubenvdlinde

@rubenvdlinde rubenvdlinde commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

openconnectors **2094 unit tests across 255 files** under tests/Unit` have never once run in CI.

Cause

enable-phpunit defaults to false in the shared quality.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:

quality / PHPUnit (PHP ${{ matrix.php-version }}, NC ${{ matrix.nextcloud-ref }})   skipped

GitHub evaluates a matrix jobs if:` 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:

quality / PHPUnit (PHP 8.3, NC stable31)   success
quality / PHPUnit (PHP 8.4, NC stable32)   success
...

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+ completed development runs, openconnectors php-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 mounted vendor/, which makes Composers classmap resolve $baseDirthrough the symlink and silently autoloadlib/` from the main checkout:

Tests: 2094, Assertions: 7552, 0 failures

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 own lib/`, 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.yml in this repo also declares a PHPUnit + coverage-gate job, but is deliberately disabled (branches: [never], see #1109) because it returns startup_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\Emitter stub misdeclared core's interface. The stub declared listen(): void with 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:

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

The chain is LazyRoot extends LazyFolder implements IRootFolder, and IRootFolder 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 loads LazyRoot cleanly.

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 own OC\AppConfig::loadConfig() during boot, because our Doctrine stub shadowed the real class.

The per-class class_exists() guards cannot prevent this: they run before lib/base.php is required, so core's classes are not yet loadable, every probe answers false, and the stub always wins.

phpunit.xml declares exactly one testsuite — tests/Unit — and the stub wiring exists so that suite needs no server. The boot is now opt-in behind OPENCONNECTOR_TEST_BOOTSTRAP_NEXTCLOUD=1, which tests/Integration can set. Control: variable unset, lib/base.php is never required; set to 1 against 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, whose find() signature this repo's stub knowingly diverges from — see the KNOWN, DELIBERATE REMAINING DRIFT note already in tests/stubs/OCA/OpenRegister/Service/ObjectService.php. Production is:

find($id, $_extend, $files, $register, $schema, $_rbac, $_multitenancy, $_render)

the stub omits $_extend and $files. I confirmed this against openregister@origin/development rather than taking the comment's word for it. I attempted the realignment and measured the blast radius: three tests/Helpers/ doubles plus ~29 positional willReturnCallback closures, 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.

`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.
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/openconnector @ 29f10ca

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.
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/openconnector @ dd4e476

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.
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/openconnector @ 2ffd2a5

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.

@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/openconnector @ 1f8c646

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant