From c66a88456a3ec54525a2a80ec52b27ba515d0ab4 Mon Sep 17 00:00:00 2001 From: agu2347 <94227848+agu2347@users.noreply.github.com> Date: Mon, 24 Aug 2026 21:55:50 +0530 Subject: [PATCH 1/2] Fix AttributeError masking dropped AEDT connection in solution_type fallback design_solutions.py falls back to self._design_type.default_solution when self._odesign.GetSolutionType() raises (e.g. because the gRPC connection to AEDT was lost). Every constant class in generic/aedt_constants.py only defines solution_default, never default_solution, so this fallback itself raises AttributeError. That AttributeError, naming a design-type constant, replaces the original connection error and points debugging in the wrong direction. DesignSolution.solution_type already uses the correct solution_default spelling in its getter; the setter (value=None branch) and HFSSDesignSolution.solution_type getter/setter still used the wrong default_solution spelling. Fix all nine remaining occurrences. Added tests/unit/test_design_solutions.py with unit tests (no AEDT installation required) that reproduce the exact AttributeError against the unfixed code via a fake ODesign whose GetSolutionType() raises, and pass against the fix. Fixes #8020 --- .../aedt/core/application/design_solutions.py | 18 +-- tests/unit/test_design_solutions.py | 104 ++++++++++++++++++ 2 files changed, 113 insertions(+), 9 deletions(-) create mode 100644 tests/unit/test_design_solutions.py diff --git a/src/ansys/aedt/core/application/design_solutions.py b/src/ansys/aedt/core/application/design_solutions.py index 4d212c379530..3e6b8b41c208 100644 --- a/src/ansys/aedt/core/application/design_solutions.py +++ b/src/ansys/aedt/core/application/design_solutions.py @@ -86,14 +86,14 @@ def solution_type(self, value: str) -> None: DesignType.EMIT, DesignType.Q3D, ]: - self._solution_type = self._design_type.default_solution + self._solution_type = self._design_type.solution_default elif self._odesign: try: self._solution_type = self._odesign.GetSolutionType() except Exception: - self._solution_type = self._design_type.default_solution + self._solution_type = self._design_type.solution_default else: - self._solution_type = self._design_type.default_solution + self._solution_type = self._design_type.solution_default elif value and value in self._solution_options: self._solution_type = value if self._solution_options[value]["name"]: @@ -215,9 +215,9 @@ def solution_type(self) -> str: elif "Terminal" in self._solution_type: self._solution_type = "Terminal" except Exception: - self._solution_type = self._design_type.default_solution + self._solution_type = self._design_type.solution_default elif self._solution_type is None: - self._solution_type = self._design_type.default_solution + self._solution_type = self._design_type.solution_default return self._solution_type @solution_type.setter @@ -246,9 +246,9 @@ def solution_type(self, value: str) -> None: elif "Terminal" in self._solution_type: self._solution_type = "Terminal" except Exception: - self._solution_type = self._design_type.default_solution + self._solution_type = self._design_type.solution_default else: - self._solution_type = self._design_type.default_solution + self._solution_type = self._design_type.solution_default elif value and value in self._solution_options and self._solution_options[value]["name"]: if value == "Transient" or value == "Transient Network": value = "Transient Network" @@ -415,7 +415,7 @@ def solution_type(self): try: self._solution_type = self._odesign.GetSolutionType() except Exception: - self._solution_type = self._design_type.default_solution + self._solution_type = self._design_type.solution_default return self._solution_type @solution_type.setter @@ -515,7 +515,7 @@ def solution_type(self) -> str: try: self._solution_type = self._odesign.GetSolutionType() except Exception: - self._solution_type = self._design_type.default_solution + self._solution_type = self._design_type.solution_default return self._solution_type @solution_type.setter diff --git a/tests/unit/test_design_solutions.py b/tests/unit/test_design_solutions.py new file mode 100644 index 000000000000..f2359c44c725 --- /dev/null +++ b/tests/unit/test_design_solutions.py @@ -0,0 +1,104 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2021 - 2026 Synopsys, Inc. and ANSYS, Inc. All rights reserved. +# 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. + +import pytest + +from ansys.aedt.core.application.design_solutions import DesignSolution +from ansys.aedt.core.application.design_solutions import HFSSDesignSolution +from ansys.aedt.core.generic.aedt_constants import HfssConstants +from ansys.aedt.core.generic.aedt_constants import Q3dConstants + + +class RaisingODesign: + """Stand-in for a design whose gRPC connection to AEDT has dropped. + + ``GetSolutionType()`` raising mirrors what happens when the connection + to AEDT is lost: the ``except Exception`` fallback in + ``design_solutions.py`` is exercised. + """ + + def GetSolutionType(self): + raise RuntimeError("connection lost") + + +def test_constants_only_define_solution_default(): + """The constant classes only ever define ``solution_default``.""" + assert hasattr(HfssConstants, "solution_default") + assert not hasattr(HfssConstants, "default_solution") + assert hasattr(Q3dConstants, "solution_default") + assert not hasattr(Q3dConstants, "default_solution") + + +def test_design_solution_getter_falls_back_on_connection_error(): + """DesignSolution.solution_type getter should not raise on a dropped connection.""" + ds = DesignSolution.__new__(DesignSolution) + ds._odesign = RaisingODesign() + ds._design_type = HfssConstants + ds._solution_type = None + + assert ds.solution_type == HfssConstants.solution_default + + +def test_design_solution_setter_falls_back_on_connection_error(): + """DesignSolution.solution_type setter (value=None) should not raise on a dropped connection. + + Regression test for the bug where the fallback read + ``self._design_type.default_solution`` instead of ``solution_default``, + turning a lost-connection error into a confusing AttributeError. + """ + ds = DesignSolution.__new__(DesignSolution) + ds._odesign = RaisingODesign() + ds._design_type = HfssConstants + ds._solution_type = None + ds._solution_options = { + "Modal": {"name": "HFSS Modal Network", "options": None}, + "Terminal": {"name": "HFSS Terminal Network", "options": None}, + } + + ds.solution_type = None + + assert ds._solution_type == HfssConstants.solution_default + + +def test_design_solution_setter_no_odesign_falls_back(): + """DesignSolution.solution_type setter with no attached design still falls back correctly.""" + ds = DesignSolution.__new__(DesignSolution) + ds._odesign = None + ds._design_type = Q3dConstants + ds._solution_type = None + ds._solution_options = {} + + ds.solution_type = None + + assert ds._solution_type == Q3dConstants.solution_default + + +def test_hfss_design_solution_getter_falls_back_on_connection_error(): + """HFSSDesignSolution.solution_type getter should not raise on a dropped connection.""" + hds = HFSSDesignSolution.__new__(HFSSDesignSolution) + hds._odesign = RaisingODesign() + hds._design_type = HfssConstants + hds._solution_type = None + + assert hds.solution_type == HfssConstants.solution_default From a99e9b3f83a7cf5da8d2929896ddc5d8692f25ac Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 24 Aug 2026 16:28:12 +0000 Subject: [PATCH 2/2] CHORE: Auto fixes from pre-commit hooks --- tests/unit/test_design_solutions.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/unit/test_design_solutions.py b/tests/unit/test_design_solutions.py index f2359c44c725..c031ae041c78 100644 --- a/tests/unit/test_design_solutions.py +++ b/tests/unit/test_design_solutions.py @@ -22,7 +22,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -import pytest from ansys.aedt.core.application.design_solutions import DesignSolution from ansys.aedt.core.application.design_solutions import HFSSDesignSolution