diff --git a/src/ansys/aedt/core/visualization/advanced/touchstone_parser.py b/src/ansys/aedt/core/visualization/advanced/touchstone_parser.py index a0d81bec98c..8f46baa50d3 100644 --- a/src/ansys/aedt/core/visualization/advanced/touchstone_parser.py +++ b/src/ansys/aedt/core/visualization/advanced/touchstone_parser.py @@ -29,6 +29,7 @@ import subprocess import warnings +from ansys.aedt.core.aedt_logger import pyaedt_logger as logger from ansys.aedt.core.generic.aedt_versions import aedt_versions from ansys.aedt.core.generic.general_methods import pyaedt_function_handler @@ -67,30 +68,6 @@ keys = {REAL_IMAG: ("real", "imag"), MAG_ANGLE: ("mag", "deg"), DB_ANGLE: ("db20", "deg")} -def _parse_ports_name(file): - """Parse and interpret the option line in the touchstone file. - - Parameters - ---------- - file : str - Path of the touchstone file. - - Returns - ------- - List of str - Names of the ports in the touchstone file. - - """ - portnames = [] - line = file.readline() - while not line.startswith("! Port"): - line = file.readline() - while line.startswith("! Port"): - portnames.append(line.split(" = ")[1].strip()) - line = file.readline() - return portnames - - class TouchstoneData(rf.Network): """Contains data information from Touchstone Read call.""" @@ -180,7 +157,7 @@ def plot_insertion_losses(self, threshold=-3, plot=True): List of tuples representing insertion loss excitations. """ temp_list = self.get_insertion_loss_index(threshold=threshold) - if plot: # pragma: no cover + if plot: for i in temp_list: self.plot_s_db(*i, logx=self.log_x) plt.show() @@ -208,9 +185,9 @@ def plot(self, index_couples=None, show=True): self.plot_s_db(*i, logx=self.log_x) if show: plt.show() - return plt + return True - def plot_return_losses(self): # pragma: no cover + def plot_return_losses(self): """Plot all return losses. Returns @@ -260,6 +237,7 @@ def get_mixed_mode_touchstone_data(self, num_of_diff_ports=None, port_ordering=" ts_diff.renumber(port_order, new_port_order) else: + logger.error("Invalid input provided for 'port_ordering'.") return False ts_diff.se2gmm(num_of_diff_ports) @@ -320,7 +298,7 @@ def get_insertion_loss_index_from_prefix(self, tx_prefix, rx_prefix): receiver_list = [i for i in self.port_names if rx_prefix in i] values = [] if len(trlist) != len(receiver_list): - print("TX and RX should be same length lists") + logger.error("TX and RX should be same length lists.") return False for i, j in zip(trlist, receiver_list): values.append([self.port_names.index(i), self.port_names.index(j)]) @@ -587,12 +565,12 @@ def find_touchstone_files(input_dir): Dictionary with the SNP file names as the key and the absolute path as the value. """ out = {} - if not os.path.exists(input_dir): # pragma: no cover + if not os.path.exists(input_dir): return out pat_snp = re.compile(r"\.s\d+p$", re.IGNORECASE) - sNpFiles = {f: os.path.join(input_dir, f) for f in os.listdir(input_dir) if re.search(pat_snp, f)} + files = {f: os.path.join(input_dir, f) for f in os.listdir(input_dir) if re.search(pat_snp, f)} pat_ts = re.compile("\.ts$") for f in os.listdir(input_dir): if re.search(pat_ts, f): - sNpFiles[f] = os.path.abspath(os.path.join(input_dir, f)) - return sNpFiles + files[f] = os.path.abspath(os.path.join(input_dir, f)) + return files diff --git a/tests/conftest.py b/tests/conftest.py index f23f5a2648e..2daabf60f5b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -45,3 +45,17 @@ def pytest_collection_modifyitems(config: pytest.Config, items: List[pytest.Item item.add_marker(pytest.mark.solvers) elif item.nodeid.startswith(SYSTEM_GENERAL_TEST_PREFIX): item.add_marker(pytest.mark.general) + + +@pytest.fixture +def touchstone_file(tmp_path): + file_path = tmp_path / "dummy.s2p" + file_content = """ +! Terminal data exported +! Port[1] = Port1 +! Port[2] = Port2 +0.1 0.1 0.2 +""" + + file_path.write_text(file_content) + return file_path diff --git a/tests/integration/test_touchstone_data.py b/tests/integration/test_touchstone_data.py new file mode 100644 index 00000000000..336b86bd7e2 --- /dev/null +++ b/tests/integration/test_touchstone_data.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2021 - 2024 ANSYS, Inc. and/or its affiliates. +# SPDX-License-Identifier: MIT +# +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from unittest.mock import patch + +from ansys.aedt.core.visualization.advanced.touchstone_parser import TouchstoneData + + +@patch("ansys.aedt.core.visualization.advanced.touchstone_parser.plt") +def test_plot_insertion_losses(mock_show, touchstone_file): + ts = TouchstoneData(touchstone_file=touchstone_file) + res = ts.plot_insertion_losses() + + assert res is not [] + mock_show.show.assert_called_once() + + +@patch.object(TouchstoneData, "plot_s_db") +@patch("ansys.aedt.core.visualization.advanced.touchstone_parser.plt") +def test_plot(mock_show, mock_plot_s_db, touchstone_file): + ts = TouchstoneData(touchstone_file=touchstone_file) + res = ts.plot(show=True) + + assert res + mock_show.show.assert_called_once() + + +@patch.object(TouchstoneData, "plot_s_db") +@patch("ansys.aedt.core.visualization.advanced.touchstone_parser.plt") +def test_plot_next_xtalk_losses(mock_show, mock_plot_s_db, touchstone_file): + ts = TouchstoneData(touchstone_file=touchstone_file) + res = ts.plot_next_xtalk_losses() + + assert res + mock_show.show.assert_called_once() + + +@patch.object(TouchstoneData, "plot_s_db") +@patch("ansys.aedt.core.visualization.advanced.touchstone_parser.plt") +def test_plot_fext_xtalk_losses(mock_show, mock_plot_s_db, touchstone_file): + ts = TouchstoneData(touchstone_file=touchstone_file) + res = ts.plot_fext_xtalk_losses("Port", "Port") + + assert res + mock_show.show.assert_called_once() diff --git a/tests/system/general/test_44_TouchstoneParser.py b/tests/system/general/test_44_TouchstoneParser.py index 9242fe85fbb..77068edea35 100644 --- a/tests/system/general/test_44_TouchstoneParser.py +++ b/tests/system/general/test_44_TouchstoneParser.py @@ -22,9 +22,13 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +import logging import os from ansys.aedt.core import Hfss3dLayout +from ansys.aedt.core.visualization.advanced.touchstone_parser import TouchstoneData +from ansys.aedt.core.visualization.advanced.touchstone_parser import find_touchstone_files +from mock import patch import pytest from tests import TESTS_GENERAL_PATH @@ -57,7 +61,6 @@ def test_01_get_touchstone_data(self): assert ts_data.get_fext_xtalk_index_from_prefix("diff1", "diff2") def test_02_read_ts_file(self): - from ansys.aedt.core.visualization.advanced.touchstone_parser import TouchstoneData ts1 = TouchstoneData(touchstone_file=os.path.join(test_T44_dir, "port_order_1234.s8p")) assert ts1.get_mixed_mode_touchstone_data() @@ -77,3 +80,57 @@ def test_03_check_touchstone_file(self): assert v[1] elif v and v[0] == "causality": assert not v[1] + + +def test_get_mixed_mode_touchstone_data_failure(touchstone_file, caplog: pytest.LogCaptureFixture): + ts = TouchstoneData(touchstone_file=touchstone_file) + + assert not ts.get_mixed_mode_touchstone_data(port_ordering="12") + assert [ + record + for record in caplog.records + if record.levelno == logging.ERROR and record.message == "Invalid input provided for 'port_ordering'." + ] + + +def test_get_return_loss_index_with_dummy_prefix(touchstone_file): + ts = TouchstoneData(touchstone_file=touchstone_file) + res = ts.get_return_loss_index(excitation_name_prefix="dummy_prefix") + + assert not res + + +def test_get_insertion_loss_index_from_prefix_failure(touchstone_file, caplog: pytest.LogCaptureFixture): + ts = TouchstoneData(touchstone_file=touchstone_file) + res = ts.get_insertion_loss_index_from_prefix("Port", "Dummy") + + assert not res + assert [ + record + for record in caplog.records + if record.levelno == logging.ERROR and record.message == "TX and RX should be same length lists." + ] + + +def test_get_next_xtalk_index_with_dummy_prefix(touchstone_file): + ts = TouchstoneData(touchstone_file=touchstone_file) + res = ts.get_next_xtalk_index("Dummy") + + assert not res + + +@patch("os.path.exists", return_value=False) +def test_find_touchstone_files_with_non_existing_directory(mock_exists): + res = find_touchstone_files("dummy_path") + + assert res == {} + + +@patch("os.path.exists", return_value=True) +@patch("os.listdir") +def test_find_touchstone_files_success(mock_listdir, mock_exists): + mock_listdir.return_value = {"dummy.ts", "dummy.txt"} + res = find_touchstone_files("dummy_path") + + assert "dummy.ts" in res + assert "dummy.txt" not in res