Skip to content

Commit 6f94f25

Browse files
maint: Improve testing
1 parent 9c45b85 commit 6f94f25

File tree

7 files changed

+304
-5
lines changed

7 files changed

+304
-5
lines changed

.github/workflows/cicd.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@ jobs:
6767
run: |
6868
pytest
6969
70+
# - uses: codecov/codecov-action@v5
71+
# name: 'Upload coverage to CodeCov'
72+
# with:
73+
# token: ${{ secrets.CODECOV_TOKEN }}
74+
7075
doc-build:
7176
name: Build documentation
7277
runs-on: ubuntu-latest

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,4 +175,5 @@ cython_debug/
175175
.vscode
176176
._build
177177

178-
doc/source/api
178+
doc/source/api
179+
.cov

pyproject.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,10 @@ Releases = "https://github.com/ansys/ansys-tools-common/releases/"
8080
[tool.flit.module]
8181
name = "ansys.tools.common"
8282

83+
[tool.pytest.ini_options]
84+
addopts = "-ra --cov=ansys.tools.common --cov-report html:.cov/html --cov-report xml:.cov/xml --cov-report term --capture=sys -vv"
85+
86+
8387
[tool.ruff]
8488
line-length = 120
8589
fix = true

src/ansys/tools/common/abstractions/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,4 @@
2222
"""Init module for abstractions."""
2323

2424
from .connection import AbstractGRPCConnection # noqa F401
25-
from .launcher import AbstractServiceLauncher # noqa F401
25+
from .launcher import LauncherProtocol # noqa F401

src/ansys/tools/common/abstractions/connection.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ class AbstractGRPCConnection(ABC):
4747
@abstractmethod
4848
def __init__(self, host: str, port: str) -> None:
4949
"""Initialize the gRPC connection with host and port."""
50+
self.__host = host
51+
self.__port = port
5052
pass
5153

5254
@abstractmethod
@@ -59,21 +61,31 @@ def close(self) -> None:
5961
"""Disconnect from the gRPC server."""
6062
pass
6163

62-
@abstractmethod
6364
@property
65+
@abstractmethod
6466
def service(self):
6567
"""Return the gRPC stub for making requests."""
6668
pass
6769

6870
@property
6971
def _host(self) -> str:
7072
"""Return the host for the gRPC connection."""
71-
return self._host
73+
return self.__host
74+
75+
@_host.setter
76+
def _host(self, value: str) -> None:
77+
"""Set the host for the gRPC connection."""
78+
self.__host = value
7279

7380
@property
7481
def _port(self) -> str:
7582
"""Return the port for the gRPC connection."""
76-
return self._port
83+
return self.__port
84+
85+
@_port.setter
86+
def _port(self, value: str) -> None:
87+
"""Set the port for the gRPC connection."""
88+
self.__port = value
7789

7890
@property
7991
def _channel(self, options: list = None) -> grpc.Channel:

