Repository navigation
fix(test): collect two live-service driver suites that pytest refused (#14979) - #15013
Merged
Merged
Conversation
…#14979) SessionTakeoverTestSuite and TestTemporalInvalidation both defined __init__, so pytest declined the class and 18 test_* methods were dead text while the *_test.py name, the collected tree and the clean import all said they ran. Neither actually needed a live service, which is what made them convertible ahead of the rest of the #14979 population: - takeover_manager_e2e_test.py drives WorkflowAutomationManager in-process and TerminalWebSocket through an AsyncMock socket. Its try/except ImportError wrapper is gone (every method opened with a silent return when it tripped), test_chat_integration now mocks the orchestrator instead of asserting "if workflow_id:" against a live planner, and test_command_risk_assessment asserts SecureCommandExecutor.assess_command_risk instead of a copy of the frontend classifier defined inside the test body. The run_demo_workflow driver became test_status_reports_every_step_of_a_multi_step_workflow. - services/temporal_invalidation_test.py drives the service against an in-process fakeredis rule store. test_actual_invalidation_sweep had a hidden ordering dependency on the dry-run test; the fixtures built naive datetimes that make matches_fact raise TypeError; three assertions were conditional on the behaviour they checked; two rule branches tripped on age before the confidence and source-pattern paths were ever read. Every method ended on a return value, which would have moved all 18 into #14920's population on collection; those are gone. Ratchet: autobot-backend 77 -> 59 uncollected test_* methods, lowered in this commit with the reason inline.
Open
4 tasks
Contributor
✅ SSOT Configuration Compliance: Passing🎉 No new hardcoded values of either class — Known backlog in |
Applied Black, isort, and autoflake to match code-quality checks. Triggered by workflow auto-fix-formatting.yml.
5 tasks
) Review of #15013 found four red checks and one dishonest deferral. File-size ratchet. takeover_manager_e2e_test.py shrank 663 -> 411, which is under MAX_LINES (600), so its grandfather entries were DELETED from both scripts/python_file_size_known_large.py and repo_tests/python_file_size_ratchet_baseline.py rather than lowered -- the audit's own message says an entry naming a compliant file exempts nothing while looking authoritative. #14629 turned discovery on for this audit, which is why it surfaced now. The explanatory comment on the 77 -> 59 change grew the uncollected-class guard 921 -> 926, past its recorded ceiling. A grandfathered exemption freezes the size it was granted for, so the comment was condensed back to 921 instead; the same two facts are still there, folded into the paragraph above the dict -- including the correction that 'every one of the remaining offenders is a live-service driver' is no longer true. Stale citation. atomic_facts_extraction_test.py was deferred citing #6994. #6994 closed on 2026-05-06 via #7076, and the TODO(#6994) in that file was planted by that same closing commit, which excluded this work from #6994's scope on its face. Nothing has tracked it since. Filed as #15043 and the comment now points there, saying plainly that self.extraction_agent is never assigned so all 8 methods would raise AttributeError if collected. The dead no-op statement goes with it. No behaviour change; the file's 8 stay inside the ceiling. Decomposition (#560). temporal_invalidation_test.py was rewritten in this PR, so the two functions over the 70-line threshold were split now rather than carried: _setup_test_facts (79) is a table of seven rows plus a loop, and test_invalidation_rule_matching (74) delegates to _assert_rule_matches and _fresh_fact. Longest function in the file is now 31 lines. The three mutations that cover it -- age, confidence and source-pattern rules -- were re-run after the split and all three are still red.
Applied Black, isort, and autoflake to match code-quality checks. Triggered by workflow auto-fix-formatting.yml.
5 tasks
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.
Refs #14979. Partial by design — 18 of a measured 106, with the remaining 88 triaged below rather than swept.
Thinking Path
The number in #14979 is stale. Re-deriving the population with
repo_tests/test_methods_in_uncollected_classes_test.py's own_scan()on this base gives 106 uncollectedtest_*methods in 16 files, not 90 in 14. The extra 16 areagents/atomic_facts_extraction_test.py(8) andservices/temporal_invalidation_test.py(8): both are namedTest*, so #14927's name-based sweep never saw them, and both define__init__, so pytest refuses the class. They only became visible when #14999 made_init_statewalk the MRO. That is the sweep working as designed, and it is why the first move here was to measure rather than to trust the table.The population is not one thing. Sorting the 16 files by what they actually dial:
utils/hardware_metrics_test.pyreads live device stateagents/atomic_facts_extraction_test.pyautobot-infrastructure/autobot-frontendare not intestpathsThe two in-process files are the whole of what is safely convertible today, and they were worth doing precisely because the issue's own analysis assumed all 14 were live-service drivers. They are not:
takeover_manager_e2e_test.pyimportsAsyncMockand issues zero network calls, andtemporal_invalidation_test.pyships its ownMockFactExtractionService. Both were uncollectable for the ordinary reason — a class with__init__— and nothing else.A rename would not have been enough, and the issue is right about that. Each conversion turned up something the driver loop had been hiding, and each of those had to be fixed for the collected method to mean anything:
takeover_manager_e2e_test.pywrapped its imports intry/except ImportErrorand opened all ten methods withif not COMPONENTS_AVAILABLE: return. A broken import produced ten silent passes. The imports are now plain, so an import failure is a collection error with a traceback.test_chat_integrationcalled the realOrchestrator, whose manager-side handler turns any planner error intoreturn None(fix(orchestrator): workflow planning is dead at every caller — un-awaited coroutines plus retired WorkflowTask field names #13730), and then asserted onlyif workflow_id:. It passed whether or not planning worked. The orchestrator is now anAsyncMockand the assertions are unconditional.test_command_risk_assessmentasserted a Python transcription of the frontend'sassessCommandRisk, defined inside the test body — a test asserting its own copy of the logic, which no product change can fail. It now assertsSecureCommandExecutor.assess_command_risk. Retargeting it surfaced a real divergence between the UI gate and the executor, filed as security(terminal): the UI risk gate and the executor disagree on 6 of 8 sampled commands #15011.test_error_handlingwrapped both halves intry/except Exceptionand logged the exception as a success.test_actual_invalidation_sweepdepended onrun_all_testshaving called the dry-run test first against a shared Redis. Run alone it returned"No invalidation rules available"and asserted against the error response.test_invalidation_rule_matchingtripped the age limit before the confidence and source-pattern paths were ever consulted, somin_confidenceandsource_patternswere unexercised. Both mutations came back green until fresh fixtures were added; they are red now.if stats["recent_sweeps"] > 0: assert ...).datetime.now(), which makesInvalidationRule.matches_factraiseTypeErroragainst its timezone-awarenow. The one production producer passes an aware value, so this was the fixture's bug.Skips were considered and then removed. Three takeover tests need the workflow state machine's Redis persistence, and the backend conftest replaces
autobot_shared.redis_clientwith a socket-free stand-in (#14932).require_real_redis_client— the mechanism #14930 established — was the first answer, and it worked: three honest, named skips. It was then replaced with an injected in-processfakeredis, the patternservices/workflow_redis_backfill_test.pyandllm_shared/tests/test_provider_degradation.pyalready use, because a test that runs beats a test that explains why it does not. All 19 methods run for real; none skip. Reaching theNoneTypedereference on the way there is filed as #15012.What was deliberately left, and the citation that was wrong.
agents/atomic_facts_extraction_test.pyis dead in a way a rename would disguise:self.extraction_agentis never assigned, so every one of its eight methods wouldAttributeErroron collection. The first revision of this PR deferred it citing #6994 — which review showed was already closed (2026-05-06, via #7076), and worse, theTODO(#6994)in that file was planted by that same closing commit and says on its face that this work was out of scope for #6994. Nothing had tracked it since May. It is now #15043, and the comment in the file points there and states the actual defect instead of a finished issue. Collecting the file to a red suite says nothing about the code under test, and skipping it would hide a broken test behind a precondition that is not a precondition — the exact defect this umbrella exists to end. It stays uncounted-but-recorded, with its 8 inside the ceiling.What Changed
autobot-backend/takeover_manager_e2e_test.py—SessionTakeoverTestSuite→TestSessionTakeover,__init__→setup_method. TheCOMPONENTS_AVAILABLEwrapper and all ten silent-return guards are gone;run_all_tests,print_test_summaryand the__main__driver give way to pytest. Therun_demo_workflow()showcase, which built a five-step workflow and asserted nothing, becametest_status_reports_every_step_of_a_multi_step_workflow. 10 methods → 11 collected tests, all passing.autobot-backend/services/temporal_invalidation_test.py—__init__→setup_methodwith an injectedfakeredisrule store,run_all_testsandmain()removed, every trailingreturn <value>dropped so none of these enter #14920's population, the naive-datetime fixtures fixed, and the conditional assertions made unconditional. 8 methods → 8 collected tests, all passing.repo_tests/test_methods_in_uncollected_classes_test.py—autobot-backendceiling 77 → 59, with the reason inline. Down only. The other two trees are untouched, and the collectable-test floors are unchanged.The two
print-based driver summaries are gone rather than left behind# noqa: print; nothing else in either file prints.File-size ratchet (from review).
takeover_manager_e2e_test.pyshrank 663 → 411, which is underMAX_LINES(600), so its grandfather entries were deleted fromscripts/python_file_size_known_large.pyandrepo_tests/python_file_size_ratchet_baseline.pyrather than lowered — the audit's own message is that an entry naming a compliant file exempts nothing while looking authoritative. Separately, the explanatory comment on the 77 → 59 change had grown the uncollected-class guard 921 → 926, past its own recorded ceiling; a grandfathered exemption freezes the size it was granted for, so the comment was condensed back to 921 rather than the ceiling raised. Both facts survive, folded into the paragraph above the dict — including its correction, since "every one of the remaining offenders is a live-service driver" is no longer true.Decomposition (#560, from review).
temporal_invalidation_test.pyis rewritten by this PR, so its two functions over the 70-line threshold were split now rather than carried:_setup_test_facts(79 lines) is a seven-row table plus a loop, andtest_invalidation_rule_matching(74) delegates to_assert_rule_matchesand_fresh_fact. Longest function in the file is now 31 lines. Its rule-count floor rose from>= 3to>= 4in the process, since all four rule families are in fact exercised.autobot-backend/agents/atomic_facts_extraction_test.py— comment only, no behaviour change: the staleTODO(#6994)now points at #15043 and says what is actually wrong. The dead no-op statement beforeself.test_contentgoes with it. The file's 8 methods stay inside the ceiling; it is still deferred.Verification
Local, characterising this interpreter only — CI is authoritative. Python 3.10.12, pytest 9.0.2.
The file-size ratchet was red before this fix and is green after: the first revision left
takeover_manager_e2e_test.pypinned at 663 in both lists while the file was 411, and pushed the uncollected-class guard 5 lines past its ceiling. Deleting the two now-compliant entries and condensing the comment takesrepo_tests/python_file_size_ratchet_test.pyfrom 5 failed / 26 passed to 31 passed. Lowering the entry to 411 was tried first and is wrong —test_every_entry_carries_a_ceilingrejects any ceiling at or belowMAX_LINES, andaudit_ceilingsnames the file directly. #14629 turned discovery on for this audit, which is why it surfaced now.The offender count is unchanged by the review fixes: 88 repo-wide, 59 in
autobot-backend, file-by-file identical.The returning-test ratchet is a two-way one: it fails if a count drops below its budget as well as above. It passes unchanged, confirming these 18 conversions neither added a returning test nor moved that population — which is what the #14927 exclusion predicted.
Mutation evidence. Every guarded line was broken, confirmed red, and restored. No mutation is pushed; the tree is clean at
git statusafter each._extract_command_from_stepmaps package-update to a different commandservices/workflow_automation/manager.py:140test_chat_integrationREDservices/workflow_automation/manager.py:105test_chat_integrationREDservices/workflow_automation/models.py:162test_status_reports_every_step_...REDservices/workflow_automation/controller.py:43test_error_handlingREDassess_command_riskreturns SAFE for everythingsecure_command_executor.py:670test_command_risk_assessmentREDis_pausedservices/workflow_automation/controller.py:78test_manual_takeoverREDservices/workflow_automation/controller.py:55test_manual_takeover,test_pause_resume_workflowREDWAITING_APPROVALservices/workflow_automation/executor.py:375test_step_confirmation_flowREDservices/temporal_invalidation_service.py:87test_invalidation_rule_matchingREDservices/temporal_invalidation_service.py:92test_invalidation_rule_matchingREDservices/temporal_invalidation_service.py:98test_invalidation_rule_matchingREDis_contradictory_toalways Falsemodels/atomic_fact.py:173test_contradiction_detectionREDtotal_rulesservices/temporal_invalidation_service.py:909test_invalidation_statisticsREDTwo of those — the confidence and source-pattern rules — came back green on the first attempt, because the fixtures tripped the age limit first. That is what drove the extra fresh-fixture assertions; both are red now. A mutation run that finds nothing is the finding.
All three
temporal_invalidation_service.pymutations (:87age,:92confidence,:98source pattern) were re-run after the decomposition and all three are still red, so extracting_assert_rule_matchesand_fresh_factpreserved the strength rather than diluting it.Ratchet movement.
autobot-backend77 → 59 (−18), derived by the guard's own_scan()before and after. Repo-wide 106 → 88. Ceilings only ever move down; no budget was raised and no count went up. The two file-size entries went to zero by deletion, which is what that ratchet requires once a file is compliant.Model Used
Claude Opus 5 (1M context).
Discovered and filed
security(terminal)— the UI risk gate (TerminalWindow.vue:506) and the executor (secure_command_executor.py:670) disagree on 6 of 8 sampled commands.sudo apt updateislowin the UI, which asks nothing, and HIGH at the executor; a recursive delete prompts the user and is then refused outright. Two hand-maintained classifiers, no shared source.bug(workflow)—state_machine.pydereferences aNoneRedis client at three call sites, so an unreachable store surfaces asAttributeError: 'NoneType' object has no attribute 'set'mid-transition instead of naming the subsystem.bug(test)— the 8 atomic-fact extraction tests cannot run because the agent under test is never constructed. Filed to replace the closed-issue citation this PR originally carried; it records what those 8 actually need (aKnowledgeExtractionAgentconstructible with no live provider, and aMockLLMServicewhose outputextract_facts_from_textcan parse).Deferred, with reasons
autobot-backend/agents/atomic_facts_extraction_test.pyself.extraction_agentnever assigned; tracked by #15043autobot-backend/utils/hardware_metrics_test.pyautobot-backend/comprehensive_system_validation_test.pyautobot-backend/knowledge/chat_knowledge_system_e2e_test.pyautobot-backend/monitoring/monitoring_and_alerts_test.pyautobot-backend/agents/multi_agent_workflow_validation_test.pyautobot-backend/npu_integration_e2e_test.pyautobot-backend/async_baseline_performance_test.pyautobot-backend/knowledge/knowledge_performance_test.pyautobot-infrastructure/shared/scripts/utilities/test_autobot_functionality.pytestpathsautobot-infrastructure/shared/tests/performance/test_performance_optimization.pytestpathsautobot-infrastructure/shared/scripts/analysis/test_npu_worker.pytestpathsautobot-infrastructure/shared/tests/integration/test_distributed_system_integration.pytestpathsautobot-frontend/tests/frontend_comprehensive_corrected_test.pytestpaths;autobot-frontend/is in flight under #14989The 29 methods in
autobot-infrastructureandautobot-frontendneed the tree wired into a pytest invocation before a conversion means anything — #14917's family — as #14979's own acceptance criteria says.Nothing was renamed out of the
test_*namespace, nothing was blanket-skipped, and nothing was deleted to lower a count.