feat(navigation): complete autonomous integration tests and goal weighting
This commit is contained in:
@@ -18,3 +18,26 @@ def test_autonomous_goals_config_parsing():
|
||||
goal = brain.get_current_goal(dopamine, mock_configs.args.goals)
|
||||
|
||||
assert goal in mock_configs.args.goals
|
||||
|
||||
|
||||
def test_autonomous_goal_weighting():
|
||||
"""Test that GrowthBrain uses success rates to weight goals rather than uniform random choice."""
|
||||
brain = GrowthBrain(username="test_user")
|
||||
dopamine = MagicMock()
|
||||
dopamine.boredom = 0
|
||||
|
||||
available_goals = ["goal_A", "goal_B", "goal_C"]
|
||||
|
||||
# Simulate that goal_B has been incredibly successful, goal_A moderately, goal_C not at all.
|
||||
success_rates = {"goal_A": 2, "goal_B": 100, "goal_C": 0}
|
||||
|
||||
# If weighting works, running this many times should result in goal_B being chosen overwhelmingly
|
||||
choices = {"goal_A": 0, "goal_B": 0, "goal_C": 0}
|
||||
for _ in range(100):
|
||||
# We pass success_rates to get_current_goal
|
||||
choice = brain.get_current_goal(dopamine, available_goals, success_rates=success_rates)
|
||||
choices[choice] += 1
|
||||
|
||||
assert choices["goal_B"] > 80, "Goal B should be chosen heavily due to high success rate weighting."
|
||||
assert choices["goal_A"] < 20, "Goal A should be chosen rarely."
|
||||
assert choices["goal_A"] > choices["goal_C"], "Goal A should still be chosen more than C."
|
||||
|
||||
85
tests/unit/test_dm_engine_thread_escape.py
Normal file
85
tests/unit/test_dm_engine_thread_escape.py
Normal file
@@ -0,0 +1,85 @@
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from GramAddict.core.dm_engine import _run_zero_latency_dm_loop
|
||||
|
||||
|
||||
@patch("GramAddict.core.llm_provider.query_llm")
|
||||
def test_dm_engine_escapes_thread_without_hardcoded_strings(mock_query_llm):
|
||||
mock_query_llm.return_value = {"response": "Hi!"}
|
||||
"""
|
||||
Test that dm_engine successfully presses 'back' a second time if it is
|
||||
still trapped in a thread, without relying on hardcoded resource-ids.
|
||||
"""
|
||||
mock_device = MagicMock()
|
||||
mock_zero_engine = MagicMock()
|
||||
mock_nav_graph = MagicMock()
|
||||
mock_configs = MagicMock()
|
||||
mock_session_state = MagicMock()
|
||||
|
||||
# Setup cognitive stack
|
||||
mock_telepathic = MagicMock()
|
||||
mock_dopamine = MagicMock()
|
||||
|
||||
mock_cognitive_stack = {"telepathic": mock_telepathic, "dopamine": mock_dopamine}
|
||||
|
||||
# We only want one iteration
|
||||
mock_dopamine.is_app_session_over.side_effect = [False] + [True] * 10
|
||||
mock_dopamine.wants_to_change_feed.return_value = False
|
||||
mock_dopamine.boredom = 0
|
||||
mock_session_state.check_limit.return_value = False
|
||||
|
||||
# Simulate an inbox with one unread thread, and then a valid message to pass the context guard
|
||||
mock_telepathic._extract_semantic_nodes.side_effect = [
|
||||
[{"x": 100, "y": 200, "bounds": "[50,150][150,250]", "semantic": "unread thread"}],
|
||||
[{"x": 100, "y": 200, "bounds": "[50,150][150,250]", "text": "Hello there"}],
|
||||
[{"x": 100, "y": 200, "bounds": "[50,150][150,250]", "semantic": "input field"}],
|
||||
[{"x": 100, "y": 200, "bounds": "[50,150][150,250]", "semantic": "send button"}],
|
||||
]
|
||||
|
||||
# We simulate a "Thread" view XML but WITHOUT the hardcoded instagram IDs
|
||||
# Instead, we give it enough structural info to be parsed as a thread by ScreenIdentity.
|
||||
|
||||
inbox_xml = """
|
||||
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
|
||||
<hierarchy>
|
||||
<node package="com.instagram.android" class="android.widget.FrameLayout" text="" resource-id="com.instagram.android:id/some_new_inbox_container" content-desc="Inbox">
|
||||
<node package="com.instagram.android" class="android.widget.TextView" text="Messages" resource-id="" content-desc="" />
|
||||
<node package="com.instagram.android" class="android.widget.ImageView" text="" resource-id="com.instagram.android:id/direct_tab" selected="true" content-desc="direct" />
|
||||
</node>
|
||||
</hierarchy>
|
||||
"""
|
||||
|
||||
# The thread XML lacks 'direct_thread_header' and 'row_thread_composer_edittext'
|
||||
# but still has message inputs (which ScreenIdentity should use).
|
||||
thread_xml = """
|
||||
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
|
||||
<hierarchy>
|
||||
<node package="com.instagram.android" class="android.widget.FrameLayout" text="">
|
||||
<node package="com.instagram.android" class="android.widget.EditText" text="Message..." resource-id="com.instagram.android:id/some_new_message_input" content-desc="" />
|
||||
<node package="com.instagram.android" class="android.widget.ImageView" text="" resource-id="com.instagram.android:id/some_new_back_button" content-desc="Back" />
|
||||
</node>
|
||||
</hierarchy>
|
||||
"""
|
||||
|
||||
# Sequence of XML dumps:
|
||||
# 1. Main loop (Inbox)
|
||||
# 2. After clicking thread, we check what it is (Thread) -> Wait, telepathic handles replying.
|
||||
# 3. After replying (or skipping), it checks if we are still in thread (Thread XML again).
|
||||
mock_device.dump_hierarchy.side_effect = [inbox_xml] + [thread_xml] * 20
|
||||
|
||||
_run_zero_latency_dm_loop(
|
||||
mock_device,
|
||||
mock_zero_engine,
|
||||
mock_nav_graph,
|
||||
mock_configs,
|
||||
mock_session_state,
|
||||
"MessageInbox",
|
||||
mock_cognitive_stack,
|
||||
)
|
||||
print(f"PRESS CALLS: {mock_device.press.call_args_list}")
|
||||
# The device.press("back") should be called TWICE to escape the thread:
|
||||
# Once at the end of thread processing (line 213).
|
||||
# Once more because we are STILL in the thread (line 222).
|
||||
assert (
|
||||
mock_device.press.call_count == 2
|
||||
), f"Expected 2 presses, got {mock_device.press.call_count}: {mock_device.press.call_args_list}"
|
||||
Reference in New Issue
Block a user