tests/test_connection.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# Copyright (C) 2025 ANSYS, Inc. and/or its affiliates.
2+
# SPDX-License-Identifier: MIT
3+
#
4+
#
5+
# Permission is hereby granted, free of charge, to any person obtaining a copy
6+
# of this software and associated documentation files (the "Software"), to deal
7+
# in the Software without restriction, including without limitation the rights
8+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
# copies of the Software, and to permit persons to whom the Software is
10+
# furnished to do so, subject to the following conditions:
11+
#
12+
# The above copyright notice and this permission notice shall be included in all
13+
# copies or substantial portions of the Software.
14+
#
15+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
# SOFTWARE.
22+
23+
"""Module for testing gRPC connection abstraction."""
24+
25+
from unittest.mock import MagicMock
26+
27+
import pytest
28+
29+
from ansys.tools.common.abstractions.connection import AbstractGRPCConnection
30+
31+
32+
class MockGRPCConnection(AbstractGRPCConnection):
33+
"""Mock implementation of AbstractGRPCConnection for testing."""
34+
35+
def __init__(self, host: str, port: str) -> None:
36+
"""Initialize the mock gRPC connection."""
37+
self._host = host
38+
self._port = port
39+
self._connected = False
40+
41+
def connect(self) -> None:
42+
"""Connect to the mock gRPC server."""
43+
self._connected = True
44+
45+
def close(self) -> None:
46+
"""Close the mock gRPC connection."""
47+
self._connected = False
48+
49+
@property
50+
def service(self):
51+
"""Service property that returns a mock gRPC stub."""
52+
return MagicMock()
53+
54+
@property
55+
def is_closed(self) -> bool:
56+
"""Status of the connection."""
57+
return not self._connected
58+
59+
60+
@pytest.fixture
61+
def mock_connection():
62+
"""Fixture for creating a mock gRPC connection."""
63+
return MockGRPCConnection(host="localhost", port="50051")
64+
65+
66+
def test_initialization(mock_connection):
67+
"""Test initialization of the connection."""
68+
assert mock_connection._host == "localhost"
69+
assert mock_connection._port == "50051"
70+
assert mock_connection.is_closed
71+
72+
73+
def test_connect(mock_connection):
74+
"""Test connecting to the gRPC server."""
75+
mock_connection.connect()
76+
assert not mock_connection.is_closed
77+
78+
79+
def test_close(mock_connection):
80+
"""Test disconnecting from the gRPC server."""
81+
mock_connection.connect()
82+
mock_connection.close()
83+
assert mock_connection.is_closed
84+
85+
86+
def test_service_property(mock_connection):
87+
"""Test the service property."""
88+
service = mock_connection.service
89+
assert service is not None
90+
assert isinstance(service, MagicMock)
91+
92+
93+
def test_is_closed_property(mock_connection):
94+
"""Test the is_closed property."""
95+
assert mock_connection.is_closed
96+
mock_connection.connect()
97+
assert not mock_connection.is_closed
98+
mock_connection.close()
99+
assert mock_connection.is_closed

