Skip to content

Commit

Permalink
TST: Fix DIT tests
Browse files Browse the repository at this point in the history
  • Loading branch information
zdomke committed Dec 17, 2024
1 parent f709eb9 commit 7820dda
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 88 deletions.
4 changes: 2 additions & 2 deletions trace/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def qapp(qapp_args):


@pytest.fixture
def qtrace(qapp):
def qtrace(qtbot, qapp):
"""Fixture for an instance of the TraceDisplay. Always uses an instance of
PyDMApplication.
Expand All @@ -81,7 +81,7 @@ def qtrace(qapp):

trace.close()
qapp.processEvents()
del trace
trace.deleteLater()


@pytest.fixture
Expand Down
7 changes: 6 additions & 1 deletion trace/tests/test_widgets/test_archive_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@ def search_wid(qapp):
"""
# Set PYDM_ARCHIVER_URL so tests have a predictable state
with patch.dict(os.environ, {"PYDM_ARCHIVER_URL": DUMMY_ARCHIVER_URL}):
yield ArchiveSearchWidget()
asw = ArchiveSearchWidget()
yield asw

asw.close()
qapp.processEvents()
asw.deleteLater()


def create_dummy_reply(data: bytes = b"", error_code=QNetworkReply.NoError):
Expand Down
197 changes: 113 additions & 84 deletions trace/tests/test_widgets/test_data_insight_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@
from qtpy.QtCore import QByteArray
from qtpy.QtNetwork import QNetworkReply

from pydm.widgets.archiver_time_plot import ArchivePlotCurveItem

from main import TraceDisplay
from widgets.data_insight_tool import DataInsightTool
from widgets.data_insight_tool import DataInsightTool, DataVisualizationModel

DUMMY_ARCHIVER_URL = "dummy.archiver.url"

Expand Down Expand Up @@ -49,33 +51,53 @@


@pytest.fixture
def dit_wid(qtrace: TraceDisplay, monkeypatch, get_test_file):
def dit_wid(qtrace: TraceDisplay):
"""Fixture for an instance of the DataInsightTool.
Yields
------
An instance of DataInsightTool.
"""
# Populate the model with data and give the first curve a unit
test_filename = get_test_file("test_file.trc")
test_data = json.loads(test_filename.read_text())["curves"]
# Set PYDM_ARCHIVER_URL so tests have a predictable state
with patch.dict(os.environ, {"PYDM_ARCHIVER_URL": DUMMY_ARCHIVER_URL}):
dit = DataInsightTool(qtrace, qtrace.curves_model, qtrace.ui.main_plot)
yield dit

qtrace.curves_model.set_model_curves(test_data)
curve_0 = qtrace.curves_model.curve_at_index(0)
curve_0.data_buffer = np.array([[6, 7, 8, 9, 10], [100, 101, 102, 103, 104]])
curve_0.units = test_data[0]["yAxisName"]
curve_1 = qtrace.curves_model.curve_at_index(1)
curve_1.data_buffer = np.array([[6, 7, 8, 9, 10], [200, 201, 202, 203, 204]])
dit.close()
dit.deleteLater()

def mock_caget(address: str, *args, **kwargs):
if address.endswith("EGU") or address.endswith(".DESC"):
return address

monkeypatch.setattr(epics, "caget", mock_caget)
@pytest.fixture
def dit_model():
"""Fixture for an instance of the DataVisualizationModel.
# Set PYDM_ARCHIVER_URL so tests have a predictable state
with patch.dict(os.environ, {"PYDM_ARCHIVER_URL": DUMMY_ARCHIVER_URL}):
yield DataInsightTool(qtrace, qtrace.curves_model, qtrace.ui.main_plot)
Yields
------
An instance of DataVisualizationModel.
"""
dvm = DataVisualizationModel()
yield dvm
dvm.deleteLater()


@pytest.fixture
def mock_curve():
"""Fixture for an instance of a mock ArchivePlotCurveItem object with preset
values for all attributes the DataVisualizationModel uses.
Yields
------
An instance of ArchivePlotCurveItem.
"""
curve_item = MagicMock(spec=ArchivePlotCurveItem)
curve_item.address = "KLYS:LI22:31:KVAC"
curve_item.units = "torr"
curve_item.min_x.return_value = 6
curve_item.max_x.return_value = 10
curve_item.getBufferSize.return_value = 5
curve_item.data_buffer = np.array([[6, 7, 8, 9, 10], [100, 101, 102, 103, 104]])

