Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ __pycache__/
*.egg-info/

# Virtual environments
.venv/
.test_venv/
.cvs_venv/
.ruff_venv/
Expand Down
7 changes: 5 additions & 2 deletions cvs/runners/_base_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,11 +184,14 @@ def execute(self, **kwargs) -> RunResult:
try:
# Setup phase
log.info(f"Setting up {self.__class__.__name__}...")
if not self.setup():
setup_ok = self.setup()
# Set even on partial/failed setup so the finally block below still
# tears down resources a partially-successful setup already created.
self._setup_complete = True
if not setup_ok:
return RunResult(
status=RunStatus.FAILED, start_time=start_time, end_time=time.time(), error_message="Setup failed"
)
self._setup_complete = True

# Run phase
log.info(f"Running {self.__class__.__name__}...")
Expand Down
Empty file.
79 changes: 79 additions & 0 deletions cvs/runners/unittests/test_base_runner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
"""
Unit tests for BaseRunner.execute()'s setup -> run -> teardown lifecycle.

Copyright 2025 Advanced Micro Devices, Inc.
All rights reserved.
"""

import unittest

from cvs.runners._base_runner import BaseRunner, RunConfig, RunResult, RunStatus


class _FakeRunner(BaseRunner):
"""Minimal concrete BaseRunner for exercising execute()."""

def __init__(self, config, setup_return=True, run_return=None, run_raises=None):
super().__init__(config)
self._setup_return = setup_return
self._run_return = run_return
self._run_raises = run_raises
self.teardown_calls = 0

def setup(self) -> bool:
return self._setup_return

def run(self, **kwargs) -> RunResult:
if self._run_raises is not None:
raise self._run_raises
return self._run_return

def teardown(self) -> bool:
self.teardown_calls += 1
return True


def _config() -> RunConfig:
return RunConfig(nodes=["10.0.0.1"], username="testuser")


class TestExecuteTeardownLifecycle(unittest.TestCase):
def test_teardown_runs_after_successful_setup_and_run(self):
run_result = RunResult(status=RunStatus.COMPLETED, start_time=0, end_time=1)
runner = _FakeRunner(_config(), setup_return=True, run_return=run_result)

result = runner.execute()

self.assertEqual(result.status, RunStatus.COMPLETED)
self.assertEqual(runner.teardown_calls, 1)

def test_teardown_runs_when_setup_fails(self):
runner = _FakeRunner(_config(), setup_return=False)

result = runner.execute()

self.assertEqual(result.status, RunStatus.FAILED)
self.assertEqual(result.error_message, "Setup failed")
self.assertEqual(runner.teardown_calls, 1)

def test_teardown_runs_when_run_raises(self):
runner = _FakeRunner(_config(), setup_return=True, run_raises=RuntimeError("boom"))

result = runner.execute()

self.assertEqual(result.status, RunStatus.FAILED)
self.assertIn("boom", result.error_message)
self.assertEqual(runner.teardown_calls, 1)

def test_teardown_not_run_before_setup_attempted(self):
runner = _FakeRunner(
_config(), setup_return=True, run_return=RunResult(status=RunStatus.COMPLETED, start_time=0, end_time=1)
)

self.assertFalse(runner._setup_complete)
runner.execute()
self.assertTrue(runner._setup_complete)


if __name__ == "__main__":
unittest.main()
Loading