Skip to content

Commit eb8ba0a

Browse files
committed
Review comments and extended unit testing
1 parent e8f30d6 commit eb8ba0a

File tree

8 files changed

+164
-112
lines changed

8 files changed

+164
-112
lines changed

lib/pbench/server/api/resources/datasets_compare.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ def _get(
100100
stream_file = {}
101101
for dataset in datasets:
102102
try:
103-
info = cache_m.filestream(dataset.resource_id, "result.csv")
103+
info = cache_m.get_inventory(dataset.resource_id, "result.csv")
104104
file = info["stream"].read().decode("utf-8")
105105
info["stream"].close()
106106
except Exception as e:

lib/pbench/server/api/resources/datasets_inventory.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ def _get(
6161

6262
cache_m = CacheManager(self.config, current_app.logger)
6363
try:
64-
file_info = cache_m.filestream(dataset.resource_id, target)
64+
file_info = cache_m.get_inventory(dataset.resource_id, target)
6565
except TarballNotFound as e:
6666
raise APIAbort(HTTPStatus.NOT_FOUND, str(e))
6767

@@ -89,6 +89,8 @@ def _get(
8989
stream, as_attachment=target is None, download_name=file_info["name"]
9090
)
9191
except Exception as e:
92+
if stream:
93+
stream.close()
9294
raise APIInternalError(
9395
f"Problem sending {dataset}:{target} stream {stream}: {str(e)!r}"
9496
)

lib/pbench/server/api/resources/datasets_visualize.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ def _get(
7070

7171
cache_m = CacheManager(self.config, current_app.logger)
7272
try:
73-
info = cache_m.filestream(dataset.resource_id, "result.csv")
73+
info = cache_m.get_inventory(dataset.resource_id, "result.csv")
7474
file = info["stream"].read().decode("utf-8")
7575
info["stream"].close()
7676
except Exception as e:

lib/pbench/server/cache_manager.py

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from collections import deque
22
from dataclasses import dataclass
33
from enum import auto, Enum
4-
from io import SEEK_SET
54
from logging import Logger
65
from pathlib import Path
76
import shlex
@@ -47,7 +46,7 @@ class CacheExtractBadPath(CacheManagerError):
4746
"""Request to extract a path that's bad or not a file"""
4847

4948
def __init__(self, tar_name: Path, path: Union[str, Path]):
50-
self.name = Dataset.stem(tar_name)
49+
self.name = tar_name.name
5150
self.path = str(path)
5251

5352
def __str__(self) -> str:
@@ -214,27 +213,26 @@ def close(self):
214213

215214
def getbuffer(self):
216215
"""Return the underlying byte buffer (used by send_file)"""
217-
return self.getbuffer()
216+
return self.stream.getbuffer()
218217

219-
def read(self, size: int = -1, /) -> bytes:
218+
def read(self, *args, **kwargs) -> bytes:
220219
"""Encapsulate a read operation"""
221-
return self.stream.read(size)
220+
return self.stream.read(*args, **kwargs)
222221

223222
def readable(self) -> bool:
224223
"""Return the readable state of the stream"""
225224
return self.stream.readable()
226225

227-
def seek(self, offset: int, whence: int = SEEK_SET, /) -> int:
226+
def seek(self, *args, **kwargs) -> int:
228227
"""Allow setting the relative position in the stream"""
229-
return self.stream.seek(offset, whence)
228+
return self.stream.seek(*args, **kwargs)
230229

231-
def __str__(self) -> str:
230+
def __repr__(self) -> str:
232231
"""Return a string representation"""
233-
return f"<Stream {self.stream} from {self.tarfile}"
232+
return f"<Stream {self.stream} from {self.tarfile}>"
234233

235234
def __iter__(self):
236235
"""Allow iterating through lines in the buffer"""
237-
self.stream.seek(0)
238236
return self
239237

240238
def __next__(self):
@@ -537,17 +535,12 @@ def extract(tarball_path: Path, path: Path) -> Inventory:
537535
raise CacheExtractBadPath(tarball_path, path)
538536
return Inventory(stream, tar)
539537

540-
def filestream(self, path: str) -> Optional[JSONOBJECT]:
538+
def get_inventory(self, path: str) -> Optional[JSONOBJECT]:
541539
"""Access the file stream of a tarball member file.
542540
543541
Args:
544542
path: relative path within the tarball of a file
545543
546-
Returns:
547-
An inventory object that mimics an IO[bytes] object while also
548-
maintaining a reference to the tarfile TarFile object to be
549-
closed later.
550-
551544
Returns:
552545
Dictionary with file info and file stream
553546
"""
@@ -574,7 +567,7 @@ def _get_metadata(tarball_path: Path) -> Optional[JSONOBJECT]:
574567
"""
575568
name = Dataset.stem(tarball_path)
576569
try:
577-
data: IO[bytes] = Tarball.extract(tarball_path, f"{name}/metadata.log")
570+
data = Tarball.extract(tarball_path, f"{name}/metadata.log")
578571
except CacheExtractBadPath:
579572
return None
580573
else:
@@ -1119,7 +1112,7 @@ def get_info(self, dataset_id: str, path: Path) -> dict[str, Any]:
11191112
tmap = tarball.get_info(path)
11201113
return tmap
11211114

1122-
def filestream(self, dataset_id: str, target: str) -> Optional[JSONOBJECT]:
1115+
def get_inventory(self, dataset_id: str, target: str) -> Optional[JSONOBJECT]:
11231116
"""Return filestream data for a file within a dataset tarball
11241117
11251118
{
@@ -1136,7 +1129,7 @@ def filestream(self, dataset_id: str, target: str) -> Optional[JSONOBJECT]:
11361129
File info including a byte stream for a regular file
11371130
"""
11381131
tarball = self.find_dataset(dataset_id)
1139-
return tarball.filestream(target)
1132+
return tarball.get_inventory(target)
11401133

11411134
def uncache(self, dataset_id: str):
11421135
"""Remove the unpacked tarball tree.

lib/pbench/test/unit/server/test_cache_manager.py

Lines changed: 99 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from pbench.server.cache_manager import (
1414
BadDirpath,
1515
BadFilename,
16+
CacheExtractBadPath,
1617
CacheManager,
1718
CacheType,
1819
Controller,
@@ -903,35 +904,117 @@ def test_filestream(
903904
tmp_path, "dir_name"
904905
)
905906
tb.cache_map(tar_dir)
906-
file_info = tb.filestream(file_path)
907+
file_info = tb.get_inventory(file_path)
907908
assert file_info["type"] == exp_file_type
908909
assert file_info["stream"].stream == exp_stream
909910

910-
def test_filestream_tarfile_open(self, monkeypatch, tmp_path):
911-
"""Test to check non-existent file or tarfile unpack issue"""
912-
tar = Path("/mock/dir_name.tar.xz")
913-
cache = Path("/mock/.cache")
911+
def test_tarfile_open_fails(self, monkeypatch, tmp_path):
912+
"""Test to check non-existent tarfile"""
913+
tar = Path("/mock/result.tar.xz")
914914

915915
def fake_tarfile_open(self, path):
916916
raise tarfile.TarError("Invalid Tarfile")
917917

918918
with monkeypatch.context() as m:
919-
m.setattr(Tarball, "__init__", TestCacheManager.MockTarball.__init__)
920-
m.setattr(Controller, "__init__", TestCacheManager.MockController.__init__)
921919
m.setattr(tarfile, "open", fake_tarfile_open)
922-
tb = Tarball(tar, Controller(Path("/mock/archive"), cache, None))
923-
tar_dir = TestCacheManager.MockController.generate_test_result_tree(
924-
tmp_path, "dir_name"
925-
)
926-
tb.cache_map(tar_dir)
927920

928-
expected_error_msg = (
929-
f"The dataset tarball named '{tb.tarball_path}' is not found"
930-
)
921+
expected_error_msg = f"The dataset tarball named '{tar}' is not found"
931922
with pytest.raises(TarballNotFound) as exc:
932-
tb.filestream("subdir1/f11.txt")
923+
Tarball.extract(tar, Path("subdir1/f11.txt"))
924+
assert str(exc.value) == expected_error_msg
925+
926+
def test_tarfile_extractfile_fails(self, monkeypatch, tmp_path):
927+
"""Test to check non-existent path in tarfile"""
928+
tar = Path("/mock/result.tar.xz")
929+
path = Path("subdir/f11.txt")
930+
931+
class MockTarFile:
932+
def extractfile(self, path):
933+
raise Exception("Mr Robot refuses trivial human command")
934+
935+
def fake_tarfile_open(self, path):
936+
return MockTarFile()
937+
938+
with monkeypatch.context() as m:
939+
m.setattr(tarfile, "open", fake_tarfile_open)
940+
expected_error_msg = f"Unable to extract {path} from {tar.name}"
941+
with pytest.raises(CacheExtractBadPath) as exc:
942+
Tarball.extract(tar, path)
933943
assert str(exc.value) == expected_error_msg
934944

945+
def test_tarfile_extractfile_notfile(self, monkeypatch, tmp_path):
946+
"""Test to check target that's not a file"""
947+
tar = Path("/mock/result.tar.xz")
948+
path = Path("subdir/f11.txt")
949+
950+
class MockTarFile:
951+
def extractfile(self, path):
952+
return None
953+
954+
def fake_tarfile_open(self, path):
955+
return MockTarFile()
956+
957+
with monkeypatch.context() as m:
958+
m.setattr(tarfile, "open", fake_tarfile_open)
959+
expected_error_msg = f"Unable to extract {path} from {tar.name}"
960+
with pytest.raises(CacheExtractBadPath) as exc:
961+
Tarball.extract(tar, path)
962+
assert str(exc.value) == expected_error_msg
963+
964+
@pytest.mark.parametrize(
965+
"tarball,stream", (("hasmetalog.tar.xz", True), ("nometalog.tar.xz", False))
966+
)
967+
def test_get_metadata(self, monkeypatch, tarball, stream):
968+
"""Verify access and processing of `metadata.log`"""
969+
970+
@staticmethod
971+
def fake_extract(t: Path, f: Path):
972+
if str(t) == tarball:
973+
if str(f) == f"{Dataset.stem(t)}/metadata.log":
974+
if stream:
975+
return io.BytesIO(b"[test]\nfoo = bar\n")
976+
else:
977+
raise CacheExtractBadPath(t, f)
978+
raise Exception(f"Unexpected mock exception with stream:{stream}: {t}, {f}")
979+
980+
with monkeypatch.context() as m:
981+
m.setattr(Tarball, "extract", fake_extract)
982+
metadata = Tarball._get_metadata(Path(tarball))
983+
984+
if stream:
985+
assert metadata == {"test": {"foo": "bar"}}
986+
else:
987+
assert metadata is None
988+
989+
def test_inventory(self):
990+
closed = False
991+
992+
class MockTarFile:
993+
def close(self):
994+
nonlocal closed
995+
closed = True
996+
997+
def __repr__(self) -> str:
998+
return "<Mock tarfile>"
999+
1000+
raw = b"abcde\nfghij\n"
1001+
stream = Inventory(io.BytesIO(raw), MockTarFile())
1002+
assert re.match(
1003+
r"<Stream <_io.BytesIO object at 0x[a-z0-9]+> from <Mock tarfile>",
1004+
str(stream),
1005+
)
1006+
1007+
assert stream.getbuffer() == raw
1008+
assert stream.readable()
1009+
assert stream.read(5) == b"abcde"
1010+
assert stream.read() == b"\nfghij\n"
1011+
assert stream.seek(0) == 0
1012+
assert [b for b in stream] == [b"abcde\n", b"fghij\n"]
1013+
stream.close()
1014+
assert closed
1015+
with pytest.raises(ValueError):
1016+
stream.read()
1017+
9351018
def test_find(
9361019
self, selinux_enabled, server_config, make_logger, tarball, monkeypatch
9371020
):

lib/pbench/test/unit/server/test_datasets_compare.py

Lines changed: 17 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
from pathlib import Path
44
from typing import Any, Optional
55

6-
from pquisby.lib.post_processing import QuisbyProcessing
76
import pytest
87
import requests
98

@@ -56,46 +55,31 @@ def query_api(
5655

5756
return query_api
5857

59-
class MockTarball:
60-
tarball_path = Path("/dataset/tarball.tar.xz")
61-
name = "tarball"
62-
63-
def filestream(self, _path: str) -> dict[str, Any]:
64-
return {"stream": BytesIO(b"CSV_file_as_a_string")}
65-
66-
def mock_find_dataset(self, dataset) -> MockTarball:
67-
# Validate the resource_id
68-
Dataset.query(resource_id=dataset)
69-
return self.MockTarball()
70-
7158
def test_dataset_not_present(self, query_get_as, monkeypatch):
7259
monkeypatch.setattr(Metadata, "getvalue", mock_get_value)
7360

7461
query_get_as(["fio_2"], "drb", HTTPStatus.INTERNAL_SERVER_ERROR)
7562

7663
def test_unsuccessful_get_with_incorrect_data(self, query_get_as, monkeypatch):
77-
def mock_filestream(_path: str) -> dict[str, Any]:
64+
def mock_get_inventory(_self, _dataset: str, _path: str) -> dict[str, Any]:
7865
return {"stream": BytesIO(b"IncorrectData")}
7966

80-
def mock_compare_csv_to_json(
81-
self, benchmark_name, input_type, data_stream
82-
) -> JSON:
83-
return {"status": "failed", "exception": "Unsupported Media Type"}
67+
class MockQuisby:
68+
def compare_csv_to_json(self, _b, _i, _d) -> JSON:
69+
return {"status": "failed", "exception": "Unsupported Media Type"}
8470

85-
monkeypatch.setattr(CacheManager, "find_dataset", self.mock_find_dataset)
86-
monkeypatch.setattr(CacheManager, "filestream", mock_filestream)
71+
monkeypatch.setattr(CacheManager, "get_inventory", mock_get_inventory)
8772
monkeypatch.setattr(Metadata, "getvalue", mock_get_value)
8873
monkeypatch.setattr(
89-
QuisbyProcessing, "compare_csv_to_json", mock_compare_csv_to_json
74+
"pbench.server.api.resources.datasets_compare.QuisbyProcessing", MockQuisby
9075
)
9176
query_get_as(["uperf_1", "uperf_2"], "test", HTTPStatus.INTERNAL_SERVER_ERROR)
9277

9378
def test_tarball_unpack_exception(self, query_get_as, monkeypatch):
94-
def mock_filestream(path: str) -> dict[str, Any]:
95-
raise CacheExtractBadPath(Path("tarball"), path)
79+
def mock_get_inventory(_self, _dataset: str, _path: str) -> dict[str, Any]:
80+
raise CacheExtractBadPath(Path("tarball"), _path)
9681

97-
monkeypatch.setattr(CacheManager, "find_dataset", self.mock_find_dataset)
98-
monkeypatch.setattr(self.MockTarball, "filestream", mock_filestream)
82+
monkeypatch.setattr(CacheManager, "get_inventory", mock_get_inventory)
9983
monkeypatch.setattr(Metadata, "getvalue", mock_get_value)
10084
query_get_as(["uperf_1", "uperf_2"], "test", HTTPStatus.INTERNAL_SERVER_ERROR)
10185

@@ -143,15 +127,17 @@ def mock_filestream(path: str) -> dict[str, Any]:
143127
def test_datasets_with_different_benchmark(
144128
self, user, datasets, exp_status, exp_message, query_get_as, monkeypatch
145129
):
146-
def mock_compare_csv_to_json(
147-
self, benchmark_name, input_type, data_stream
148-
) -> JSON:
149-
return {"status": "success", "json_data": "quisby_data"}
130+
class MockQuisby:
131+
def compare_csv_to_json(self, _b, _i, _d) -> JSON:
132+
return {"status": "success", "json_data": "quisby_data"}
133+
134+
def mock_get_inventory(_self, _dataset: str, _path: str) -> dict[str, Any]:
135+
return {"stream": BytesIO(b"IncorrectData")}
150136

151-
monkeypatch.setattr(CacheManager, "find_dataset", self.mock_find_dataset)
137+
monkeypatch.setattr(CacheManager, "get_inventory", mock_get_inventory)
152138
monkeypatch.setattr(Metadata, "getvalue", mock_get_value)
153139
monkeypatch.setattr(
154-
QuisbyProcessing, "compare_csv_to_json", mock_compare_csv_to_json
140+
"pbench.server.api.resources.datasets_compare.QuisbyProcessing", MockQuisby
155141
)
156142

157143
response = query_get_as(datasets, user, exp_status)

0 commit comments

Comments
 (0)