yield curve_item


def create_dummy_reply(data: bytes = b"", error_code=QNetworkReply.NoError):
Expand Down Expand Up @@ -111,42 +133,48 @@ def test_data_visualization_qtmodeltester(qtmodeltester, dit_wid):
qtmodeltester.check(dit_wid.data_vis_model, force_py=True)


@pytest.mark.parametrize(
["curve_ind", "expected_meta"], ((0, "torr, KLYS:LI22:31:KVAC.DESC"), (1, "KLYS:LI22:41:KVAC.DESC"))
)
def test_set_meta_data(dit_wid, curve_ind, expected_meta):
def test_set_meta_data(dit_wid, mock_curve, monkeypatch):
"""Test DataInsightTool.set_meta_data() is successfully called when the selected
PV is changed, and that set_meta_data sets the meta data label to the correct value.
Parameters
----------
dit_wid : fixture
Instance of DataInsightTool for application testing
curve_ind : int
The index to set as the current index of the PV Selection Combobox
expected_meta : str
The expected text for the meta data label
mock_curve : fixture
A mock curve object with preset values for all attributes the DataVisualizationModel uses
monkeypatch : fixture
To override epics.caget
Expectations
------------
DataInsightTool.meta_data_label should contain the expected text for the given index
"""
dit_wid.pv_select_box.setCurrentIndex(curve_ind)
assert dit_wid.meta_data_label.text() == expected_meta

def mock_caget(address: str, *args, **kwargs):
if address.endswith("EGU") or address.endswith(".DESC"):
return address

monkeypatch.setattr(epics, "caget", mock_caget)

dit_wid.data_vis_model.set_all_data(mock_curve, (6, 9))
dit_wid.set_meta_data()

assert dit_wid.meta_data_label.text() == "torr, KLYS:LI22:31:KVAC.DESC"


@pytest.mark.parametrize(
("extension", "expected_data"),
((".csv", CSV_TEST_DATA), (".json", JSON_TEST_DATA)),
)
def test_export_data_success(dit_wid, tmp_path, extension, expected_data):
def test_export_data_success(dit_model, tmp_path, extension, expected_data):
"""Test DataVisualizationModel.export_data() successfully writes a file and it
matches the expected output.
Parameters
----------
dit_wid : fixture
Instance of DataInsightTool for application testing
dit_model : fixture
Instance of DataVisualizationModel for application testing
tmp_path : fixture
A fixture which will provide a temporary directory unique to each test function
extension : str
Expand All @@ -159,27 +187,30 @@ def test_export_data_success(dit_wid, tmp_path, extension, expected_data):
DataInsightTool and DataVisualizationModel will make a file with the expected name and content.
"""
# Construct testcase
model_df = dit_wid.data_vis_model.df
model_df["Datetime"] = [6, 7, 8, 9, 10]
model_df["Datetime"] = model_df["Datetime"].apply(datetime.fromtimestamp, tz=timezone.utc)
model_df["Value"] = [100, 101, 102, 103, 104]
model_df["Severity"] = ["NaN"] * 5
model_df["Source"] = ["Live"] * 5
dit_model.address = "KLYS:LI22:31:KVAC"
dit_model.unit = "torr"
dit_model.description = "KLYS:LI22:31:KVAC.DESC"
dit_model.df["Datetime"] = [6, 7, 8, 9, 10]
dit_model.df["Datetime"] = dit_model.df["Datetime"].apply(datetime.fromtimestamp, tz=timezone.utc)
dit_model.df["Value"] = [100, 101, 102, 103, 104]
dit_model.df["Severity"] = ["NaN"] * 5
dit_model.df["Source"] = ["Live"] * 5

file = tmp_path / f"test_export_data_success{extension}"
dit_wid.data_vis_model.export_data(file, extension)
dit_model.export_data(file, extension)

assert file.is_file()
assert file.read_text() == expected_data


