From c42545f4c8b842cf293af22ef3564a2732464a0a Mon Sep 17 00:00:00 2001 From: Christophe DAVID Date: Mon, 17 Jan 2022 16:32:44 +0100 Subject: [PATCH] Moved get_xfoil. Enhanced PackageReader (better check for existence). --- .github/workflows/watchman_tests.yml | 2 +- src/conftest.py | 39 ++++++++++++++++++- .../_utils/resource_management/contents.py | 12 +++++- .../external/xfoil/tests/test_xfoil.py | 18 +++------ .../tests/test_aerodynamics_landing.py | 12 ++---- .../module_management/_bundle_loader.py | 5 +-- tests/{xfoil_exe/__init__.py => conftest.py} | 5 ++- .../oad_process/test_oad_process.py | 17 ++++---- tests/xfoil_exe/get_xfoil.py | 26 ------------- 9 files changed, 71 insertions(+), 65 deletions(-) rename tests/{xfoil_exe/__init__.py => conftest.py} (83%) delete mode 100644 tests/xfoil_exe/get_xfoil.py diff --git a/.github/workflows/watchman_tests.yml b/.github/workflows/watchman_tests.yml index a3ed6d2bb..0529c06af 100644 --- a/.github/workflows/watchman_tests.yml +++ b/.github/workflows/watchman_tests.yml @@ -34,7 +34,7 @@ jobs: shell: bash - name: Unit tests - run: pytest --no-cov src + run: pytest --no-cov src -c ./conftest.py shell: bash - name: Notebook tests diff --git a/src/conftest.py b/src/conftest.py index d3fd15cad..4ec2cd2ff 100644 --- a/src/conftest.py +++ b/src/conftest.py @@ -19,7 +19,9 @@ import os.path as pth import sys -from typing import List +from platform import system +from shutil import which +from typing import List, Optional from unittest.mock import Mock import pytest @@ -36,8 +38,41 @@ CONFIGURATION_FILE_PATH = pth.join(DATA_FOLDER_PATH, "sellar.yml") +@pytest.fixture(autouse=True) +def no_xfoil_skip(request, xfoil_path): + """ + Use @pytest.mark.skip_if_no_xfoil() before a test to skip it if xfoil_path + fixture returns None and OS is not Windows. + """ + if request.node.get_closest_marker("skip_if_no_xfoil"): + if xfoil_path is None and system() != "Windows": + pytest.skip("No XFOIL executable available") + + +@pytest.fixture +def xfoil_path() -> Optional[str]: + """ + On a system that is not Windows, a XFOIL executable with name "xfoil" can + be put in "/tests/xfoil_exe/". + In this case, this fixture will return its path. + + :return: The path of the XFOIL executable + """ + if system() == "Windows": + # On Windows, we use the embedded executable + return None + + path = pth.abspath(pth.join("tests/xfoil_exe", "xfoil")) + if pth.exists(path): + # If there is a local xfoil, use it + return path + + # Otherwise, use one that is in PATH, if it exists + return which("xfoil") + + @pytest.fixture -def plugin_root_path(): +def plugin_root_path() -> str: """Returns the path to dummy plugins, for check purpose.""" return pth.abspath("tests/dummy_plugins") diff --git a/src/fastoad/_utils/resource_management/contents.py b/src/fastoad/_utils/resource_management/contents.py index 4264d0f06..88f59ed6c 100644 --- a/src/fastoad/_utils/resource_management/contents.py +++ b/src/fastoad/_utils/resource_management/contents.py @@ -29,6 +29,7 @@ def __init__(self, package_name: str): :param package_name: """ self.is_package = False + self.exists = True self.is_module = False self.has_error = False self._contents = [] @@ -49,7 +50,16 @@ def package_name(self, package_name): except TypeError: self.is_module = True except ModuleNotFoundError: - pass + # Either the indicated package does not exist, or it is a file with no extension. + # We want to ensure existence to correctly set self.exists. + if "." in package_name: + parent_package_name, module_name = package_name.rsplit(".", 1) + parent_package = PackageReader(parent_package_name) + if parent_package.is_package and module_name in parent_package.contents: + self.exists = True + else: + # Here we assume non-existence + self.exists = False except Exception: # pylint: disable = W0703 # Here we catch any Python error that may happen when reading the loaded code. # Thus, we ensure to not break the application if a module is incorrectly written. diff --git a/src/fastoad/models/aerodynamics/external/xfoil/tests/test_xfoil.py b/src/fastoad/models/aerodynamics/external/xfoil/tests/test_xfoil.py index b3df89221..773ef2c60 100644 --- a/src/fastoad/models/aerodynamics/external/xfoil/tests/test_xfoil.py +++ b/src/fastoad/models/aerodynamics/external/xfoil/tests/test_xfoil.py @@ -2,7 +2,7 @@ Test module for XFOIL component """ # This file is part of FAST-OAD : A framework for rapid Overall Aircraft Design -# Copyright (C) 2021 ONERA & ISAE-SUPAERO +# Copyright (C) 2022 ONERA & ISAE-SUPAERO # FAST is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or @@ -18,24 +18,18 @@ import os.path as pth import shutil -from platform import system import pytest from openmdao.core.indepvarcomp import IndepVarComp from fastoad._utils.testing import run_system -from tests.xfoil_exe.get_xfoil import get_xfoil_path from ..xfoil_polar import DEFAULT_2D_CL_MAX, XfoilPolar XFOIL_RESULTS = pth.join(pth.dirname(__file__), "results") -xfoil_path = None if system() == "Windows" else get_xfoil_path() - -@pytest.mark.skipif( - system() != "Windows" and xfoil_path is None, reason="No XFOIL executable available" -) -def test_compute(): +@pytest.mark.skip_if_no_xfoil() +def test_compute(xfoil_path): """Tests a simple XFOIL run""" if pth.exists(XFOIL_RESULTS): @@ -80,10 +74,8 @@ def test_compute(): assert pth.exists(pth.join(XFOIL_RESULTS, "polar_result.txt")) -@pytest.mark.skipif( - system() != "Windows" and xfoil_path is None, reason="No XFOIL executable available" -) -def test_compute_with_provided_path(): +@pytest.mark.skip_if_no_xfoil() +def test_compute_with_provided_path(xfoil_path): """Test that option "use_exe_path" works""" ivc = IndepVarComp() ivc.add_output("xfoil:reynolds", 18000000) diff --git a/src/fastoad/models/aerodynamics/tests/test_aerodynamics_landing.py b/src/fastoad/models/aerodynamics/tests/test_aerodynamics_landing.py index 643050d81..a111c7aa5 100644 --- a/src/fastoad/models/aerodynamics/tests/test_aerodynamics_landing.py +++ b/src/fastoad/models/aerodynamics/tests/test_aerodynamics_landing.py @@ -2,7 +2,7 @@ Test module for aerodynamics groups """ # This file is part of FAST-OAD : A framework for rapid Overall Aircraft Design -# Copyright (C) 2021 ONERA & ISAE-SUPAERO +# Copyright (C) 2022 ONERA & ISAE-SUPAERO # FAST is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or @@ -15,18 +15,14 @@ # along with this program. If not, see . import os.path as pth -from platform import system import pytest from pytest import approx from fastoad._utils.testing import run_system from fastoad.io import VariableIO -from tests.xfoil_exe.get_xfoil import get_xfoil_path from ..aerodynamics_landing import AerodynamicsLanding -xfoil_path = None if system() == "Windows" else get_xfoil_path() - def get_indep_var_comp(var_names): """Reads required input data and returns an IndepVarcomp() instance""" @@ -36,10 +32,8 @@ def get_indep_var_comp(var_names): return ivc -@pytest.mark.skipif( - system() != "Windows" and xfoil_path is None, reason="No XFOIL executable available" -) -def test_aerodynamics_landing_with_xfoil(): +@pytest.mark.skip_if_no_xfoil() +def test_aerodynamics_landing_with_xfoil(xfoil_path): """Tests AerodynamicsHighSpeed""" input_list = [ "data:TLAR:approach_speed", diff --git a/src/fastoad/module_management/_bundle_loader.py b/src/fastoad/module_management/_bundle_loader.py index d4430e270..b2b01c692 100644 --- a/src/fastoad/module_management/_bundle_loader.py +++ b/src/fastoad/module_management/_bundle_loader.py @@ -344,7 +344,7 @@ def _install_python_package(self, package_name: str) -> Tuple[Set[Bundle], Set[s failed = set() package = PackageReader(package_name) - if package.has_error: + if package.has_error or not package.exists: failed.add(package_name) elif package.is_package: # It is a package, let's explore it. @@ -362,9 +362,6 @@ def _install_python_package(self, package_name: str) -> Tuple[Set[Bundle], Set[s sub_bundles, sub_failed = self._install_python_package(item_package) bundles.update(sub_bundles) failed.update(sub_failed) - elif not package.is_module: - # Does not exist. - failed.add(package_name) return bundles, failed diff --git a/tests/xfoil_exe/__init__.py b/tests/conftest.py similarity index 83% rename from tests/xfoil_exe/__init__.py rename to tests/conftest.py index 94845a3e6..a6cc6cf86 100644 --- a/tests/xfoil_exe/__init__.py +++ b/tests/conftest.py @@ -1,5 +1,5 @@ # This file is part of FAST-OAD : A framework for rapid Overall Aircraft Design -# Copyright (C) 2020 ONERA & ISAE-SUPAERO +# Copyright (C) 2022 ONERA & ISAE-SUPAERO # FAST is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or @@ -10,3 +10,6 @@ # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . + +# pylint: disable=unused-import +from src.conftest import no_xfoil_skip, xfoil_path # noqa: F401 diff --git a/tests/integration_tests/oad_process/test_oad_process.py b/tests/integration_tests/oad_process/test_oad_process.py index 0a6d18088..66fbf0b97 100644 --- a/tests/integration_tests/oad_process/test_oad_process.py +++ b/tests/integration_tests/oad_process/test_oad_process.py @@ -2,7 +2,7 @@ Test module for Overall Aircraft Design process """ # This file is part of FAST-OAD : A framework for rapid Overall Aircraft Design -# Copyright (C) 2021 ONERA & ISAE-SUPAERO +# Copyright (C) 2022 ONERA & ISAE-SUPAERO # FAST is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or @@ -34,13 +34,10 @@ _IConfigurationModifier, ) from tests import root_folder_path -from tests.xfoil_exe.get_xfoil import get_xfoil_path DATA_FOLDER_PATH = pth.join(pth.dirname(__file__), "data") RESULTS_FOLDER_PATH = pth.join(pth.dirname(__file__), "results") -xfoil_path = None if system() == "Windows" else get_xfoil_path() - @pytest.fixture(scope="module") def cleanup(): @@ -75,12 +72,13 @@ def test_oad_process(cleanup): _check_weight_performance_loop(problem) -def test_non_regression_breguet(cleanup): +def test_non_regression_breguet(cleanup, xfoil_path): run_non_regression_test( "oad_process_breguet.yml", "CeRAS01_legacy_breguet_result.xml", "non_regression_breguet", use_xfoil=True, + xfoil_path=xfoil_path, ) @@ -114,12 +112,13 @@ class XFOILConfigurator(_IConfigurationModifier): """Overwrite XFOIL usage setting of configuration file""" use_xfoil: bool + xfoil_path: str = None def modify(self, problem: om.Problem): - if self.use_xfoil and (system() == "Windows" or xfoil_path): + if self.use_xfoil and (system() == "Windows" or self.xfoil_path): problem.model.aerodynamics_landing._OPTIONS["use_xfoil"] = True if system() != "Windows": - problem.model.aerodynamics_landing._OPTIONS["xfoil_exe_path"] = xfoil_path + problem.model.aerodynamics_landing._OPTIONS["xfoil_exe_path"] = self.xfoil_path # BTW we narrow computed alpha range for sake of CPU time problem.model.aerodynamics_landing._OPTIONS["xfoil_alpha_min"] = 16.0 problem.model.aerodynamics_landing._OPTIONS["xfoil_alpha_max"] = 22.0 @@ -130,6 +129,7 @@ def run_non_regression_test( legacy_result_file, result_dir, use_xfoil=False, + xfoil_path=None, global_tolerance=1e-2, vars_to_check=None, specific_tolerance=5.0e-3, @@ -141,6 +141,7 @@ def run_non_regression_test( :param legacy_result_file: reference data for inputs and outputs :param result_dir: relative name, folder will be in RESULTS_FOLDER_PATH :param use_xfoil: if True, XFOIL computation will be activated + :param xfoil_path: used if use_xfoil==True :param vars_to_check: variables that will be concerned by specific_tolerance :param specific_tolerance: test will fail if absolute relative error between computed and reference values is beyond this value for variables in vars_to_check @@ -155,7 +156,7 @@ def run_non_regression_test( api.generate_configuration_file(configuration_file_path) # just ensure folders are created... shutil.copy(pth.join(DATA_FOLDER_PATH, conf_file), configuration_file_path) configurator = FASTOADProblemConfigurator(configuration_file_path) - configurator._set_configuration_modifier(XFOILConfigurator(use_xfoil)) + configurator._set_configuration_modifier(XFOILConfigurator(use_xfoil, xfoil_path)) # Generation of inputs ---------------------------------------- ref_inputs = pth.join(DATA_FOLDER_PATH, legacy_result_file) diff --git a/tests/xfoil_exe/get_xfoil.py b/tests/xfoil_exe/get_xfoil.py deleted file mode 100644 index c32bf8788..000000000 --- a/tests/xfoil_exe/get_xfoil.py +++ /dev/null @@ -1,26 +0,0 @@ -# This file is part of FAST-OAD : A framework for rapid Overall Aircraft Design -# Copyright (C) 2020 ONERA & ISAE-SUPAERO -# FAST is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import os.path as pth -from shutil import which -from typing import Optional - - -def get_xfoil_path() -> Optional[str]: - path = pth.join(pth.dirname(__file__), "xfoil") - if pth.exists(path): - # If there is a local xfoil, use it - return path - else: - # Otherwise, use one that is in PATH, if it exists - return which("xfoil")