Skip to content

Commit 8a3e71c

Browse files
committed
add unit tests
1 parent 58df8ea commit 8a3e71c

2 files changed

Lines changed: 75 additions & 2 deletions

File tree

agent/src/testflinger_agent/job.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ def wait_for_completion(
289289
while True:
290290
try:
291291
this_job_state = self.client.check_job_state(self.job_id)
292-
if this_job_state in ("complete", "completed", "cancelled"):
292+
if this_job_state in (JobState.COMPLETED, JobState.CANCELLED):
293293
logger.info("This job completed, exiting...")
294294
break
295295

@@ -303,7 +303,6 @@ def wait_for_completion(
303303
parent_job_id
304304
)
305305
if parent_job_state in (
306-
JobState.COMPLETE,
307306
JobState.COMPLETED,
308307
JobState.CANCELLED,
309308
):

agent/tests/test_job.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
import testflinger_agent
1616
from testflinger_agent.client import TestflingerClient as _TestflingerClient
17+
from testflinger_agent.errors import TFServerError
1718
from testflinger_agent.handlers import FileLogHandler
1819
from testflinger_agent.job import (
1920
TestflingerJob as _TestflingerJob,
@@ -245,6 +246,79 @@ def test_allocate_phase_global_timeout_exit_values(
245246
outcome_data["status"]["allocate"] == -signal.SIGKILL.value % 256
246247
)
247248

249+
@patch.object(_TestflingerJob, "allocate_phase")
250+
def test_allocate_phase_normal_completion(
251+
self, mock_allocate, client, tmp_path, requests_mock
252+
):
253+
"""Test allocate phase completes normally without timeout."""
254+
self.config["allocate_command"] = "/bin/true"
255+
fake_job_data = {
256+
"global_timeout": 60,
257+
"allocate_data": {"allocate": True},
258+
}
259+
outcome_file_path = tmp_path / "testflinger-outcome.json"
260+
outcome_file_path.write_text("{}")
261+
262+
requests_mock.post(rmock.ANY, status_code=HTTPStatus.OK)
263+
requests_mock.get(rmock.ANY, status_code=HTTPStatus.OK)
264+
265+
# allocate_phase returns (None, None) on normal completion
266+
mock_allocate.return_value = (None, None)
267+
job = _TestflingerJob(fake_job_data, client)
268+
exitcode, exit_event, exit_reason = job.run_test_phase(
269+
"allocate", tmp_path
270+
)
271+
272+
assert exitcode == 0
273+
assert exit_event != TestEvent.GLOBAL_TIMEOUT
274+
275+
@patch("testflinger_agent.job.time.sleep")
276+
@patch.object(_TestflingerJob, "wait_for_completion")
277+
def test_allocate_phase_post_result_retry(
278+
self, mock_wait, mock_sleep, client, tmp_path, requests_mock
279+
):
280+
"""Test that allocate_phase retries posting device info on failure."""
281+
mock_wait.return_value = (None, None)
282+
requests_mock.post(
283+
rmock.ANY,
284+
[
285+
{"exc": TFServerError(HTTPStatus.INTERNAL_SERVER_ERROR)},
286+
{"status_code": HTTPStatus.OK},
287+
],
288+
)
289+
requests_mock.get(rmock.ANY, status_code=HTTPStatus.OK)
290+
291+
checker = GlobalTimeoutChecker(60)
292+
job = _TestflingerJob({}, client)
293+
job.allocate_phase(tmp_path, checker)
294+
295+
# post_result should have been called twice (one failure + one retry)
296+
assert requests_mock.call_count >= 2
297+
mock_sleep.assert_called_once_with(60)
298+
299+
@pytest.mark.timeout(5)
300+
def test_wait_for_completion_parent_completes(self, client, mocker):
301+
"""Test wait_for_completion exits when the parent job completes."""
302+
parent_job_id = str(uuid.uuid1())
303+
this_job_id = str(uuid.uuid1())
304+
fake_job_data = {"parent_job_id": parent_job_id, "job_id": this_job_id}
305+
job = _TestflingerJob(fake_job_data, client)
306+
307+
# First call checks this job (allocated),
308+
# second call checks parent job (completed)
309+
mocker.patch.object(
310+
client,
311+
"check_job_state",
312+
side_effect=[JobState.ALLOCATED, JobState.COMPLETED],
313+
)
314+
315+
checker = GlobalTimeoutChecker(60)
316+
event, reason = job.wait_for_completion(checker)
317+
318+
# Assert that the exit event and reason indicate normal completion
319+
assert event is None
320+
assert reason is None
321+
248322
def test_get_device_info(self, client, tmp_path):
249323
"""Test job can read from device-info file."""
250324
# Create device-info.json to simulate device-connector

0 commit comments

Comments
 (0)