def test_export_data_dir(dit_wid, tmp_path):
def test_export_data_dir(dit_model, tmp_path):
"""Test DataVisualizationModel.export_data() is interrupted when the provided filename
is a directory.
Parameters
----------
dit_wid : fixture
Instance of DataInsightTool for application testing
dit_model : fixture
Instance of DataVisualizationModel for application testing
tmp_path : fixture
A fixture which will provide a temporary directory unique to each test function
Expand All @@ -188,29 +219,28 @@ def test_export_data_dir(dit_wid, tmp_path):
No files/directories should be made and the logger should give a warning.
"""
# Construct testcase
model_df = dit_wid.data_vis_model.df
model_df["Datetime"] = [6, 7, 8, 9, 10]
model_df["Datetime"] = model_df["Datetime"].apply(datetime.fromtimestamp, tz=timezone.utc)
model_df["Value"] = [100, 101, 102, 103, 104]
model_df["Severity"] = ["NaN"] * 5
model_df["Source"] = ["Live"] * 5
dit_model.df["Datetime"] = [6, 7, 8, 9, 10]
dit_model.df["Datetime"] = dit_model.df["Datetime"].apply(datetime.fromtimestamp, tz=timezone.utc)
dit_model.df["Value"] = [100, 101, 102, 103, 104]
dit_model.df["Severity"] = ["NaN"] * 5
dit_model.df["Source"] = ["Live"] * 5

with pytest.raises(IsADirectoryError) as exc_info:
dit_wid.data_vis_model.export_data(tmp_path, "")
dit_model.export_data(tmp_path, "")
assert exc_info.type is IsADirectoryError

assert not tmp_path.is_file()


def test_export_data_invalid_extension(dit_wid, tmp_path):
def test_export_data_invalid_extension(dit_model, tmp_path):
"""Test DataVisualizationModel.export_data() is interrupted when the provided filename
has an extension other than *.csv, *.mat, or *.json . Needed to make a valid file after to prevent
a recursive loop.
Parameters
----------
dit_wid : fixture
Instance of DataInsightTool for application testing
dit_model : fixture
Instance of DataVisualizationModel for application testing
tmp_path : fixture
A fixture which will provide a temporary directory unique to each test function
Expand All @@ -219,64 +249,63 @@ def test_export_data_invalid_extension(dit_wid, tmp_path):
An error should be given when trying to make the *.xlsx file, but *.csv succeeds.
"""
# Construct testcase
model_df = dit_wid.data_vis_model.df
model_df["Datetime"] = [6, 7, 8, 9, 10]
model_df["Datetime"] = model_df["Datetime"].apply(datetime.fromtimestamp, tz=timezone.utc)
model_df["Value"] = [100, 101, 102, 103, 104]
model_df["Severity"] = ["NaN"] * 5
model_df["Source"] = ["Live"] * 5
dit_model.df["Datetime"] = [6, 7, 8, 9, 10]
dit_model.df["Datetime"] = dit_model.df["Datetime"].apply(datetime.fromtimestamp, tz=timezone.utc)
dit_model.df["Value"] = [100, 101, 102, 103, 104]
dit_model.df["Severity"] = ["NaN"] * 5
dit_model.df["Source"] = ["Live"] * 5

file_xlsx = tmp_path / "test_export_save_file_invalid_extension.xlsx"

with pytest.raises(ValueError) as exc_info:
dit_wid.data_vis_model.export_data(file_xlsx, ".xlsx")
dit_model.export_data(file_xlsx, ".xlsx")
assert exc_info.type is ValueError
assert not file_xlsx.is_file()


