diff --git a/color.php b/color.php index 7ddb4f3e2..3e6d027e3 100644 --- a/color.php +++ b/color.php @@ -213,6 +213,7 @@ function color_import_processor(array &$colors) : array { switch ($line_item) { case 'hex': $hexcol = $j; + // Fall through: retain the hex column alongside the name column. case 'name': if (!$first_column) { $save_order .= ', '; diff --git a/data_debug.php b/data_debug.php index 9fb51b182..b0d6a8f14 100644 --- a/data_debug.php +++ b/data_debug.php @@ -129,6 +129,7 @@ switch (grv('action')) { case 'runall': debug_runall_filtered(); + // Fall through: render the refreshed list after running the selected sources. default: $refresh = [ 'seconds' => grv('refresh'), diff --git a/data_source_profiles.php b/data_source_profiles.php index bcafc2f4c..d89092f64 100644 --- a/data_source_profiles.php +++ b/data_source_profiles.php @@ -1141,7 +1141,8 @@ function profile_edit() : void { form_selectable_cell(filter_value($rra['name'], '', $url), $i); form_selectable_cell('' . get_span($profile['step'] * $rra['steps'] * $rra['rows']) . '', $i); - form_selectable_cell('' . isset($timespans[$rra['timespan']]) ? $timespans[$rra['timespan']] : get_span($rra['timespan']) . '', $i); // @phpstan-ignore-line + $retention = $timespans[$rra['timespan']] ?? get_span($rra['timespan']); + form_selectable_cell('' . $retention . '', $i); form_selectable_cell('' . $rra['steps'] . '', $i); form_selectable_cell('' . $rra['rows'] . '', $i); diff --git a/graphs.php b/graphs.php index b2b7fd2c0..a01a767d4 100644 --- a/graphs.php +++ b/graphs.php @@ -204,6 +204,7 @@ switch (grv('action')) { case 'unlock': $_SESSION['sess_graph_lock_id'] = gfrv('id'); $_SESSION['sess_graph_locked'] = (grv('action') == 'lock' ? true : false); + // Fall through: show the editor with the new lock state. case 'graph_edit': top_header(); graph_edit(); diff --git a/lib/api_aggregate.php b/lib/api_aggregate.php index 9c5987cb8..4da91e386 100644 --- a/lib/api_aggregate.php +++ b/lib/api_aggregate.php @@ -1290,6 +1290,7 @@ function aggregate_create_update(int &$local_graph_id, array $member_graphs, arr (local_graph_id, graph_type_id, consolidation_function_id, text_format, value, hard_return, gprint_id, sequence) VALUES (?, 1, ?, '', '', 'on', 2, ?)", [$local_graph_id, $cf_id, $next_item_sequence++]); + // Fall through: all totals also need the common totalling graph items. case AGGREGATE_TOTAL_ONLY: // use the prefix for totalling GPRINTs as given by the user switch ($_total_type) { diff --git a/lib/utility.php b/lib/utility.php index 49bb515e4..44938da54 100644 --- a/lib/utility.php +++ b/lib/utility.php @@ -2200,8 +2200,10 @@ function memory_bytes(mixed $val) : mixed { switch($last) { case 'g': $val *= 1024; + // Fall through: gigabytes also need the megabyte and kilobyte multipliers. case 'm': $val *= 1024; + // Fall through: megabytes also need the kilobyte multiplier. case 'k': $val *= 1024; } diff --git a/poller_dsstats.php b/poller_dsstats.php index 16ab2d751..b8e5d1890 100644 --- a/poller_dsstats.php +++ b/poller_dsstats.php @@ -85,6 +85,12 @@ if (cacti_sizeof($parms)) { case '--partition': $fpartition = true; + // Preserve the legacy --partition=TYPE spelling for QA callers. + if ($value !== '') { + $type = $value; + } + + break; case '--type': $type = $value; diff --git a/rrdcheck.php b/rrdcheck.php index 8704eeab6..ed7d686aa 100644 --- a/rrdcheck.php +++ b/rrdcheck.php @@ -43,6 +43,7 @@ switch(grv('action')) { rrdcheck_purge(); + // Fall through: display the remaining problems after purging. default: rrdcheck_display_problems(); } diff --git a/tests/Unit/DataCollection/Poller/DsstatsPartitionOptionsTest.php b/tests/Unit/DataCollection/Poller/DsstatsPartitionOptionsTest.php new file mode 100644 index 000000000..5d9e90a17 --- /dev/null +++ b/tests/Unit/DataCollection/Poller/DsstatsPartitionOptionsTest.php @@ -0,0 +1,40 @@ +createForNewestSupportedVersion(); + $nodes = $parser->parse($source); + $block = (new \PhpParser\NodeFinder())->findFirst($nodes, static function ($node) { + return $node instanceof \PhpParser\Node\Stmt\If_ && + $node->cond instanceof \PhpParser\Node\Expr\FuncCall && + $node->cond->name instanceof \PhpParser\Node\Name && + $node->cond->name->toString() === 'cacti_sizeof' && + $node->cond->args[0]->value instanceof \PhpParser\Node\Expr\Variable && + $node->cond->args[0]->value->name === 'parms'; + }); + expect($block)->not->toBeNull(); + $fragment = substr($source, $block->getStartFilePos(), $block->getEndFilePos() - $block->getStartFilePos() + 1); + + // Execute the actual repository argument parser, before DB/poller work. + $parse = static function (array $parms) use ($fragment) : array { + $debug = false; + $force = false; + $fpartition = false; + $type = 'pmaster'; + $thread_id = 0; + eval($fragment); + + return [$type, $fpartition]; + }; + + expect($parse($parameters))->toBe([$expectedType, $expectedPartition]); +})->with([ + 'no partition request' => [[], 'pmaster', false], + 'bare long option' => [['--partition'], 'pmaster', true], + 'bare short option' => [['-p'], 'pmaster', true], + 'type before partition' => [['--type=bmaster', '--partition'], 'bmaster', true], + 'type after partition' => [['--partition', '--type=dmaster'], 'dmaster', true], + 'legacy partition type' => [['--partition=bmaster'], 'bmaster', true], + 'legacy short partition type' => [['-p=dmaster'], 'dmaster', true], +]); diff --git a/tests/Unit/Ui/Graph/ProfileRetentionCellTest.php b/tests/Unit/Ui/Graph/ProfileRetentionCellTest.php new file mode 100644 index 000000000..a81e0954a --- /dev/null +++ b/tests/Unit/Ui/Graph/ProfileRetentionCellTest.php @@ -0,0 +1,42 @@ +createForNewestSupportedVersion(); + $nodes = $parser->parse($source); + $finder = new \PhpParser\NodeFinder(); + $printer = new \PhpParser\PrettyPrinter\Standard(); + $spanFunction = $finder->findFirst($nodes, static fn ($node) => $node instanceof \PhpParser\Node\Stmt\Function_ && $node->name->toString() === 'get_span'); + expect($spanFunction)->not->toBeNull(); + // Bind the actual page-local get_span body as a closure; never shadow core functions. + $spanNode = new \PhpParser\Node\Expr\Closure([ + 'params' => $spanFunction->params, 'stmts' => $spanFunction->stmts, + 'returnType' => $spanFunction->returnType, + ]); + $span = eval('return ' . $printer->prettyPrintExpr($spanNode) . ';'); + $assignment = $finder->findFirst($nodes, static fn ($node) => $node instanceof \PhpParser\Node\Expr\Assign && + $node->var instanceof \PhpParser\Node\Expr\Variable && $node->var->name === 'retention'); + $call = $finder->findFirst($nodes, static function ($node) use ($printer) { + return $node instanceof \PhpParser\Node\Expr\FuncCall && + $node->name instanceof \PhpParser\Node\Name && + $node->name->toString() === 'form_selectable_cell' && + preg_match('/\$(timespans|retention)\b/', $printer->prettyPrintExpr($node->args[0]->value)); + }); + expect($call)->not->toBeNull(); + + // Route only the page-local dependency to its production closure. + foreach ($finder->findInstanceOf($nodes, \PhpParser\Node\Expr\FuncCall::class) as $dependency) { + if ($dependency->name instanceof \PhpParser\Node\Name && $dependency->name->toString() === 'get_span') { + $dependency->name = new \PhpParser\Node\Expr\Variable('span'); + } + } + $rra = ['timespan' => 7200]; + $timespans = $configuredLabel === null ? [] : [7200 => $configuredLabel]; + + if ($assignment !== null) { + eval($printer->prettyPrintExpr($assignment) . ';'); + } + $result = eval('return ' . $printer->prettyPrintExpr($call->args[0]->value) . ';'); + expect($result)->toBe('' . ($configuredLabel ?? '2 Hours') . ''); +})->with(['configured span' => ['Configured span'], 'computed span' => [null]]);