Repository navigation
Issue 577 TestPlan GetTestsuites Null Guard Fix
Fixed: generating any report/document for a test plan with no linked test cases (e.g. a freshly created empty plan, on-build report) logged 2 PHP E_WARNINGs into the Event Viewer — and the same unguarded code path could hard-crash the request:
E_WARNING
foreach() argument must be of type array|object, null given - in .../lib/functions/testplan.class.php - Line 2147
E_WARNING
foreach() argument must be of type array|object, null given - in .../lib/functions/testplan.class.php - Line 2154
Direct invocation additionally died with an uncaught TypeError one line
later: usort(): Argument #1 ($array) must be of type array, null given at
line 2164 — truncating the HTTP response (500) because $finalset was never
initialized when both dedup loops skipped.

testplan::get_testsuites($id) builds its suite list from
testplan_tcversions:
-
$recordset = $this->db->get_recordset($sql);→get_recordset()initializes its output tonulland only appends rows while fetching, so it returns NULL when the plan has zero linked tcversions (lib/functions/database.class.php:776). - Line 2146 assigned that null to
$superset, thenforeach($recordset ...)(2147) andforeach($superset ...)(2154) iteratednull→ 2× E_WARNING under PHP 8. - Because both loops skipped,
$finalset[] = $value;never executed, leaving$finalsetundefined forusort($finalset, ...)on line 2164 → uncaughtTypeErrorunder PHP 8.
Callers affected: tlTestPlanMetrics::getExecCountersByTestSuiteExecStatus()
(report screens / charts), XML-RPC getTestSuitesForTestPlan.
Minimal change inside get_testsuites() (lib/functions/testplan.class.php):
// issue #577: get_recordset() returns NULL when the test plan has no
// linked tcversions => foreach() on null + undefined $finalset for usort().
// Cast to array (same pattern as get_parenttestsuites()).
$recordset = (array)$this->db->get_recordset($sql);
...
$dup_track = array();
$finalset = array();
foreach($superset as $value)Why this way:
-
Matches house style: the sibling method
get_parenttestsuites()in the same class already uses(array)$this->db->get_recordset($sql)for exactly this reason; metrics helpers do the same for their fetchers. -
Behavior identical for populated plans:
get_recordset()returns a numerically indexed row list or NULL — casting NULL→[]is a no-op for real result sets, so nothing changes except the absence of warnings. -
Kills all three symptoms at once: no foreach-on-null warnings AND
$finalsetis always an array forusort(), so the empty plan now returnsarray()instead of crashing. -
Alternatives rejected: early-returning
array()before the loops would duplicate the guard logic and leave the$finalsethazard latent for future edits; changingget_recordset()globally was too broad for this bug.
Reproduction fixture: project + plan with a build but zero linked test cases.
| Step | Pre-fix | Post-fix |
|---|---|---|
Direct get_testsuites(emptyPlan)
|
2× E_WARNING + TypeError crash | returns array(0) {}, clean |
printDocument.php?type=testreport_onbuild via browser |
2× E_WARNING events | document renders, 0 new events |
Metrics chain getExecCountersByTestSuiteExecStatus()
|
fatal before reaching suites | completes, tsuites_full=[]
|
| Plan WITH linked case (positive regression) | works | still returns [Root Suite, TS-A] name-sorted |
Fix commit: branch fix/issue-577. Regression suite: tmp/TLU_Test_Cases.md
("Regression — Issue #577").