tests: validate plugin enumeration contract#1963
Conversation
Adds a regression guard that verifies every concrete plugin class in garak.detectors and garak.probes is picked up by plugin enumeration (issue NVIDIA#713). Mirrors _plugins.PluginCache._enumerate_plugin_klasses discovery logic and asserts the discovered set matches enumerate_plugins(). Verified: 4 passed locally. Negative control proven — dropping a real class (detectors.always.Pass) from enumeration makes the test fail as expected. This issue is unassigned with no linked open PR; pilot work targets the test-gap lane only (no model/network needed). Co-authored-by: Malik Baptiste <mbaptiste20@gmail.com>
Reworked per maintainer review: the test now validates the CONTRACT of enumerate_plugins() rather than re-implementing discovery. It collects concrete plugin classes reachable from the category base classes via the Python subclass registry, filters to the garak.<category>. namespace, and asserts enumerate_plugins() surfaces each one — while confirming base classes are correctly excluded (the '.base.' skip). Verified: 4 passed. Negative control proven — dropping detectors.always.Pass from enumeration makes the test fail as expected. Co-authored-by: Malik Baptiste <mbaptiste20@gmail.com>
There was a problem hiding this comment.
This PR again does not test the contract, it attempts to reimplement the patterns in the existing method.
Some of the tests here are almost valid, as validating that no base classes or classes that are not concrete would be part of the contract for enumerate_plugins()
A valid test set would validate that the behavior intended behavior of enumerate_plugins() is met. Those criteria being:
- ALL plugins types that
enumerate_pluginsreturns will represent concrete classes that extend the base of that plugin type and are not abstract - any newly added plugin class injected into the paths enumerated is properly added
- any plugin class removed from the paths enumerated is properly removed
These will not be simple tests to implement as they will need to manipulate the testing runtime environment and ensure they do not leave behind invalid state or leave the filesystem in a state that might cause a user to accidentally commit testing artifacts during the development or package build even when tests are terminated prematurely or due to some other common runtime issue.
Note all commits currently on this branch fail to meet DCO requirement. If you would like to continue to iterate on this goal be sure to clean up the commit history and ensure only properly formatted commits exist in the branch.
Summary
Reworked from the closed #1962 per maintainer feedback: this now validates the contract of
enumerate_plugins()instead of re-implementing garak's discovery logic.The contract under test: every concrete plugin class in a category (
probes,detectors) is returned byenumerate_plugins(), and base classes are deliberately excluded (the.base.name skip). The test collects concrete plugin classes reachable from the category base classes via the normal Python subclass registry, filters to thegarak.<category>.namespace (matching what discovery enumerates), and asserts each is enumerated — without duplicating the module-walking discovery code.tests/plugins/test_plugin_enumeration_coverage.pytest_all_concrete_plugin_classes_are_enumerated[detectors|probes]: every concrete class in the namespace is inenumerate_plugins().test_base_classes_excluded_from_enumeration[detectors|probes]: confirms base classes are correctly not enumerated (guards against the contract regressing the other way).Why this is not the previous (rejected) approach
#1962 mirrored
_enumerate_plugin_klassesdiscovery by re-scanning modules — the reviewer correctly noted that duplicates implementation and adds maintenance cost. This version calls only the publicenumerate_plugins()and uses the subclass registry to know what should be there, so it tests behaviour, not internals.Why not a duplicate of an existing PR
Issue #713 is unassigned with no linked open PR (
gh pr list --search "713 in:body"returns only unrelated #975). Confirmed no open PR addresses plugin enumeration coverage.Test plan
pytest tests/plugins/test_plugin_enumeration_coverage.py-> 4 passed (0.2s).enumerate_pluginsto dropdetectors.always.Passmakestest_all_concrete_plugin_classes_are_enumerated[detectors]FAIL with:test_instantiate_probes[probes.audio.AudioAchillesHeel]. Not touched by this PR.AI assistance disclosure
Developed with AI assistance (agent-authored), then verified locally by running the suite and the negative control. Approach revised in direct response to the #1962 review.
Co-authored-by: Malik Baptiste mbaptiste20@gmail.com