mirror of
https://github.com/Cacti/cacti.git
synced 2026-10-23 03:27:02 +09:00
* style(php): wrap CLI and poller calls without changing execution Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix(ci): review security inventories after poller wrapping Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * test(boost): compare handoff source contracts by PHP tokens Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * style(php): wrap poller constant strings without changing values Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * style(php): wrap poller concatenations with token-preserving source contracts Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * test(cli): assert CDEF repair queries after constant folding Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * test: verify PHP formatting preserves parsed behavior Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> --------- Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
95 lines
4.4 KiB
PHP
95 lines
4.4 KiB
PHP
<?php
|
|
/*
|
|
+-------------------------------------------------------------------------+
|
|
| Copyright (C) 2004-2026 The Cacti Group |
|
|
| |
|
|
| This program is free software; you can redistribute it and/or |
|
|
| modify it under the terms of the GNU General Public License |
|
|
| as published by the Free Software Foundation; either version 2 |
|
|
| of the License, or (at your option) any later version. |
|
|
+-------------------------------------------------------------------------+
|
|
| Cacti: The Complete RRDtool-based Graphing Solution |
|
|
+-------------------------------------------------------------------------+
|
|
*/
|
|
|
|
/*
|
|
* Regression test for #7520: the archive-table DROP in poller_boost.php was
|
|
* gated on `$rrd_updates > 0`, an aggregate SUM(status) over whatever
|
|
* completion rows landed. If 3 of 4 shards succeeded and the 4th child
|
|
* crashed before writing a completion row, the sum from the 3 successful
|
|
* shards was still positive and every archive table for the run -- including
|
|
* the crashed child's entire unprocessed shard -- got dropped.
|
|
*
|
|
* The drain loop a few lines above already computes the correct signal
|
|
* (boost_completed_children() < $expected_children) to detect a crashed
|
|
* child; the fix wires that same completeness check into the drop decision.
|
|
*/
|
|
|
|
$boostPollerPath = CACTI_PATH_BASE . '/poller_boost.php';
|
|
|
|
test('archive-table drop is gated on shard completeness, not the status sum', function () use ($boostPollerPath) {
|
|
$contents = file_get_contents($boostPollerPath);
|
|
|
|
$rrd_updates_pos = strpos($contents, 'COALESCE(SUM(CAST(status AS SIGNED)), 0)');
|
|
expect($rrd_updates_pos)->not->toBeFalse();
|
|
|
|
$drop_pos = strpos($contents, 'DROP TABLE IF EXISTS `$table`', $rrd_updates_pos);
|
|
expect($drop_pos)->not->toBeFalse();
|
|
|
|
$segment = substr($contents, $rrd_updates_pos, $drop_pos - $rrd_updates_pos);
|
|
|
|
// The completeness check must appear between the SUM() read and the
|
|
// actual DROP, gating it.
|
|
expect($segment)->toContain('boost_completed_children($run_id) >= $expected_children');
|
|
expect($segment)->toContain('$failed_children === 0');
|
|
|
|
// A bare "if ($rrd_updates > 0) { ... DROP ... }" with nothing else
|
|
// between them is exactly the bug: the drop must not be reachable
|
|
// without the completeness check in between.
|
|
$gate_pos = strpos($segment, 'boost_completed_children($run_id) >= $expected_children');
|
|
expect($gate_pos)->toBeGreaterThan(0);
|
|
});
|
|
|
|
test('partial-completion path logs a warning instead of silently keeping the tables', function () use ($boostPollerPath) {
|
|
$contents = file_get_contents($boostPollerPath);
|
|
|
|
$parser = (new \PhpParser\ParserFactory())->createForNewestSupportedVersion();
|
|
$finder = new \PhpParser\NodeFinder();
|
|
$printer = new \PhpParser\PrettyPrinter\Standard();
|
|
$branches = $finder->findInstanceOf($parser->parse($contents), \PhpParser\Node\Stmt\If_::class);
|
|
$gate = null;
|
|
|
|
foreach ($branches as $branch) {
|
|
$condition = $printer->prettyPrintExpr($branch->cond);
|
|
|
|
if (str_contains($condition, 'boost_completed_children($run_id) >= $expected_children')) {
|
|
$gate = $branch;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
expect($gate)->not->toBeNull();
|
|
expect($gate->else)->not->toBeNull();
|
|
$logs = $finder->find($gate->else->stmts, fn ($node) => $node instanceof \PhpParser\Node\Expr\FuncCall &&
|
|
$node->name instanceof \PhpParser\Node\Name &&
|
|
$node->name->toString() === 'cacti_log'
|
|
);
|
|
expect($logs)->toHaveCount(1);
|
|
$format = array_values($logs)[0]->args[0]->value;
|
|
expect($format)->toBeInstanceOf(\PhpParser\Node\Expr\FuncCall::class);
|
|
expect($format->name->toString())->toBe('sprintf');
|
|
$message = (new \PhpParser\ConstExprEvaluator())->evaluateDirectly($format->args[0]->value);
|
|
expect($message)->toContain('leaving archive tables in place for the next run to pick up');
|
|
});
|
|
|
|
test('boost_completed_children and expected_children are still computed the same way the drain loop uses', function () use ($boostPollerPath) {
|
|
$contents = file_get_contents($boostPollerPath);
|
|
|
|
// The drain loop's own completeness check must still be present and
|
|
// unchanged; the drop-gate fix reuses these exact symbols rather than
|
|
// inventing a parallel accounting mechanism that could drift from it.
|
|
expect($contents)->toContain('boost_completed_children($run_id) < $expected_children');
|
|
expect(substr_count($contents, '$expected_children'))->toBeGreaterThanOrEqual(4);
|
|
});
|