@pytest.mark.parametrize(
("curve_ind", "x_range", "expected_data"),
("x_range", "expected_data"),
(
(0, (5, 11), ([6, 7, 8, 9, 10], [100, 101, 102, 103, 104])),
(0, (7.5, 9.5), ([8, 9], [102, 103])),
(1, (6.5, 9.5), ([7, 8, 9], [201, 202, 203])),
((5, 11), ([6, 7, 8, 9, 10], [100, 101, 102, 103, 104])),
((7.5, 9.5), ([8, 9], [102, 103])),
),
)
def test_model_set_live_data(dit_wid, curve_ind, x_range, expected_data):
def test_model_set_live_data(dit_model, x_range, expected_data, mock_curve):
"""Test DataVisualizationModel.set_live_data() stores the correct data from
the curves_model into its DataFrame.
Parameters
----------
dit_wid : fixture
Instance of DataInsightTool for application testing
dit_model : fixture
Instance of DataVisualizationModel for application testing
curve_ind : int
The index corresponding to a curve in the curves_model
expected_data : tuple
The expected data to be stored in the Datetime and Value columns of the DataFrame
mock_curve : fixture
A mock curve object with preset values for all attributes the DataVisualizationModel uses
Expectations
------------
DataVisualizationModel.df contains the expected Datetime and Value values for the given curve.
"""
curve_item = dit_wid.curves_model.curve_at_index(curve_ind)
mod = dit_wid.data_vis_model
mod.set_live_data(curve_item, x_range)

assert mod.df["Datetime"].apply(datetime.timestamp).to_list() == expected_data[0]
assert mod.df["Value"].to_list() == expected_data[1]
dit_model.set_live_data(mock_curve, x_range)

assert dit_model.df["Datetime"].apply(datetime.timestamp).to_list() == expected_data[0]
assert dit_model.df["Value"].to_list() == expected_data[1]


def test_model_get_archive_data_success(qtbot, dit_wid, mock_logger):
def test_model_get_archive_data_success(qtbot, dit_model, mock_logger):
"""Test DataVisualizationModel.get_archive_data() stores the correct data from
the Archiver Appliance into its DataFrame.
Parameters
----------
qtbot : fixture
pytest-qt window for widget testing
dit_wid : fixture
Instance of DataInsightTool for application testing
dit_model : fixture
Instance of DataVisualizationModel for application testing
mock_logger : fixture
A fixture used for mocking the logger's warning and error methods
Expand All @@ -286,29 +315,28 @@ def test_model_get_archive_data_success(qtbot, dit_wid, mock_logger):
called, and DataVisualizationModel.df contains the expected Datetime and Value
values for the given curve.
"""
mock_logger.debug.reset_mock()
reply_str = json.dumps(ARCH_TEST_DATA).encode()
reply = create_dummy_reply(data=reply_str)

mod = dit_wid.data_vis_model

with qtbot.waitSignal(mod.reply_recieved, timeout=100):
mod.recieve_archive_reply(reply)
with qtbot.waitSignal(dit_model.reply_recieved, timeout=100):
dit_model.recieve_archive_reply(reply)

mock_logger.debug.assert_not_called()
assert mod.df["Datetime"].apply(datetime.timestamp).to_list() == [1, 2, 3, 4, 5]
assert mod.df["Value"].to_list() == [95, 96, 97, 98, 99]
assert dit_model.df["Datetime"].apply(datetime.timestamp).to_list() == [1, 2, 3, 4, 5]
assert dit_model.df["Value"].to_list() == [95, 96, 97, 98, 99]


def test_model_get_archive_data_error(qtbot, dit_wid, mock_logger):
def test_model_get_archive_data_error(qtbot, dit_model, mock_logger):
"""Test DataVisualizationModel.get_archive_data() does nothing when an error
is recieved from the Archiver Appliance.
Parameters
----------
qtbot : fixture
pytest-qt window for widget testing
dit_wid : fixture
Instance of DataInsightTool for application testing
dit_model : fixture
Instance of DataVisualizationModel for application testing
mock_logger : fixture
A fixture used for mocking the logger's warning and error methods
Expand All @@ -317,8 +345,9 @@ def test_model_get_archive_data_error(qtbot, dit_wid, mock_logger):
DataVisualizationModel.recieve_archive_reply is emitted, logger.debug is called,
and DataVisualizationModel.df does not contain any extra data.
"""
mock_logger.debug.reset_mock()
error_reply = create_dummy_reply(error_code=QNetworkReply.UnknownServerError)

with qtbot.waitSignal(dit_wid.data_vis_model.reply_recieved, timeout=100):
dit_wid.data_vis_model.recieve_archive_reply(error_reply)
with qtbot.waitSignal(dit_model.reply_recieved, timeout=100):
dit_model.recieve_archive_reply(error_reply)
mock_logger.debug.assert_called_once()
Loading

0 comments on commit 7820dda

Please sign in to comment.