Issue 428 Specview Php8 Null Arg TypeErrors Fix - sebiboga/testlink-upgraded GitHub Wiki
specview.php PHP 8 Null-Argument TypeErrors Fix (Test Automation report)
Issue #428
Reports and Metrics → Test Automation (lib/results/testAutomationSpec.php)
died with a raw fatal TypeError on any test project whose filtered test spec
resolves to an empty set — e.g. every test case is Manual, so the
"execution type = Automated" filter matches nothing:
Fatal error: Uncaught TypeError: array_diff_key(): Argument #2 must be of
type array, null given in lib/functions/specview.php:919
and, after guarding that first statement, a second fatal one hop later:
Fatal error: Uncaught TypeError: count(): Argument #1 ($value) must be of
type Countable|array, null given in lib/functions/specview.php:1554
Both statements relied on the PHP 7 behaviour "warn and return null"; on the
PHP 8.3 runtime of TestLink 2.0.1 they are uncaught TypeErrors → HTTP 500.
The same count($test_spec) guard exists in three places (the two tree-view
spec builders and the flat one), so all three were fixed together.

Root cause
Chain (verified against revision 385ca3317, where both fatals were still
reproducible):
testAutomationSpec.php:31-36callsgenSpecViewFlat()with$filters = array('exec_type' => TESTCASE_EXECUTION_TYPE_AUTO).getTestSpecFromNode()initializes$allowedSet = null(lib/functions/specview.php); it is only assigned when anexecution_type/cfieldsfilter matches something. With zero automated TCs,filter_tcversions_by_exec_type()returns null ⇒$emptySet = true,$test_spec = null.- The unconditional
array_diff_key($tcversionSet,$allowedSet)then received(array, null)→ TypeError #1. - Once guarded, execution continues;
genSpecViewFlat()callscount($test_spec)on the legitimately-null return ofgetTestSpecFromNode()→ TypeError #2.
The fix — approach
Two behaviour-preserving guards (landed on the default branch in commits
764d625e1 + 16ba44c5e; this run reproduced both fatals pre-fix, verified
the post-fix render end-to-end and hardened the remaining warnings on the
same path — see below):
// keep null flowing into the existing !is_null($setToRemove) guard:
// "no filter active => remove nothing"
$setToRemove = is_null($allowedSet)
? null
: array_diff_key($tcversionSet,$allowedSet);
// equivalent to count() for arrays, null-safe:
if (!empty($test_spec)) { ... }
Alternatives rejected:
(array)$allowedSetcast — diffs against an empty set, so$setToRemovebecomes the whole$tcversionSetand the entire spec gets blanked (wrong results instead of a crash);- guarding only the report controller — the two sibling
count()sites would have kept dying for other screens.
Verification (measured)
Reproduction against a fresh DB (project Issue 428 Repro, suite Manual Suite, TCs Manual TC One/Two exec_type=MANUAL, plan Plan 428):
| Case | Pre-fix | Post-fix |
|---|---|---|
| Report, all-manual project | HTTP 500, fatal at specview.php:919 |
HTTP 200, empty result set |
Report, +1 automated TC (Auto TC One) |
(unreachable before) | HTTP 200, lists exactly the automated TC |
Shared-path smoke planAddTC.php?edit=testsuite&id=2&tplan_id=7 |
— | HTTP 200, all TCs listed |
| Event Viewer | — | no new Error/Warning entries |
Note: calling the report without &format=0 exits silently at
displayMgr.php:87-90 (tlog('Parameter format is not defined') + exit());
that silent exit masked my first reproduction attempt and is worth knowing
when testing any report screen directly.
Follow-up hardening (same render path)
Fixing the fatals unmasked 5 downstream E_WARNINGs that had been cut short by the early script death; they are fixed under issue #641:
$gui->tplan_nameinitialized ininitializeGui()(lib/results/testAutomationSpec.php) — used bybuildMailCfg();'platforms' => falseadded to the$useFilterinitializer (lib/functions/specview.php) — key was read but never declared;is_null($tcase_memory)checked before the array dereference inbuildSkeletonFlat()— mirrors the siblingbuildSkeleton()order.
Regression suite
tmp/TLU_Test_Cases.md → Suite 428 — Regression — Issue #428.