Bugfix/general - #293
Merged
Merged
Bugfix/general#293
Conversation
alexandraBara
approved these changes
Sep 22, 2026
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.
Summary
I did a scan of potential bugs in the repo. The below fixes are to cleanup various bugs that were found. All new issues generated a new unit tests and then were improved so that test would pass.
1.
Literalfields produced unconstrained CLI argumentsFiles:
typeutils.py,cli/dynamicparserbuilder.pyTests:
test_dynamic_parser_builder.py,test_type_utils.pyprocess_type()reduced any parameterized type to a singleinner_typewithnext(arg for arg in get_args(...)). ForLiteral["fast","slow"]that kept"fast"and dropped the rest, so the allowed-value set was lost before it ever reached the parser. -get_literal_choices()returnedNoneon both branches — it computed a value and then discarded it.add_argument()looked for choices by scanningget_args(annotation)for a nestedLiteral, which only matchesOptional[Literal[...]]; for a bare -Literal[...]the args are the values themselves, so no match. Net effect:--modeaccepted any string and the constraint was enforced later by pydantic, if at all.-
build_type_class()now keeps the full value list forLiteral,get_literal_choices()reads it, andadd_argument()handles a bareLiteralannotation.metavarformatting also assumedstrvalues and would raiseTypeErroron an int-valuedLiteral.2.
Annotatedwas not stripped inside aUnionFiles:
typeutils.pyTests:
test_type_utils.pyprocess_type()unwrappedAnnotatedat the top level only. Inside theUnionbranch,Optional[Annotated[int, "meta"]]yieldedTypeClass(type_class=Annotated, inner_type=int), so every consumer keyed onint/str/listmissed it and fell through to the genericstrdefault.3. File artifacts could be written outside the run directory
Files:
connection/inband/inband.pyTests:
test_file_artifact.pyBoth artifact classes built their target with
os.path.join(log_path, self.filename).os.path.joindiscards the left operand when the right is absolute, so an artifact carrying/tmp/x.txtwrote to/tmp/x.txtand escaped the run directory silently. A filename containing a subdirectory failed differently — the parent was never created, soopen()raisedFileNotFoundError.New
BaseFileArtifact.resolve_write_path()is shared by both subclasses: it drops the path anchor and any../.segments, joins the remainder underlog_path, and creates parents. Nested names are preserved; nothing can resolve outside the log directory.4. Shared hook list leaked log paths between plugins
Files:
interfaces/plugin.py,base/inbandcollectortask.pyTests:
test_plugin_interface.py,test_datacollector.pyPluginInterface.__init__assigned the caller'stask_result_hookslist by reference and then appended to it. When the executor passes one hook list to several plugins, the first plugin'sFileSystemLogHookends up in the shared list; the second plugin's guard testedisinstance(hook, FileSystemLogHook)with no path comparison, matched the first plugin's hook, and skipped adding its own — so it wrote its results into the first plugin's directory. Now the list is copied per plugin and the guard compareslog_base_path.Separately,
InBandDataCollector.__init__did not accept or forwardlog_path, so in-band collectors dropped it entirely.5. Connection-manager cache handed out its internal dict
Files:
pluginregistry.pyTests:
test_connection_manager_entrypoints.pyload_connection_managers_from_entry_points()returned the module-level cache by reference; a caller mutating the returned dict corrupted the cache for the rest of the process. Two of the three return paths now copy.Incomplete: the double-checked return inside the lock (
pluginregistry.py:233) still returns the cache uncopied, which is the path a second thread takes. The copy added at line 228 is redundant —_load_connection_managers_uncached()already returnsmanagers.copy()at line 207.6. JSON/config load failures were swallowed or crashed the run
Files:
configregistry.py,cli/inputargtypes.py,cli/compare_runs.pyTests:
test_config_registry.py,test_input_arg_types.py,test_compare_runs.pyConfigRegistrycaughtValidationError/JSONDecodeErrorandpassed, so a malformed config silently vanished and the run proceeded with an incomplete plugin set. It now raisesRuntimeErrornaming the file.ModelArgHandler.process_file_argpassed whateverjson.loadreturned intoself.model(**data). A JSON array or string produced a bareTypeErrorout of argparse instead of a usable message.arg_check()rejects non-objects withArgumentTypeError._load_plugin_data_from_runcaughtJSONDecodeError/TypeError/OSErrorbut not pydantic'sValidationError, so one malformedresult.jsonaborted the whole comparison. It is now skipped with a warning like the other failures.Call this out in the PR description: the
ConfigRegistrychange is a behavior change, not just a crash fix. Any unrelated.jsonsitting in a config directory that used to be ignored will now fail the run. (This is why the test fixtures moved intofixtures/valid_configs/.) The newraise RuntimeError(...)statements also drop the cause — they should usefrom e.7. Built-in plugin configs were mutated in place
Files:
cli/helper.pyTests:
test_cli_helper.pyget_plugin_configs()appendedbuilt_in_configs[config]by reference, and the caller then mergesglobal_argsinto those objects. The registry's copy of the built-in config carried that mutation forward, so a second config selection in the same process started from dirty state. Now deep-copied on append.Note:
base_configis constructed fresh on line 115, so the[deepcopy(c) for c in [base_config]]on line 120 protects nothing and reads oddly — worth collapsing to a plain[base_config]or[deepcopy(base_config)].8. CLI logging defects
Files:
cli/helper.pyTests:
test_cli_helper.pygenerate_reference_config()calledlogger.warningwith a two-%sformat string and one argument.loggingtraps theTypeErrorinternally and emits--- Logging error ---instead of the warning, so the skip reason was never recorded. Arity is now correct, though the second value is alwaysNoneat that point — the message could be reworded.dump_to_csv()logged"Data written to csv file"after thetry/except, so it reported success even when the write raised and was swallowed. The success log moved inside thetry, withFileNotFoundErrorandValueErrorreported distinctly.9. Subclass validation ran against abstract bases
Files:
interfaces/task.py,interfaces/dataanalyzertask.pyTests:
test_task.py,test_dataanalyzer.pyTask.__init_subclass__raisedTypeErrorwheneverTASK_TYPEwasNone, including for abstract intermediate classes that legitimately leave it unset, and it assumed the attribute exists. Both checks are now gated oninspect.isabstract(cls).DataAnalyzergot the same treatment forDATA_MODEL, plus a check thatanalyze_datais defined and callable.The
DataAnalyzercondition is redundant as written —(not isabstract and not getattr(cls, "DATA_MODEL", None)) or (not isabstract and cls.DATA_MODEL is None); the first clause already covers the second.10. Collector returning no data was reported as success
Files:
interfaces/datacollectortask.pyTests:
test_datacollector.pyThe guard was
if data is None and not result.status.ExecutionStatusis a plainenum.Enum, so every member — includingUNSET— is truthy, andnot result.statusis alwaysFalse. The branch never executed: a collector that returned no data and set no status was finalized as-is rather than being markedEXECUTION_FAILURE. The check is now explicit against{OK, UNSET}, with a default message when the collector supplied none.11. Data-model discovery picked the wrong file
Files:
interfaces/dataplugin.py,models/datamodel.pyTests:
test_dataplugin.py,test_datamodel.py_find_datamodel_path()testedendswith("datamodel.json"),== want_json, andendswith(".log")inside a single pass overos.listdir(). Whichever file the OS listed first won, so a.login the same directory could shadow the real data-model JSON — nondeterministic across machines. Now two ordered passes: model JSON first,.logonly as fallback. The suffix match was also generic; it is now keyed to the model class name.pascal_to_snake()while they are written usingresolve_log_dir_name(), so any plugin with a registered log-dir-name override was looked up under the wrong path and its data never found.DataPlugin.datasetter reported the expected type asself.DATA_MODEL.__class__.__name__, which is the metaclass — every error readexpected ModelMetaclass.DataModel.import_model()calledtarfile.is_tarfile()before theos.path.isdir()check. On a directory that raisesIsADirectoryError, so folder-based import never worked. Order is now isdir → tarfile → JSON file.self.model_fieldsalso moved toself.__class__.model_fields(instance access is deprecated in pydantic 2.11+).Dead code introduced here: the trailing
return cls()inimport_modelis unreachable after the if/elif/else.12. Zero-width regex match hung the analyzer
Files:
base/regexanalyzer.pyTests:
test_regexanalyzer.pycheck_all_regexes()driveswhile search_from <= len(content)and advances withsearch_from = match_obj.end(). For a pattern that can match empty (^,\b,a*),end() == start() == search_from, so the cursor never moves — infinite loop, and in the ungrouped path it appends an event every iteration, so memory grows until the process dies. The cursor is now forced forward one character on a zero-width match.13. Negative MCE bank numbers parsed as ranges
Files:
base/match_ignore.pyTests:
test_match_ignore.pyparse_mce_bank_spec()treated any token containing-as a range, so"-5"split into("", "5")andint("")raised a bareValueErrorwith no context. Empty endpoints and negative end values are now rejected with an explicitInvalid MCE bank rangemessage.14. Sudo password sent without a trailing newline
Files:
connection/inband/inbandremote.pyTests:
test_shellcommand.pyOperator precedence bug. The expression was:
A conditional expression binds looser than
+, so this parses aspassword if password else ("" + "\n"). When a password was actually configured, it was written to stdin with no terminating newline — the remotesudoprompt never saw a completed line and the command blocked until timeout. The newline only appeared in the branch where there was no password. Now parenthesized so"\n"is appended in both cases.15. HTTP-date
Retry-Aftercrashed OEM diagnostic collectionFiles:
connection/redfish/redfish_oem_diag.pyTests:
test_redfish_oem_diag.pyRFC 7231 allows
Retry-Afteras either delta-seconds or an HTTP-date.int(resp.headers.get("Retry-After", 1) or 1)raisesValueErroron the date form, aborting collection against BMCs that use it. Now falls back to the 1-second default.16. Utility fixes
Files:
utils.pyTests:
test_utils.pybytes_to_human_readable()topped out atTB, so petabyte-scale values rendered as1000.0TB. Added aPBtier.find_annotation_in_container()calledissubclass(item, target_type)on everyget_argselement.Literalargs are values, not types, soissubclass("fast", str)raisedTypeError: issubclass() arg 1 must be a class. Non-types are now converted withtype(item)before the check.17.
CollectorArgsconfig key had no effectFiles:
models/collectorargs.pyTests:
test_collectorargs.pymodel_configwas a raw dict containing"exclude_none": True. That is not a pydantic model-config key — it is amodel_dump()argument — so it was inert while looking like it did something. Replaced with a typedConfigDict(extra="forbid"), which also makes the mistake a type error next time.Test plan
pytest test/unitpytest test/functional(if applicable)pre-commit run --all-filesChecklist