refactor(thold): lift the evaluation settings out of the threshold check - #800
Open
somethingwithproof wants to merge 6 commits into
Open
refactor(thold): lift the evaluation settings out of the threshold check#800somethingwithproof wants to merge 6 commits into
somethingwithproof wants to merge 6 commits into
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
Refactors the thold_check_threshold() evaluator setup by extracting its configuration/derived-values preamble into a dedicated thold_evaluation_context() helper, and introduces a PHPUnit-based characterization/coverage harness to lock in existing behavior while enabling further safe refactors.
Changes:
- Extracts evaluation settings/derived values into
thold_evaluation_context()and updatesthold_check_threshold()to consume the returned context. - Adds PHPUnit unit/characterization tests plus a Cacti stub framework and scenario/outcome helpers to exercise evaluator behavior without a live Cacti install.
- Adds CI (Docker-based) to run lint, tests with coverage, and enforce changed-lines coverage.
Reviewed changes
Copilot reviewed 17 out of 19 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| thold_functions.php | Adds thold_evaluation_context() and replaces inline preamble in thold_check_threshold() with context unpacking. |
| tests/Unit/ThresholdHiLowCharacterizationTest.php | Characterization tests for the hi/low arm behavior over a single poll. |
| tests/Unit/ThresholdBaselineCharacterizationTest.php | Characterization tests for the baseline arm behavior over a single poll. |
| tests/Unit/ThresholdTimeBasedCharacterizationTest.php | Characterization tests for the time-based arm behavior over a single poll. |
| tests/Unit/TholdEvaluationContextTest.php | Direct unit tests for thold_evaluation_context() behavior (triggers, recipients, graph attachment, trap settings). |
| tests/TestCase.php | PHPUnit base class providing shared reset/setup and plugin loading helpers. |
| tests/Helpers/CactiStubs.php | Programmable/recording stub layer for Cacti global functions used by the plugin. |
| tests/Helpers/ThresholdScenario.php | Builder/driver for running one threshold through one poll of thold_check_threshold(). |
| tests/Helpers/ThresholdOutcome.php | Assertion helper that interprets recorded side effects (mail/log/traps/persisted columns). |
| tests/fixtures/optional-core-functions.php | Fixture for optional core functions to exercise function_exists() branches. |
| tests/fixtures/cacti-lib/variables.php | Minimal file fixture to satisfy runtime includes in plugin code during tests. |
| tests/bootstrap-unit.php | Test bootstrap providing Cacti function shims and global environment expected by the plugin. |
| tests/bin/patch-coverage.php | Computes coverage specifically for added/modified lines versus a base ref. |
| tests/docker/Dockerfile | Dockerized PHPUnit runner pinned to PHP 8.1 with pcov for coverage. |
| tests/docker/docker-compose.yml | Local developer entrypoint to run the same Docker-based test flow as CI. |
| phpunit.xml | PHPUnit configuration targeting unit tests and scoping coverage to thold_functions.php. |
| composer.json | Adds dev dependencies (PHPUnit, phplint) and scripts for lint/test/coverage/docker. |
| .gitignore | Ignores vendor, coverage output, and PHPUnit cache artifacts. |
| .github/workflows/php-unit-tests.yml | GitHub Actions workflow to run lint, tests with coverage, and changed-lines coverage gate in Docker. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+39
to
+45
| if (!function_exists('get_total_row_data')) { | ||
| function get_total_row_data($user_id, $sql, $sql_params = [], $class = '', $timeout = 86400) { | ||
| CactiStub::record('get_total_row_data', $sql, $sql_params); | ||
|
|
||
| return CactiStub::nextReturn('get_total_row_data', 0); | ||
| } | ||
| } |
Comment on lines
+125
to
+129
| if (!function_exists('db_qstr')) { | ||
| function db_qstr($string) { | ||
| return "'" . str_replace("'", "''", (string) $string) . "'"; | ||
| } | ||
| } |
thold_check_threshold() has no return value: everything it decides is a side effect through seven global Cacti functions. ThresholdScenario builds the fixture those need and runs one poll; ThresholdOutcome reads back what was emitted, so a test asserts on behaviour rather than on the SQL text. No production file is touched. Several assertions record behaviour that is wrong rather than intended, each with a comment saying so, so that fixing it later is a deliberate edit here. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Records where this arm has drifted from the hi/low one, notably that a restoral writes the log row and clears the state but sends no mail, so an operator sees the alert and never the all-clear. Cacti's cell fetchers return false rather than '' when a query matches no row. The stub now does the same: on PHP 8 the difference is a TypeError in the re-alert arithmetic, so the old default invented a failure production does not have. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Adopts the conventions from Cacti core: tests/bootstrap-unit.php, tests/Helpers for the stubs, a phpunit.xml carrying error_reporting -1 and CACTI_TEST_BOOTSTRAP, and composer lint / test / test:coverage scripts so CI runs the same commands a developer does. The dev toolchain is Cacti's, pinned to the same platform php 8.1.0. Cacti core runs Pest and this suite does not, because pest ^2 does not resolve on PHP 8.1 -- the platform Cacti's own composer.json pins. Releases up to v2.36.0 conflict with phpunit 10.5.62 and later, every earlier 10.x release is blocked by advisory PKSA-z3gr-8qht-p93v, and v2.36.1, which does resolve, requires PHP 8.2. The stack installs on 8.2 and above; 8.1 is the floor this plugin's CI matrix targets. The tests are written in the plain PHPUnit class style that Cacti's tests/Pest.php explicitly supports, so they run unchanged under Pest wherever it is installable.
This arm compares the reading against statistics rrdtool reports for a reference window, so the scenario supplies those rather than static bounds. Reaching them means answering the three rrdtool calls thold makes -- file existence, an info block describing the data sources and consolidation functions, and a graph command whose printed values are decoded by position -- which the helper now does. Completes the three arms, so Phase 2 can start moving code. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
Each was assigned and never read. One of them, $baseu, ran a query against graph_templates_graph and discarded the result, so every threshold on every poll cycle paid for a round trip that fed nothing. $show_datasource likewise called thold_datasource_required() for a value nobody used. The others are $global_alert_address, $deadnotify (issue Cacti#751), $suffix, $show_units, $units_suffix and $decimals. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
The preamble computed the settings the three threshold arms read, so reaching any of that logic meant driving a whole poll. It moves to thold_evaluation_context(), which derives everything from the threshold row and the Cacti settings and decides nothing, and the check unpacks what it needs. Pure motion. The characterization tests from the previous commits pass unchanged, which is what they were written for, and the rules that were buried in the preamble now have direct tests: trigger fallback, whether alerts also reach the warning recipients, and when a graph is attached. thold_check_threshold() goes from 1432 lines to 1378. Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
somethingwithproof
force-pushed
the
refactor/evaluation-context
branch
from
August 17, 2026 04:41
0169913 to
10ee9c6
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Phase 2 of restructuring
thold_check_threshold().Stacked on #799 — the characterization tests there are the safety net this phase depends on, so it should merge after. Until then this PR's diff shows #799's commits too; once that lands, what remains is two commits against
thold_functions.phpplus one test file.Eight write-only locals
The preamble assigned eight values that nothing ever read. Verified by listing every reference to each inside the function:
Two of them cost real work per evaluation:
$baseuranSELECT base_value FROM graph_templates_graph WHERE local_graph_id = ?and discarded the result. That is one round trip per threshold per poll cycle, feeding nothing.$show_datasourcecalledthold_datasource_required()for a value nobody used.$deadnotifyis the one #751 reports.Lifting the preamble
What remains moves to
thold_evaluation_context(). It derives everything from the threshold row and the Cacti settings, decides nothing, and has no side effects, which is what lets it move without changing behaviour.thold_check_threshold()unpacks the twenty values its arms actually use.thold_check_threshold()goes from 1432 lines to 1378.The real gain is reachability. These rules were only exercisable by driving a whole poll; they now have direct tests:
thold_fail_triggerfalls back to the globalalert_triggerVerification
The 35 characterization tests pass unchanged — no assertion needed editing, which is the check that this phase was pure motion. 15 new tests cover the extracted function directly. 50 total, 100% of the changed lines covered.
composer lintandcomposer testboth clean.Next
Phase 3 extracts the emitter — the subject/syslog/email/command/trap/log/hook block repeated 18 times across the three arms — and is where the divergences behind #742 through #752 get collapsed onto one implementation. Having
$contextas one value to pass is what makes that tractable.