tests/test_logger.py

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
# Copyright (C) 2025 ANSYS, Inc. and/or its affiliates.
2+
# SPDX-License-Identifier: MIT
3+
#
4+
#
5+
# Permission is hereby granted, free of charge, to any person obtaining a copy
6+
# of this software and associated documentation files (the "Software"), to deal
7+
# in the Software without restriction, including without limitation the rights
8+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
# copies of the Software, and to permit persons to whom the Software is
10+
# furnished to do so, subject to the following conditions:
11+
#
12+
# The above copyright notice and this permission notice shall be included in all
13+
# copies or substantial portions of the Software.
14+
#
15+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
# SOFTWARE.
22+
"""Module for testing the logger."""
23+
24+
import logging
25+
26+
import pytest
27+
28+
from ansys.tools.common.logger import LOGGER, CustomFormatter, Logger
29+
30+
31+
def test_logger_singleton():
32+
"""Test that Logger is a singleton."""
33+
another_instance = Logger(level=logging.INFO, logger_name="AnotherLogger")
34+
assert LOGGER is another_instance
35+
36+
37+
def test_logger_name():
38+
"""Test the name of the logger."""
39+
assert LOGGER.get_logger().name == "Logger"
40+
41+
42+
def test_logger_level():
43+
"""Test setting and getting the logger level."""
44+
LOGGER.set_level(logging.WARNING)
45+
assert LOGGER.get_logger().level == logging.WARNING
46+
47+
48+
def test_logger_enable_output(capsys):
49+
"""Test enabling logger output to a stream."""
50+
LOGGER.enable_output()
51+
LOGGER.set_level(logging.DEBUG) # Set the logger to DEBUG level for testing
52+
LOGGER.info("Test message")
53+
captured = capsys.readouterr()
54+
assert "Test message" in captured.err
55+
56+
57+
def test_logger_file_handler(tmp_path):
58+
"""Test adding a file handler to the logger."""
59+
log_dir = tmp_path / "logs"
60+
LOGGER.add_file_handler(log_dir)
61+
LOGGER.set_level(logging.DEBUG) # Set the logger to DEBUG level for testing
62+
LOGGER.info("Test message in file handler")
63+
log_file = next(log_dir.glob("log_*.log"))
64+
assert log_file.exists()
65+
with log_file.open() as f:
66+
content = f.read()
67+
assert "Timestamp" in content
68+
69+
70+
def test_custom_formatter_truncation():
71+
"""Test truncation of module and function names in CustomFormatter."""
72+
formatter = CustomFormatter("%(module)s | %(funcName)s")
73+
assert formatter.max_column_width == 15 # Default width
74+
formatter.set_column_width(10)
75+
record = logging.LogRecord(
76+
name="test",
77+
level=logging.INFO,
78+
pathname="test_path",
79+
lineno=1,
80+
msg="Test message",
81+
args=None,
82+
exc_info=None,
83+
)
84+
record.module = "very_long_module_name"
85+
record.funcName = "very_long_function_name"
86+
formatted_message = formatter.format(record)
87+
assert "very_lo..." in formatted_message
88+
assert "very_lo..." in formatted_message
89+
90+
91+
def test_custom_formatter_column_width():
92+
"""Test setting and getting column width in CustomFormatter."""
93+
formatter = CustomFormatter("%(module)s | %(funcName)s")
94+
formatter.set_column_width(12)
95+
assert formatter.max_column_width == 12
96+
97+
with pytest.raises(ValueError):
98+
formatter.set_column_width(5)
99+
100+
101+
def test_logger_debug(capsys):
102+
"""Test the debug method."""
103+
LOGGER.enable_output()
104+
LOGGER.set_level(logging.DEBUG) # Set the logger to DEBUG level for testing
105+
LOGGER.debug("Debug message")
106+
captured = capsys.readouterr()
107+
assert "DEBUG" in captured.err
108+
assert "Debug message" in captured.err
109+
110+
111+
def test_logger_info(capsys):
112+
"""Test the info method."""
113+
LOGGER.enable_output()
114+
LOGGER.set_level(logging.INFO) # Set the logger to DEBUG level for testing
115+
LOGGER.info("Debug message")
116+
captured = capsys.readouterr()
117+
assert "INFO" in captured.err
118+
assert "Debug message" in captured.err
119+
120+
121+
def test_logger_warn(capsys):
122+
"""Test the warn method."""
123+
LOGGER.enable_output()
124+
LOGGER.set_level(logging.WARN) # Set the logger to DEBUG level for testing
125+
LOGGER.warn("Debug message")
126+
captured = capsys.readouterr()
127+
assert "WARN" in captured.err
128+
assert "Debug message" in captured.err
129+
130+
131+
def test_logger_warning(capsys):
132+
"""Test the warning method."""
133+
LOGGER.enable_output()
134+
LOGGER.set_level(logging.WARNING) # Set the logger to DEBUG level for testing
135+
LOGGER.warning("Debug message")
136+
captured = capsys.readouterr()
137+
assert "WARNING" in captured.err
138+
assert "Debug message" in captured.err
139+
140+
141+
def test_logger_error(capsys):
142+
"""Test the error method."""
143+
LOGGER.enable_output()
144+
LOGGER.set_level(logging.ERROR) # Set the logger to DEBUG level for testing
145+
LOGGER.error("Debug message")
146+
captured = capsys.readouterr()
147+
assert "ERROR" in captured.err
148+
assert "Debug message" in captured.err
149+
150+
151+
def test_logger_critical(capsys):
152+
"""Test the critical method."""
153+
LOGGER.enable_output()
154+
LOGGER.set_level(logging.CRITICAL) # Set the logger to DEBUG level for testing
155+
LOGGER.critical("Debug message")
156+
captured = capsys.readouterr()
157+
assert "CRITICAL" in captured.err
158+
assert "Debug message" in captured.err
159+
160+
161+
def test_logger_fatal(capsys):
162+
"""Test the fatal method."""
163+
LOGGER.enable_output()
164+
LOGGER.set_level(logging.FATAL) # Set the logger to DEBUG level for testing
165+
LOGGER.fatal("Debug message")
166+
captured = capsys.readouterr()
167+
assert "CRITICAL" in captured.err
168+
assert "Debug message" in captured.err
169+
170+
171+
def test_logger_log(capsys):
172+
"""Test the fatal method."""
173+
LOGGER.enable_output()
174+
LOGGER.set_level(logging.FATAL) # Set the logger to DEBUG level for testing
175+
LOGGER.log(logging.FATAL, "Debug message")
176+
captured = capsys.readouterr()
177+
assert "CRITICAL" in captured.err
178+
assert "Debug message" in captured.err

0 commit comments

Comments
 (0)