mirror of
https://github.com/Cacti/plugin_monitor.git
synced 2026-10-23 11:36:46 +09:00
Three follow-ups to the legend-chip redesign (PR #260): (1) legend chips kept overflowing their rounded bounds when space was tight - added min-width:max-content (keeping white-space:nowrap) so each chip is at least as wide as its label and wraps to a new row instead of clipping. (2) device-tile status icons were coloured by the core .deviceXxx classes, which did not match the legend; monitor.css now colours .mon_icon.deviceXxx from the same solid legend palette (with !important to beat the core themes' !important rules), including the Deepness emerald Up. (3) List view now adopts the Cacti deviceStatus pill design: the status cell is wrapped in a .monitorStatus.deviceStatus pill coloured from the legend palette, and the whole-row tint was removed so only the pill is coloured. includes/render.php added to the patch-coverage allowlist (auth-gated render library, not unit-loadable, same as controller.php).
203 lines
6.0 KiB
PHP
203 lines
6.0 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 |
|
|
+-------------------------------------------------------------------------+
|
|
| http://www.cacti.net/ |
|
|
+-------------------------------------------------------------------------+
|
|
*/
|
|
|
|
/*
|
|
* Report line coverage for the lines a branch changes.
|
|
*
|
|
* Whole-file coverage is not a useful gate here: most of the plugin only runs
|
|
* inside a live Cacti, so the repository figure would sit near zero no matter
|
|
* how well a change is tested. What a reviewer wants to know is whether the
|
|
* lines this branch adds are exercised, which is what this measures.
|
|
*
|
|
* Usage: php tests/bin/patch-coverage.php <clover.xml> <base-ref> [min-percent]
|
|
*
|
|
* Exits 1 if coverage is below the threshold, 2 on bad input.
|
|
*/
|
|
|
|
if ($argc < 3) {
|
|
fwrite(STDERR, "usage: patch-coverage.php <clover.xml> <base-ref> [min-percent]\n");
|
|
|
|
exit(2);
|
|
}
|
|
|
|
$clover_path = $argv[1];
|
|
$base_ref = $argv[2];
|
|
$minimum = isset($argv[3]) ? (float) $argv[3] : 100.0;
|
|
|
|
if (!is_readable($clover_path)) {
|
|
fwrite(STDERR, "cannot read coverage report: $clover_path\n");
|
|
|
|
exit(2);
|
|
}
|
|
|
|
/**
|
|
* Line numbers each measured file changed, keyed by repository-relative path.
|
|
*
|
|
* Only added and modified lines count. Deletions have nothing left to cover,
|
|
* and context lines were not part of this change.
|
|
*
|
|
* Paths stay repository-relative so the report can be produced in a container
|
|
* and evaluated on the host, where the absolute paths differ.
|
|
*
|
|
* @param string $base_ref Git ref to diff against.
|
|
*
|
|
* @return array<string, array<int, bool>>
|
|
*/
|
|
function changed_lines($base_ref) {
|
|
$command = 'git diff --no-ext-diff --unified=0 --no-color --diff-filter=AM ' . escapeshellarg($base_ref) . '...HEAD -- "*.php"';
|
|
$output = [];
|
|
$status = 0;
|
|
|
|
$last_line = exec($command, $output, $status);
|
|
|
|
if ($last_line === false || $status !== 0) {
|
|
fwrite(STDERR, "git diff failed\n");
|
|
|
|
exit(2);
|
|
}
|
|
|
|
$diff = implode("\n", $output);
|
|
|
|
$changed = [];
|
|
$file = null;
|
|
|
|
foreach (explode("\n", $diff) as $line) {
|
|
if (strncmp($line, '+++ b/', 6) === 0) {
|
|
$file = substr($line, 6);
|
|
|
|
if (strncmp($file, 'tests/', 6) === 0) {
|
|
$file = null;
|
|
|
|
continue;
|
|
}
|
|
|
|
$changed[$file] = [];
|
|
} elseif (strncmp($line, '@@', 2) === 0 && $file !== null) {
|
|
if (preg_match('/\+(\d+)(?:,(\d+))?/', $line, $match)) {
|
|
$start = (int) $match[1];
|
|
$count = isset($match[2]) ? (int) $match[2] : 1;
|
|
|
|
for ($i = 0; $i < $count; $i++) {
|
|
$changed[$file][$start + $i] = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return $changed;
|
|
}
|
|
|
|
$changed = changed_lines($base_ref);
|
|
$clover = simplexml_load_file($clover_path);
|
|
|
|
if ($clover === false) {
|
|
fwrite(STDERR, "cannot parse coverage report: $clover_path\n");
|
|
|
|
exit(2);
|
|
}
|
|
|
|
$covered = 0;
|
|
$total = 0;
|
|
$missing = [];
|
|
$measured = [];
|
|
|
|
foreach ($clover->xpath('//file') as $file) {
|
|
$path = (string) $file['name'];
|
|
$relative = null;
|
|
|
|
foreach (array_keys($changed) as $candidate) {
|
|
if ($path === $candidate || substr($path, -strlen('/' . $candidate)) === '/' . $candidate) {
|
|
$relative = $candidate;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ($relative === null) {
|
|
continue;
|
|
}
|
|
|
|
$measured[$relative] = true;
|
|
|
|
foreach ($file->line as $line) {
|
|
$number = (int) $line['num'];
|
|
|
|
// Only statement lines are measurable; method markers double-count.
|
|
if ((string) $line['type'] !== 'stmt' || !isset($changed[$relative][$number])) {
|
|
continue;
|
|
}
|
|
|
|
$total++;
|
|
|
|
if ((int) $line['count'] > 0) {
|
|
$covered++;
|
|
} else {
|
|
$missing[] = $relative . ':' . $number;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Production PHP entry points that cannot safely be loaded into the isolated
|
|
* unit process (CLI/daemon/web entry points that chdir + include auth.php,
|
|
* do pcntl signal handling, or execute at the top level) belong here, each
|
|
* with a one-line justification. Keep the exception explicit: any newly
|
|
* changed production PHP file must either appear in Clover or be added here.
|
|
*
|
|
* Empty by default; add entries per repository as the need arises.
|
|
*/
|
|
$unmeasured_allowlist = [
|
|
'includes/database.php', // install/upgrade/drop schema bootstrap; require_once's the live Cacti library, only runs during a real install/upgrade
|
|
'monitor.php', // web UI entry point (chdir + require auth.php); not loadable in the isolated unit process
|
|
'includes/controller.php', // request-controller library included by monitor.php after auth.php; not loadable in the isolated unit process
|
|
'includes/render.php', // host/tile/list render library included by monitor.php after auth.php; not loadable in the isolated unit process
|
|
];
|
|
$unmeasured = array_values(array_diff(array_keys($changed), array_keys($measured)));
|
|
$unexpected_unmeasured = array_values(array_diff($unmeasured, $unmeasured_allowlist));
|
|
|
|
if ($unmeasured !== []) {
|
|
print "Changed production PHP files absent from Clover:\n " . implode("\n ", $unmeasured) . "\n";
|
|
}
|
|
|
|
if ($unexpected_unmeasured !== []) {
|
|
print "FAIL: changed production PHP files are not measured or allowlisted:\n "
|
|
. implode("\n ", $unexpected_unmeasured) . "\n";
|
|
|
|
exit(1);
|
|
}
|
|
|
|
if ($total === 0) {
|
|
print "Patch coverage: no measured lines changed.\n";
|
|
|
|
exit(0);
|
|
}
|
|
|
|
$percent = ($covered / $total) * 100;
|
|
|
|
printf("Patch coverage: %.2f%% (%d/%d lines)\n", $percent, $covered, $total);
|
|
|
|
if ($missing !== []) {
|
|
print "Uncovered changed lines:\n " . implode("\n ", $missing) . "\n";
|
|
}
|
|
|
|
if ($percent + 0.005 < $minimum) {
|
|
printf("FAIL: below the %.2f%% minimum.\n", $minimum);
|
|
|
|
exit(1);
|
|
}
|
|
|
|
exit(0);
|