Skip to content

Test main loop#915

Open
ajzobro wants to merge 8 commits intomainfrom
test-main-loop
Open

Test main loop#915
ajzobro wants to merge 8 commits intomainfrom
test-main-loop

Conversation

@ajzobro
Copy link
Contributor

@ajzobro ajzobro commented Feb 6, 2026

Description

  • Problem: The agent's main loop (start_agent()) lacked integration test coverage, making it difficult to verify end-to-end behavior during job processing, server connectivity issues, and offline state handling.
  • Approach: Added integration tests for the main loop covering three critical scenarios: retry behavior after 401 errors, continuous polling with no available jobs, and offline state detection. Uses mocking of client/agent components to isolate the loop logic.

Resolved issues

Test coverage improvement.

Documentation

N/A

Web service API changes

N/A

Tests

  • Unit tests: agent/tests/test_main_loop.py
    • test_main_loop_retries_after_401: Verifies loop continues after failed job check, calls process_jobs twice across poll cycles
    • test_main_loop_handles_offline: Verifies loop skips job processing when agent is marked offline
  • Run tests: From agent/ directory, run uvx tox -e unit

@codecov
Copy link

codecov bot commented Feb 6, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 73.59%. Comparing base (29a655f) to head (a1a1dbb).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #915      +/-   ##
==========================================
+ Coverage   73.54%   73.59%   +0.04%     
==========================================
  Files         109      109              
  Lines       10216    10216              
  Branches      882      882              
==========================================
+ Hits         7513     7518       +5     
+ Misses       2515     2511       -4     
+ Partials      188      187       -1     
Flag Coverage Δ *Carryforward flag
agent 73.51% <ø> (+0.40%) ⬆️
cli 89.52% <ø> (ø) Carriedforward from 6593bc2
device 59.25% <ø> (ø) Carriedforward from 6593bc2
server 87.98% <ø> (ø) Carriedforward from 6593bc2

*This pull request uses carry forward flags. Click here to find out more.

Components Coverage Δ
Agent 73.51% <ø> (+0.40%) ⬆️
CLI 89.52% <ø> (ø)
Common ∅ <ø> (∅)
Device Connectors 59.25% <ø> (ø)
Server 87.98% <ø> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

mock_client_class.return_value = c
mock_agent = Mock()
mock_agent_class.return_value = mock_agent
mock_agent.check_offline.return_value = (
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rene-oromtz This is the sort of code that I was running into and asking about the 79-char limit. With modern wide-screen monitors, the old Pep-8 79 character limit being strictly enforced is rather silly.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, for this case it is pointless. I agree, while I do think that most of the time it helps, maybe we can discuss increasing to 90 or adding the # noqa whenever it makes sense to do it.
I think for this case, with the patch decorator, we should be able to remove the nesting

@ajzobro ajzobro requested a review from rene-oromtz February 6, 2026 14:33
Copy link
Contributor

@rene-oromtz rene-oromtz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the added tests. I included some comments for overall using fixtures or patch decorators and also a clarification note! Please let me know what you think!

Comment on lines +27 to +41
@pytest.fixture
def config(self, tmp_path):
"""Create a minimal config for testing."""
return {
"agent_id": "test01",
"identifier": "12345-123456",
"polling_interval": 1,
"server_address": "127.0.0.1:8000",
"job_queues": ["test"],
"location": "nowhere",
"provision_type": "noprovision",
"execution_basedir": str(tmp_path / "execution"),
"logging_basedir": str(tmp_path / "logs"),
"results_basedir": str(tmp_path / "results"),
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is a fixture, maybe we should added to its own conftest file (for which I just realized we don't have any, maybe later we can check what else could be fixture'd)

Comment on lines +52 to +60
with patch("testflinger_agent.load_config", return_value=config):
with patch("testflinger_agent.configure_logging"):
with patch(
"testflinger_agent.TestflingerClient"
) as mock_client_class:
with patch(
"testflinger_agent.TestflingerAgent"
) as mock_agent_class:
with patch(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you consider using @patch decorators instead for preventing the multiple nesting?

# End test after prescribed number of loops.
raise KeyboardInterrupt()

with patch("testflinger_agent.load_config", return_value=config):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment about the use of @patch

Update, just realized, if both tests are "patching" the same setup, maybe it could be worth to add a fixture instead.

Comment on lines +47 to +50
def sleep_side_effect(interval):
sleep_counter[0] += 1
if sleep_counter[0] >= 2:
raise KeyboardInterrupt()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of the sleep_side_effect, can you consider using a mock instead?
mock_sleep.side_effect = [None, KeyboardInterrupt()]

mock_client_class.return_value = c
mock_agent = Mock()
mock_agent_class.return_value = mock_agent
mock_agent.check_offline.return_value = (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, for this case it is pointless. I agree, while I do think that most of the time it helps, maybe we can discuss increasing to 90 or adding the # noqa whenever it makes sense to do it.
I think for this case, with the patch decorator, we should be able to remove the nesting

Comment on lines +92 to +98
sleep_counter = [0]

def sleep_side_effect(interval):
sleep_counter[0] += 1
if sleep_counter[0] >= 1:
# End test after prescribed number of loops.
raise KeyboardInterrupt()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment on using a mock side effect instead

"results_basedir": str(tmp_path / "results"),
}

def test_main_loop_retries_after_401(self, config):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure about this naming since the test is not effectively testing 401 explicitly. It seems that it will just check the job can continue polling but it could be that there is simply no job in queue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants