From 2f082ad5b7b981c01a3cb567b66bfcd84c0486f9 Mon Sep 17 00:00:00 2001 From: Zack Cerza Date: Thu, 18 Jun 2026 15:01:06 -0600 Subject: [PATCH 1/7] TestDevStack: Move create_log_file fixture So that other tests can use it. Signed-off-by: Zack Cerza --- tests/conftest.py | 34 +++++++ .../resources/ceph/test_cephdevstack_core.py | 40 +++----- tests/resources/ceph/test_devstack.py | 99 +++++++------------ 3 files changed, 79 insertions(+), 94 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index d990a375..6dfea423 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,9 @@ +import os +import pathlib import pytest +import random + +from datetime import datetime, timedelta from ceph_devstack import config @@ -7,3 +12,32 @@ def reset_config(): config.load() yield + + +@pytest.fixture(scope="function") +def create_log_file(): + def _create_log_file(data_dir: pathlib.Path, **kwargs) -> pathlib.Path: + parts = { + "timestamp": (datetime.now() - timedelta(days=random.randint(1, 100))), + "test_type": random.choice(["ceph", "rgw", "rbd", "mds"]), + "job_id": random.randint(1, 100), + "content": "some log data", + **kwargs, + } + timestamp = parts["timestamp"].strftime("%Y-%m-%d_%H:%M:%S") + test_type = parts["test_type"] + job_id = parts["job_id"] + content = parts["content"] + + run_name = f"root-{timestamp}-orch:cephadm:{test_type}-small-main-distro-default-testnode" + log_dir = data_dir / "archive" / run_name / str(job_id) + + os.makedirs(log_dir, exist_ok=True) + time_ = parts["timestamp"].timestamp() + os.utime(log_dir, times=(time_, time_)) + log_file = log_dir / "teuthology.log" + log_file.write_text(content) + os.utime(log_file, times=(time_, time_)) + return log_file + + return _create_log_file diff --git a/tests/resources/ceph/test_cephdevstack_core.py b/tests/resources/ceph/test_cephdevstack_core.py index d3b38ed2..cbc82a87 100644 --- a/tests/resources/ceph/test_cephdevstack_core.py +++ b/tests/resources/ceph/test_cephdevstack_core.py @@ -1,3 +1,4 @@ +from datetime import datetime from unittest.mock import AsyncMock, MagicMock, patch import pytest @@ -205,44 +206,27 @@ def test_get_log_file_raises_file_not_found_for_missing_log(self, tmp_path): with pytest.raises(FileNotFoundError): devstack.get_log_file(run_name, "1") - def test_get_log_file_uses_most_recent_when_no_run_name(self, tmp_path): + def test_get_log_file_uses_most_recent_when_no_run_name( + self, tmp_path, create_log_file + ): devstack = CephDevStack() archive_dir = tmp_path / "archive" archive_dir.mkdir() - # Create two runs - older_run = "root-2024-01-01_00:00:00-orch:cephadm:smoke-small-main-distro-default-testnode" - newer_run = "root-2025-01-01_00:00:00-orch:cephadm:smoke-small-main-distro-default-testnode" - - older_dir = archive_dir / older_run - older_dir.mkdir() - older_job = older_dir / "1" - older_job.mkdir() - (older_job / "teuthology.log").write_text("old log") - - newer_dir = archive_dir / newer_run - newer_dir.mkdir() - newer_job = newer_dir / "1" - newer_job.mkdir() - log_file = newer_job / "teuthology.log" - log_file.write_text("new log") - - # Override listdir behavior - def mock_listdir(path): - if str(path) == str(archive_dir): - return [older_run, newer_run] - if str(path) == str(newer_dir): - return ["1"] - return [] + create_log_file( + tmp_path, timestamp=datetime(year=2024, month=1, day=1), content="old log" + ) + new_log_file = create_log_file( + tmp_path, timestamp=datetime(year=2025, month=1, day=1), content="new log" + ) with patch("ceph_devstack.resources.ceph.Teuthology") as MockTeuthology: mock_teuthology = MagicMock() mock_teuthology.archive_dir = archive_dir MockTeuthology.return_value = mock_teuthology - with patch("os.listdir", side_effect=mock_listdir): - result = devstack.get_log_file("", "") - assert str(result) == str(log_file) + result = devstack.get_log_file("", "") + assert str(result) == str(new_log_file) def test_get_log_file_raises_too_many_jobs_when_multiple_and_no_job_id( self, tmp_path diff --git a/tests/resources/ceph/test_devstack.py b/tests/resources/ceph/test_devstack.py index fab7bdfa..11c4b861 100644 --- a/tests/resources/ceph/test_devstack.py +++ b/tests/resources/ceph/test_devstack.py @@ -1,11 +1,11 @@ -import os import io import contextlib -import random as rd -from datetime import datetime, timedelta +import pathlib import secrets import string +from datetime import datetime, timedelta + import pytest from ceph_devstack import config @@ -53,17 +53,15 @@ def test_get_job_id_throws_toomanyjobsfound_on_more_than_one_job(self): async def test_logs_command_display_log_file_of_latest_run( self, tmp_path, create_log_file ): - data_dir = str(tmp_path) - config["data_dir"] = data_dir + config["data_dir"] = str(tmp_path) f = io.StringIO() content = "custom log content" - now = datetime.now().strftime("%Y-%m-%d_%H:%M:%S") - forty_days_ago = (datetime.now() - timedelta(days=40)).strftime( - "%Y-%m-%d_%H:%M:%S" - ) - create_log_file(data_dir, timestamp=now, content=content) - create_log_file(data_dir, timestamp=forty_days_ago) + create_log_file( + tmp_path, + timestamp=datetime.now() - timedelta(days=40), + ) + create_log_file(tmp_path, timestamp=datetime.now(), content=content) with contextlib.redirect_stdout(f): devstack = CephDevStack() @@ -73,15 +71,17 @@ async def test_logs_command_display_log_file_of_latest_run( async def test_logs_display_roughly_contents_of_log_file( self, tmp_path, create_log_file ): - data_dir = str(tmp_path) - config["data_dir"] = data_dir + config["data_dir"] = str(tmp_path) f = io.StringIO() content = "".join( secrets.choice(string.ascii_letters + string.digits) for _ in range(6 * 8 * 1024) ) - now = datetime.now().strftime("%Y-%m-%d_%H:%M:%S") - create_log_file(data_dir, timestamp=now, content=content) + create_log_file( + tmp_path, + timestamp=datetime.now(), + content=content, + ) with contextlib.redirect_stdout(f): devstack = CephDevStack() @@ -91,21 +91,24 @@ async def test_logs_display_roughly_contents_of_log_file( async def test_logs_command_display_log_file_of_given_job_id( self, tmp_path, create_log_file ): - data_dir = str(tmp_path) - config["data_dir"] = data_dir + config["data_dir"] = str(tmp_path) f = io.StringIO() content = "custom log message" - now = datetime.now().strftime("%Y-%m-%d_%H:%M:%S") + now = datetime.now() create_log_file( - data_dir, + tmp_path, timestamp=now, test_type="ceph", job_id="1", content="another log", ) create_log_file( - data_dir, timestamp=now, test_type="ceph", job_id="2", content=content + tmp_path, + timestamp=now, + test_type="ceph", + job_id="2", + content=content, ) with contextlib.redirect_stdout(f): @@ -116,24 +119,18 @@ async def test_logs_command_display_log_file_of_given_job_id( async def test_logs_display_content_of_provided_run_name( self, tmp_path, create_log_file ): - data_dir = str(tmp_path) - config["data_dir"] = data_dir + config["data_dir"] = str(tmp_path) f = io.StringIO() content = "custom content" - now = datetime.now().strftime("%Y-%m-%d_%H:%M:%S") - three_days_ago = (datetime.now() - timedelta(days=3)).strftime( - "%Y-%m-%d_%H:%M:%S" - ) - create_log_file( - data_dir, - timestamp=now, + tmp_path, + timestamp=datetime.now(), ) - run_name = create_log_file( - data_dir, - timestamp=three_days_ago, + run_name: pathlib.Path = create_log_file( + tmp_path, + timestamp=datetime.now() - timedelta(days=3), content=content, - ).split("/")[-3] + ).parent.parent with contextlib.redirect_stdout(f): devstack = CephDevStack() @@ -143,40 +140,10 @@ async def test_logs_display_content_of_provided_run_name( async def test_logs_locate_display_file_path_instead_of_config( self, tmp_path, create_log_file ): - data_dir = str(tmp_path) - - config["data_dir"] = data_dir + config["data_dir"] = str(tmp_path) f = io.StringIO() - log_file = create_log_file(data_dir) + log_file = create_log_file(tmp_path) with contextlib.redirect_stdout(f): devstack = CephDevStack() await devstack.logs(locate=True) - assert log_file in f.getvalue() - - @pytest.fixture(scope="class") - def create_log_file(self): - def _create_log_file(data_dir: str, **kwargs): - parts = { - "timestamp": ( - datetime.now() - timedelta(days=rd.randint(1, 100)) - ).strftime("%Y-%m-%d_%H:%M:%S"), - "test_type": rd.choice(["ceph", "rgw", "rbd", "mds"]), - "job_id": rd.randint(1, 100), - "content": "some log data", - **kwargs, - } - timestamp = parts["timestamp"] - test_type = parts["test_type"] - job_id = parts["job_id"] - content = parts["content"] - - run_name = f"root-{timestamp}-orch:cephadm:{test_type}-small-main-distro-default-testnode" - log_dir = f"{data_dir}/archive/{run_name}/{job_id}" - - os.makedirs(log_dir, exist_ok=True) - log_file = f"{log_dir}/teuthology.log" - with open(log_file, "w") as f: - f.write(content) - return log_file - - return _create_log_file + assert str(log_file) in f.getvalue() From 9b318db31784922b3fa10e6c26323ddb3d40bb8e Mon Sep 17 00:00:00 2001 From: Zack Cerza Date: Thu, 18 Jun 2026 16:09:49 -0600 Subject: [PATCH 2/7] get_log_file: return Path object Signed-off-by: Zack Cerza --- ceph_devstack/resources/ceph/__init__.py | 21 ++++++++++++----- ceph_devstack/resources/ceph/utils.py | 30 ++++++++++-------------- tests/resources/ceph/test_devstack.py | 13 ---------- 3 files changed, 27 insertions(+), 37 deletions(-) diff --git a/ceph_devstack/resources/ceph/__init__.py b/ceph_devstack/resources/ceph/__init__.py index 27ecc5e8..48c46d1b 100644 --- a/ceph_devstack/resources/ceph/__init__.py +++ b/ceph_devstack/resources/ceph/__init__.py @@ -24,7 +24,7 @@ LoopControlDeviceWriteable, SELinuxModule, ) -from ceph_devstack.resources.ceph.utils import get_most_recent_run, get_job_id +from ceph_devstack.resources.ceph.utils import get_runs from ceph_devstack.resources.ceph.exceptions import TooManyJobsFound @@ -250,15 +250,24 @@ async def logs(self, run_name: str = "", job_id: str = "", locate: bool = False) while chunk := f.read(buffer_size): print(chunk, end="") - def get_log_file(self, run_name: str = "", job_id: str = ""): - archive_dir = Teuthology().archive_dir.expanduser() + def get_log_file(self, run_name: str = "", job_id: str = "") -> pathlib.Path: + archive_dir = Teuthology().archive_dir if not run_name: - run_name = get_most_recent_run(os.listdir(archive_dir)) - run_dir = archive_dir.joinpath(run_name) + runs = get_runs(archive_dir) + if not runs: + raise FileNotFoundError + run_dir = runs[0] + else: + run_dir = archive_dir.joinpath(run_name) if not job_id: - job_id = get_job_id(os.listdir(run_dir)) + jobs = sorted( + [dir_.name for dir_ in run_dir.iterdir() if str(dir_.name).isdigit()] + ) + if not jobs: + raise FileNotFoundError + job_id = jobs[0] log_file = run_dir.joinpath(job_id, "teuthology.log") if not log_file.exists(): diff --git a/ceph_devstack/resources/ceph/utils.py b/ceph_devstack/resources/ceph/utils.py index 43834382..3726fef3 100644 --- a/ceph_devstack/resources/ceph/utils.py +++ b/ceph_devstack/resources/ceph/utils.py @@ -1,5 +1,7 @@ +import pathlib import re from datetime import datetime +from typing import List from ceph_devstack.resources.ceph.exceptions import TooManyJobsFound @@ -14,24 +16,16 @@ def get_logtimestamp(dirname: str) -> datetime: return datetime.strptime(match_.group("timestamp"), "%Y-%m-%d_%H:%M:%S") -def get_most_recent_run(runs: list[str]) -> str: - try: - run_name = next( - iter( - sorted( - ( - dirname - for dirname in runs - if RUN_DIRNAME_PATTERN.search(dirname) - ), - key=lambda dirname: get_logtimestamp(dirname), - reverse=True, - ) - ) - ) - return run_name - except StopIteration as e: - raise FileNotFoundError from e +def get_runs(directory: pathlib.Path) -> List[pathlib.Path]: + return sorted( + ( + dir_ + for dir_ in directory.expanduser().absolute().iterdir() + if RUN_DIRNAME_PATTERN.search(dir_.name) + ), + key=lambda dir_: dir_.stat().st_mtime, + reverse=True, + ) def get_job_id(jobs: list[str]): diff --git a/tests/resources/ceph/test_devstack.py b/tests/resources/ceph/test_devstack.py index 11c4b861..018de4cb 100644 --- a/tests/resources/ceph/test_devstack.py +++ b/tests/resources/ceph/test_devstack.py @@ -11,7 +11,6 @@ from ceph_devstack import config from ceph_devstack.resources.ceph.utils import ( get_logtimestamp, - get_most_recent_run, get_job_id, ) from ceph_devstack.resources.ceph.exceptions import TooManyJobsFound @@ -23,18 +22,6 @@ def test_get_logtimestamp(self): dirname = "root-2025-03-20_18:34:43-orch:cephadm:smoke-small-main-distro-default-testnode" assert get_logtimestamp(dirname) == datetime(2025, 3, 20, 18, 34, 43) - def test_get_most_recent_run_returns_most_recent_run(self): - runs = [ - "root-2024-02-07_12:23:43-orch:cephadm:smoke-small-devlop-distro-smithi-testnode", - "root-2025-02-20_11:23:43-orch:cephadm:smoke-small-devlop-distro-smithi-testnode", - "root-2025-03-20_18:34:43-orch:cephadm:smoke-small-main-distro-default-testnode", - "root-2025-01-18_18:34:43-orch:cephadm:smoke-small-main-distro-default-testnode", - ] - assert ( - get_most_recent_run(runs) - == "root-2025-03-20_18:34:43-orch:cephadm:smoke-small-main-distro-default-testnode" - ) - def test_get_job_id_returns_job_on_unique_job(self): jobs = ["97"] assert get_job_id(jobs) == "97" From 7d17efb6d4007b6d6067ce62357e1202070b7230 Mon Sep 17 00:00:00 2001 From: Zack Cerza Date: Thu, 18 Jun 2026 16:10:01 -0600 Subject: [PATCH 3/7] logs: Use ~ for home dir with --locate Signed-off-by: Zack Cerza --- ceph_devstack/resources/ceph/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ceph_devstack/resources/ceph/__init__.py b/ceph_devstack/resources/ceph/__init__.py index 48c46d1b..22bd431e 100644 --- a/ceph_devstack/resources/ceph/__init__.py +++ b/ceph_devstack/resources/ceph/__init__.py @@ -243,7 +243,7 @@ async def logs(self, run_name: str = "", job_id: str = "", locate: bool = False) logger.error(msg) else: if locate: - print(log_file) + print(str(log_file).replace(str(pathlib.Path.home()), "~")) else: buffer_size = 8 * 1024 with open(log_file) as f: From 798712c617b764443a9e9df42f074efdf25da744 Mon Sep 17 00:00:00 2001 From: Zack Cerza Date: Thu, 18 Jun 2026 16:10:15 -0600 Subject: [PATCH 4/7] Teuthology.archive_dir: Set return type Signed-off-by: Zack Cerza --- ceph_devstack/resources/ceph/containers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ceph_devstack/resources/ceph/containers.py b/ceph_devstack/resources/ceph/containers.py index d0332fdd..ae8100bb 100644 --- a/ceph_devstack/resources/ceph/containers.py +++ b/ceph_devstack/resources/ceph/containers.py @@ -411,7 +411,7 @@ def create_cmd(self): } @property - def archive_dir(self): + def archive_dir(self) -> Path: return Path(config["data_dir"]) / "archive" async def create(self): From 130c9b5359de020ed74924d52fb7695905afda4f Mon Sep 17 00:00:00 2001 From: Zack Cerza Date: Wed, 24 Jun 2026 13:26:35 -0600 Subject: [PATCH 5/7] Config.get_value: Don't raise on unknown Signed-off-by: Zack Cerza --- ceph_devstack/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ceph_devstack/__init__.py b/ceph_devstack/__init__.py index 94546468..98c7a289 100644 --- a/ceph_devstack/__init__.py +++ b/ceph_devstack/__init__.py @@ -159,8 +159,8 @@ def get_value(self, name: str) -> str: try: obj = obj[sub_path] except KeyError: - logger.error(f"{name} not found in config") - raise + logger.debug(f"{name} not found in config") + return "" i += 1 if isinstance(obj, (str, int, bool)): return str(obj) From 87d1c41d4a5ec76a12d22b3fe8cf54c7897caf3e Mon Sep 17 00:00:00 2001 From: Zack Cerza Date: Wed, 24 Jun 2026 13:26:46 -0600 Subject: [PATCH 6/7] Config.set_value: return value Signed-off-by: Zack Cerza --- ceph_devstack/__init__.py | 3 ++- tests/test_config.py | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/ceph_devstack/__init__.py b/ceph_devstack/__init__.py index 98c7a289..d71474cd 100644 --- a/ceph_devstack/__init__.py +++ b/ceph_devstack/__init__.py @@ -166,7 +166,7 @@ def get_value(self, name: str) -> str: return str(obj) return tomlkit.dumps(obj).strip() - def set_value(self, name: str, value: str) -> None: + def set_value(self, name: str, value: str) -> str: path = name.split(".") obj = self.user_obj i = 0 @@ -187,6 +187,7 @@ def set_value(self, name: str, value: str) -> None: self.user_path.parent.mkdir(exist_ok=True) self.user_path.write_text(tomlkit.dumps(self.user_obj).strip()) i += 1 + return str(item) def unset_value(self, name: str) -> None: path = name.split(".") diff --git a/tests/test_config.py b/tests/test_config.py index 3abb9d52..5d11c1dc 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -68,6 +68,9 @@ def test_set_value_simple_key(self, test_config): test_config.set_value("test_key", "test_value") assert test_config["test_key"] == "test_value" + def test_set_value_returns_value(self, test_config): + assert test_config.set_value("test_key", "test_value") == "test_value" + def test_set_value_nested_key(self, test_config): test_config.set_value("test_section.test_key", "test_value") assert test_config["test_section"]["test_key"] == "test_value" From 098d236f5c103d3b06eb464b03f3106368b8bfa6 Mon Sep 17 00:00:00 2001 From: Zack Cerza Date: Wed, 24 Jun 2026 17:58:56 -0600 Subject: [PATCH 7/7] logs: Return logs of latest job Signed-off-by: Zack Cerza --- ceph_devstack/resources/ceph/__init__.py | 14 ++------- ceph_devstack/resources/ceph/exceptions.py | 3 -- ceph_devstack/resources/ceph/utils.py | 21 ++++++------- .../resources/ceph/test_cephdevstack_core.py | 31 ++++--------------- tests/resources/ceph/test_devstack.py | 24 ++++++-------- 5 files changed, 29 insertions(+), 64 deletions(-) delete mode 100644 ceph_devstack/resources/ceph/exceptions.py diff --git a/ceph_devstack/resources/ceph/__init__.py b/ceph_devstack/resources/ceph/__init__.py index 22bd431e..436eb3bb 100644 --- a/ceph_devstack/resources/ceph/__init__.py +++ b/ceph_devstack/resources/ceph/__init__.py @@ -24,8 +24,7 @@ LoopControlDeviceWriteable, SELinuxModule, ) -from ceph_devstack.resources.ceph.utils import get_runs -from ceph_devstack.resources.ceph.exceptions import TooManyJobsFound +from ceph_devstack.resources.ceph.utils import get_runs, get_jobs class SSHKeyPair(Secret): @@ -236,11 +235,6 @@ async def logs(self, run_name: str = "", job_id: str = "", locate: bool = False) log_file = self.get_log_file(run_name, job_id) except FileNotFoundError: logger.error("No log file found") - except TooManyJobsFound as e: - msg = "Found too many jobs ({jobs}) for target run. Please pick a job id with -j option.".format( - jobs=", ".join(e.jobs) - ) - logger.error(msg) else: if locate: print(str(log_file).replace(str(pathlib.Path.home()), "~")) @@ -262,12 +256,10 @@ def get_log_file(self, run_name: str = "", job_id: str = "") -> pathlib.Path: run_dir = archive_dir.joinpath(run_name) if not job_id: - jobs = sorted( - [dir_.name for dir_ in run_dir.iterdir() if str(dir_.name).isdigit()] - ) + jobs = get_jobs(run_dir) if not jobs: raise FileNotFoundError - job_id = jobs[0] + job_id = jobs[0].name log_file = run_dir.joinpath(job_id, "teuthology.log") if not log_file.exists(): diff --git a/ceph_devstack/resources/ceph/exceptions.py b/ceph_devstack/resources/ceph/exceptions.py deleted file mode 100644 index c18d4c40..00000000 --- a/ceph_devstack/resources/ceph/exceptions.py +++ /dev/null @@ -1,3 +0,0 @@ -class TooManyJobsFound(Exception): - def __init__(self, jobs: list[str]): - self.jobs = jobs diff --git a/ceph_devstack/resources/ceph/utils.py b/ceph_devstack/resources/ceph/utils.py index 3726fef3..b2b9d585 100644 --- a/ceph_devstack/resources/ceph/utils.py +++ b/ceph_devstack/resources/ceph/utils.py @@ -3,8 +3,6 @@ from datetime import datetime from typing import List -from ceph_devstack.resources.ceph.exceptions import TooManyJobsFound - RUN_DIRNAME_PATTERN = re.compile( r"^(?P^[a-z_]([a-z0-9_-]{0,31}|[a-z0-9_-]{0,30}))-(?P\d{4}-\d{2}-\d{2}_\d{2}:\d{2}:\d{2})" ) @@ -28,12 +26,13 @@ def get_runs(directory: pathlib.Path) -> List[pathlib.Path]: ) -def get_job_id(jobs: list[str]): - job_dir_pattern = re.compile(r"^\d+$") - dirs = [d for d in jobs if job_dir_pattern.match(d)] - - if len(dirs) == 0: - raise FileNotFoundError - elif len(dirs) > 1: - raise TooManyJobsFound(dirs) - return dirs[0] +def get_jobs(directory: pathlib.Path) -> List[pathlib.Path]: + return sorted( + ( + dir_ + for dir_ in directory.expanduser().absolute().iterdir() + if str(dir_.name).isdigit() + ), + key=lambda dir_: dir_.stat().st_mtime, + reverse=True, + ) diff --git a/tests/resources/ceph/test_cephdevstack_core.py b/tests/resources/ceph/test_cephdevstack_core.py index cbc82a87..d30a1625 100644 --- a/tests/resources/ceph/test_cephdevstack_core.py +++ b/tests/resources/ceph/test_cephdevstack_core.py @@ -14,7 +14,6 @@ TestNode as _TestNode, Teuthology, ) -from ceph_devstack.resources.ceph.exceptions import TooManyJobsFound class TestCephDevStackServiceSpecs: @@ -209,26 +208,18 @@ def test_get_log_file_raises_file_not_found_for_missing_log(self, tmp_path): def test_get_log_file_uses_most_recent_when_no_run_name( self, tmp_path, create_log_file ): - devstack = CephDevStack() - archive_dir = tmp_path / "archive" - archive_dir.mkdir() - + config["data_dir"] = str(tmp_path) create_log_file( tmp_path, timestamp=datetime(year=2024, month=1, day=1), content="old log" ) new_log_file = create_log_file( tmp_path, timestamp=datetime(year=2025, month=1, day=1), content="new log" ) + devstack = CephDevStack() + result = devstack.get_log_file("", "") + assert str(result) == str(new_log_file) - with patch("ceph_devstack.resources.ceph.Teuthology") as MockTeuthology: - mock_teuthology = MagicMock() - mock_teuthology.archive_dir = archive_dir - MockTeuthology.return_value = mock_teuthology - - result = devstack.get_log_file("", "") - assert str(result) == str(new_log_file) - - def test_get_log_file_raises_too_many_jobs_when_multiple_and_no_job_id( + def test_get_log_file_returns_latest_job_log_when_multiple_and_no_job_id( self, tmp_path ): devstack = CephDevStack() @@ -253,17 +244,7 @@ def test_get_log_file_raises_too_many_jobs_when_multiple_and_no_job_id( mock_teuthology = MagicMock() mock_teuthology.archive_dir = archive_dir MockTeuthology.return_value = mock_teuthology - - def mock_listdir(path): - if str(path) == str(run_dir): - return ["1", "2"] - return [] - - with ( - patch("os.listdir", side_effect=mock_listdir), - pytest.raises(TooManyJobsFound), - ): - devstack.get_log_file(run_name, "") + assert devstack.get_log_file(run_name, "").parent.name == "2" class TestCephDevStackRemove: diff --git a/tests/resources/ceph/test_devstack.py b/tests/resources/ceph/test_devstack.py index 018de4cb..86630d96 100644 --- a/tests/resources/ceph/test_devstack.py +++ b/tests/resources/ceph/test_devstack.py @@ -11,9 +11,8 @@ from ceph_devstack import config from ceph_devstack.resources.ceph.utils import ( get_logtimestamp, - get_job_id, + get_jobs, ) -from ceph_devstack.resources.ceph.exceptions import TooManyJobsFound from ceph_devstack.resources.ceph import CephDevStack @@ -22,20 +21,17 @@ def test_get_logtimestamp(self): dirname = "root-2025-03-20_18:34:43-orch:cephadm:smoke-small-main-distro-default-testnode" assert get_logtimestamp(dirname) == datetime(2025, 3, 20, 18, 34, 43) - def test_get_job_id_returns_job_on_unique_job(self): - jobs = ["97"] - assert get_job_id(jobs) == "97" + def test_get_jobs_returns_job_on_unique_job(self, tmp_path): + temp_dir_path = pathlib.Path(tmp_path) + job_path = temp_dir_path / "97" + job_path.mkdir() + result = get_jobs(temp_dir_path) + assert len(result) == 1 + assert result[0].name == "97" - def test_get_job_id_throws_filenotfound_on_missing_job(self): - jobs = [] + def test_get_jobs_throws_filenotfound_on_missing_job(self): with pytest.raises(FileNotFoundError): - get_job_id(jobs) - - def test_get_job_id_throws_toomanyjobsfound_on_more_than_one_job(self): - jobs = ["1", "2"] - with pytest.raises(TooManyJobsFound) as exc: - get_job_id(jobs) - assert exc.value.jobs == jobs + get_jobs(pathlib.Path("/fake/path")) async def test_logs_command_display_log_file_of_latest_run( self, tmp_path, create_log_file