diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 61f9b768e7..90ecdf738f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,6 +35,7 @@ env: ON_CI: True PYTEST_ARGUMENTS: '-vvv -rxXsa --color=yes --durations=10 --random-order --random-order-bucket=class --maxfail=10 --reruns 3 --reruns-delay 4 --cov=ansys.mapdl.core --cov-report=html --timeout=180' + BUILD_CHEATSHEET: True PYMAPDL_DEBUG_TESTING: True @@ -63,7 +64,6 @@ permissions: jobs: - update-changelog: name: "Update CHANGELOG (on release)" if: github.event_name == 'push' && contains(github.ref, 'refs/tags') diff --git a/doc/changelog.d/3577.added.md b/doc/changelog.d/3577.added.md new file mode 100644 index 0000000000..5299553dfa --- /dev/null +++ b/doc/changelog.d/3577.added.md @@ -0,0 +1 @@ +refactor: small improvements to test settings \ No newline at end of file diff --git a/src/ansys/mapdl/core/database/database.py b/src/ansys/mapdl/core/database/database.py index 3e395a6297..d35bf5c94d 100644 --- a/src/ansys/mapdl/core/database/database.py +++ b/src/ansys/mapdl/core/database/database.py @@ -313,8 +313,9 @@ def stop(self): self._mapdl._log.debug("Closing the connection with the MAPDL DB Server") self._stop() - self._channel.close() - self._channel = None + if self._channel: + self._channel.close() + self._channel = None self._stub = None self._state = None diff --git a/src/ansys/mapdl/core/launcher.py b/src/ansys/mapdl/core/launcher.py index 55a3efb8bc..a38cfc15aa 100644 --- a/src/ansys/mapdl/core/launcher.py +++ b/src/ansys/mapdl/core/launcher.py @@ -105,7 +105,6 @@ def version_from_path(*args, **kwargs): "additional_switches", "cleanup_on_exit", "clear_on_connect", - "running_on_hpc", "exec_file", "force_intel" "ip", "ip", @@ -125,6 +124,7 @@ def version_from_path(*args, **kwargs): "remove_temp_dir_on_exit", "replace_env_vars", "run_location", + "running_on_hpc", "scheduler_options", "set_no_abort", "start_instance", @@ -554,7 +554,7 @@ def check_mapdl_launch( MAPDL did not start. """ LOG.debug("Generating queue object for stdout") - stdout_queue, _ = _create_queue_for_std(process.stdout) + stdout_queue, thread = _create_queue_for_std(process.stdout) # Checking connection try: diff --git a/src/ansys/mapdl/core/mapdl_core.py b/src/ansys/mapdl/core/mapdl_core.py index afce90bf67..4201a0fc3d 100644 --- a/src/ansys/mapdl/core/mapdl_core.py +++ b/src/ansys/mapdl/core/mapdl_core.py @@ -1396,7 +1396,8 @@ def __init__(self, parent): self._parent = weakref.ref(parent) def __enter__(self): - self._parent()._log.debug("Entering non-interactive mode") + self._parent()._log.debug("Entering in non-interactive mode") + self._parent().com("Entering in non_interactive mode") self._parent()._store_commands = True def __exit__(self, *args): @@ -2283,6 +2284,7 @@ def run( self._before_run(command) short_cmd = parse_to_short_cmd(command) + self._log.debug(f"Running (verbose: {verbose}, mute={mute}): '{command}'") text = self._run(command, verbose=verbose, mute=mute) if ( diff --git a/src/ansys/mapdl/core/mapdl_grpc.py b/src/ansys/mapdl/core/mapdl_grpc.py index 2ff727025c..e2c0ec759e 100644 --- a/src/ansys/mapdl/core/mapdl_grpc.py +++ b/src/ansys/mapdl/core/mapdl_grpc.py @@ -1334,7 +1334,12 @@ def _cache_pids(self): pids = set(re.findall(r"-9 (\d+)", raw)) self._pids = [int(pid) for pid in pids] - if not self._pids: + if not self._pids and not self._mapdl_process: + self._log.debug( + f"MAPDL process is not provided. PIDs could not be retrieved." + ) + return + elif not self._pids: # For the cases where the cleanup file is not generated, # we relay on the process. parent_pid = self._mapdl_process.pid diff --git a/src/ansys/mapdl/core/misc.py b/src/ansys/mapdl/core/misc.py index 610f6821ff..63b2e35b61 100644 --- a/src/ansys/mapdl/core/misc.py +++ b/src/ansys/mapdl/core/misc.py @@ -571,9 +571,10 @@ def wrapper(self, *args, **kwargs): # assuming you want to select nothing because you supplied an empty list/tuple/array return original_sel_func(self, "none") - self._perform_entity_list_selection( - entity, original_sel_func, type_, item, comp, vmin, kabs - ) + with self.non_interactive: # to speed up + self._perform_entity_list_selection( + entity, original_sel_func, type_, item, comp, vmin, kabs + ) if kwargs.pop("Used_P", False): # we want to return the diff --git a/src/ansys/mapdl/core/parameters.py b/src/ansys/mapdl/core/parameters.py index 7b33bd3a8d..594b40188e 100644 --- a/src/ansys/mapdl/core/parameters.py +++ b/src/ansys/mapdl/core/parameters.py @@ -558,7 +558,7 @@ def _get_parameter_array(self, parm_name, shape): f"that could not be read using '{format_str}'." ) - arr_flat = np.fromstring(output, sep="\n").reshape(shape) + arr_flat = np.fromstring(output.strip(), sep="\n").reshape(shape) if len(shape) == 3: if shape[2] == 1: diff --git a/src/ansys/mapdl/core/post.py b/src/ansys/mapdl/core/post.py index 18ebc102c4..8110ec6f8a 100644 --- a/src/ansys/mapdl/core/post.py +++ b/src/ansys/mapdl/core/post.py @@ -139,13 +139,37 @@ def _mapdl(self): @supress_logging def __repr__(self): info = "PyMAPDL PostProcessing Instance\n" - info += "\tActive Result File: %s\n" % self.filename - info += "\tNumber of result sets: %d\n" % self.nsets - info += "\tCurrent load step: %d\n" % self.load_step - info += "\tCurrent sub step: %d\n" % self.sub_step + info += f"\tActive Result File: {self.filename}\n" + + # If there is no result file, this fails. + try: + nsets = int(self.nsets) + except MapdlRuntimeError as error: + self._mapdl.logger.debug( + f"Error when obtaining the number of sets:\n{error}" + ) + nsets = "NA" + + info += f"\tNumber of result sets: {nsets}\n" + info += f"\tCurrent load step: {self.load_step}\n" + info += f"\tCurrent sub step: {self.sub_step}\n" if self._mapdl.parameters.routine == "POST1": - info += "\n\n" + self._mapdl.set("LIST") + try: + nlist = self._mapdl.set("LIST") + except MapdlRuntimeError as err: + if ( + "An error occurred while attempting to open the results file" + in str(err) + ): + self._mapdl.logger.debug( + f"List of steps could not be obtained due to error:\n{err}" + ) + nlist = "Results file is not available" + else: + raise err + + info += "\n\n" + nlist else: info += "\n\nEnable routine POST1 to see a table of available results" diff --git a/tests/common.py b/tests/common.py index 9c90fdd9de..51ed2e2185 100644 --- a/tests/common.py +++ b/tests/common.py @@ -274,7 +274,10 @@ def is_exited(mapdl: Mapdl): # we cannot connect. # Kill the instance - mapdl.exit() + try: + mapdl.exit() + except Exception as e: + LOG.error(f"An error occurred when killing the instance:\n{str(e)}") # Relaunching MAPDL mapdl = launch_mapdl( @@ -285,6 +288,8 @@ def is_exited(mapdl: Mapdl): log_apdl=log_apdl(), ) + LOG.info("Successfully re-connected to MAPDL") + # Restoring the local configuration mapdl._local = local_ mapdl._exited = False diff --git a/tests/conftest.py b/tests/conftest.py index 660a088a23..deebfcaa27 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -193,7 +193,9 @@ def requires_dependency(dependency: str): # the following files are also generated by MAPDL gRPC: # - "pymapdl.apdl": The APDL commands sent to MAPDL by PyMAPDL - # - "apdl.out" : MAPDL console output. Very likely only contains the output until connected. + # - "apdl.out" : MAPDL console output. Very likely only contains the output + # until connected. + ################################################################ # # Importing packages @@ -230,14 +232,12 @@ def requires_dependency(dependency: str): viz_interface.TESTING_MODE = True - ################################################################ # # Pytest configuration # -------------------- # - # check if the user wants to permit pytest to start MAPDL START_INSTANCE = get_start_instance() @@ -558,7 +558,10 @@ def mapdl_console(request): ) mapdl = launch_mapdl( - console_path, mode="console", log_apdl="pymapdl.apdl" if DEBUG_TESTING else None + console_path, + mode="console", + log_apdl="pymapdl.apdl" if DEBUG_TESTING else None, + loglevel="DEBUG" if DEBUG_TESTING else "ERROR", ) from ansys.mapdl.core.mapdl_console import MapdlConsole @@ -604,7 +607,7 @@ def mapdl(request, tmpdir_factory): if ON_CI: mapdl._local = ON_LOCAL # CI: override for testing - if mapdl.is_local: + if ON_LOCAL and mapdl.is_local: assert Path(mapdl.directory) == Path(run_path) # using yield rather than return here to be able to test exit diff --git a/tests/test_database.py b/tests/test_database.py index a7fd1edc63..7cb8440eae 100644 --- a/tests/test_database.py +++ b/tests/test_database.py @@ -63,6 +63,9 @@ def db(mapdl): ) mapdl.clear() + if mapdl.db.active or mapdl.db._stub is None: + mapdl.db.stop() + mapdl.db.start() return mapdl.db @@ -91,6 +94,9 @@ def test_database_start_stop(mapdl, cleared): f"This MAPDL version ({mapdl_version}) docker image seems to not support DB, but local does." ) + if MapdlDb(mapdl).active: + MapdlDb(mapdl)._stop() + # verify it can be created twice mapdl.prep7() for _ in range(2): @@ -111,6 +117,9 @@ def test_database_start_stop(mapdl, cleared): with pytest.warns(UserWarning): database.stop() + # Starting the database for the rest of the test session + mapdl.db.start() + def test_database_repr(db): assert db._channel_str in str(db) @@ -138,8 +147,8 @@ def test_clear(db): def test__channel_str(db): assert db._channel_str is not None assert ":" in db._channel_str - assert re.search("\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}", db._channel_str) - assert re.search("\d{4,6}", db._channel_str) + assert re.search(r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}", db._channel_str) + assert re.search(r"\d{4,6}", db._channel_str) def test_off_db(mapdl, cleared, db): @@ -186,6 +195,10 @@ def test_repr(mapdl, cleared, db): def gen_block(mapdl): """Generate nodes and elements in a simple block.""" + from conftest import clear + + clear(mapdl) + mapdl.block(0, 1, 0, 1, 0, 1) mapdl.et(1, 186) mapdl.esize(0.25) @@ -226,8 +239,8 @@ def test_nodes_next(nodes): def test_nodes_info(nodes): assert nodes.info(1, DBDef.DB_SELECTED) == 1 - @pytest.mark.parametrize("selected", [True, False]) @staticmethod + @pytest.mark.parametrize("selected", [True, False]) def test_nodes_num(nodes, selected): assert nodes.num(selected=selected) == 425 @@ -250,7 +263,7 @@ def test_nodes_asarray(nodes): assert np.allclose(angles, 0) @staticmethod - def test_nodes_push(nodes): + def test_nodes_push(mapdl, nodes): nnum = 100000 x, y, z, xang, yang, zang = 1, 5, 10, 30, 40, 50 nodes.push(nnum, x, y, z, xang, yang, zang) @@ -265,6 +278,10 @@ def test_nodes_push(nodes): with pytest.raises(ValueError, match="X and Y angles must be input"): nodes.push(nnum, x, y, z, zang=1) + # this test changes the database, so let's restore it back + # as in `nodes` fixture. + gen_block(mapdl) + class Test_Elems(TestClass): diff --git a/tests/test_mapdl.py b/tests/test_mapdl.py index 01ffef4650..3455e816d2 100644 --- a/tests/test_mapdl.py +++ b/tests/test_mapdl.py @@ -2219,7 +2219,7 @@ def test_inquire_invalid(mapdl, cleared): def test_inquire_default(mapdl, cleared): mapdl.title("heeeelloo") - assert Path(mapdl.directory) == Path(mapdl.inquire()) + assert str(Path(mapdl.directory)) == str(Path(mapdl.inquire())) def test_vwrite_error(mapdl, cleared): diff --git a/tests/test_post.py b/tests/test_post.py index 42619bb036..489123e589 100644 --- a/tests/test_post.py +++ b/tests/test_post.py @@ -55,9 +55,10 @@ def test_repr(mapdl, cleared): assert "Enable routine POST1 to see a table of available results" in repr_ mapdl.post1() - repr_ = mapdl.post_processing.__repr__() - assert "Enable routine POST1 to see a table of available results" not in repr_ - assert mapdl.set("LIST") in repr_ + assert ( + "Enable routine POST1 to see a table of available results" + not in mapdl.post_processing.__repr__() + ) class Test_static_solve(TestClass): @@ -770,6 +771,16 @@ def resume(mapdl, plastic_solve): nsigfig = 10 mapdl.format("", "E", nsigfig + 9, nsigfig) + @staticmethod + def test_list_in_repr(mapdl, resume): + mapdl.finish() + assert "Enable routine POST1 to see a table of available results" in str( + mapdl.post_processing + ) + + mapdl.post1() + assert mapdl.set("LIST") in mapdl.post_processing.__str__() + @staticmethod @pytest.mark.parametrize("comp", COMPONENT_STRESS_TYPE) def test_nodal_plastic_component_strain(mapdl, resume, comp): diff --git a/tests/test_xpl.py b/tests/test_xpl.py index 979b58d594..639e6f9a3e 100644 --- a/tests/test_xpl.py +++ b/tests/test_xpl.py @@ -39,114 +39,143 @@ def check_supports_extract(mapdl): pytest.skip("command not supported") -@pytest.fixture(scope="function") -def xpl(mapdl, cube_solve): - xpl = mapdl.xpl - xpl.open("file.full") - return xpl +class Test_xpl: + + @staticmethod + @pytest.fixture(scope="class") + def cube_solve(mapdl): + from conftest import clear + + clear(mapdl) + + # set up the full file + mapdl.block(0, 1, 0, 1, 0, 1) + mapdl.et(1, 186) + mapdl.esize(0.5) + mapdl.vmesh("all") + + # Define a material (nominal steel in SI) + mapdl.mp("EX", 1, 210e9) # Elastic modulus in Pa (kg/(m*s**2)) + mapdl.mp("DENS", 1, 7800) # Density in kg/m3 + mapdl.mp("NUXY", 1, 0.3) # Poisson's Ratio + + # solve first 10 non-trivial modes + mapdl.modal_analysis(nmode=10, freqb=1) + mapdl.save("cube_solve_xpl") + + @staticmethod + @pytest.fixture(scope="function") + def xpl(mapdl, cube_solve): + mapdl.prep7() + mapdl.resume("cube_solve_xpl") + + xpl = mapdl.xpl + xpl.open("file.full") + + return xpl + + @staticmethod + def test_close(xpl): + xpl.close() + with pytest.raises(MapdlCommandIgnoredError): + xpl.list() + + @staticmethod + def test_xpl_str(xpl): + assert "file.full" in str(xpl) + + @staticmethod + @requires("ansys-math-core") + def test_read_int32(xpl): + vec = xpl.read("MASS") + arr = vec.asarray() + assert arr.size + assert arr.dtype == np.int32 + + @staticmethod + @requires("ansys-math-core") + def test_read_double(xpl): + vec = xpl.read("DIAGK") + arr = vec.asarray() + assert arr.size + assert arr.dtype == np.double + + @staticmethod + @requires("ansys-math-core") + def test_read_asarray(xpl): + vec1 = xpl.read("MASS", asarray=True) + vec2 = xpl.read("MASS") + assert np.allclose(vec1, vec2.asarray()) + + @staticmethod + def test_save(xpl): + xpl.save() + with pytest.raises(MapdlCommandIgnoredError): + xpl.list() + + @staticmethod + def test_copy(mapdl, cleared, xpl): + filename = "tmpfile.full" + xpl.copy(filename) + assert filename in mapdl.list_files() + + @staticmethod + def test_list(xpl): + assert "::FULL::" in xpl.list(1) + + @staticmethod + def test_help(xpl): + assert "SAVE" in xpl.help() + + @staticmethod + def test_step_where(xpl): + xpl.step("MASS") + assert "FULL::MASS" in xpl.where() + + with pytest.raises(MapdlRuntimeError): + xpl.step("notarecord") + + @staticmethod + def test_info(xpl): + assert "Record Size" in xpl.info("NGPH") + + @staticmethod + def test_print(xpl): + assert "10" in xpl.print("MASS") + + @staticmethod + def test_json(xpl): + json_out = xpl.json() + assert json_out["name"] == "FULL" + assert "children" in json_out + + @staticmethod + def test_up(xpl): + xpl.step("MASS") + xpl.up() + assert "Current Location : FULL" in xpl.where() + + xpl.up("TOP") + assert "Current Location : FULL" in xpl.where() + + @staticmethod + def test_goto(xpl): + xpl.goto("MASS") + assert "Current Location : FULL::MASS" in xpl.where() + + @staticmethod + @requires("ansys-math-core") + @pytest.mark.usefixtures("check_supports_extract") + def test_extract(xpl): + # expecting fixture to already have a non-result file open + assert xpl._filename[-3:] != "rst" + with pytest.raises(MapdlRuntimeError, match="result files"): + mat = xpl.extract("NSL") + + xpl.open("file.rst") + + with pytest.raises(ValueError, match="the only supported recordname is 'NSL'"): + xpl.extract("NOD") - -def test_close(xpl): - xpl.close() - with pytest.raises(MapdlCommandIgnoredError): - xpl.list() - - -def test_xpl_str(xpl): - assert "file.full" in str(xpl) - - -@requires("ansys-math-core") -def test_read_int32(xpl): - vec = xpl.read("MASS") - arr = vec.asarray() - assert arr.size - assert arr.dtype == np.int32 - - -@requires("ansys-math-core") -def test_read_double(xpl): - vec = xpl.read("DIAGK") - arr = vec.asarray() - assert arr.size - assert arr.dtype == np.double - - -@requires("ansys-math-core") -def test_read_asarray(xpl): - vec1 = xpl.read("MASS", asarray=True) - vec2 = xpl.read("MASS") - assert np.allclose(vec1, vec2.asarray()) - - -def test_save(xpl): - xpl.save() - with pytest.raises(MapdlCommandIgnoredError): - xpl.list() - - -def test_copy(mapdl, cleared, xpl): - filename = "tmpfile.full" - xpl.copy(filename) - assert filename in mapdl.list_files() - - -def test_list(xpl): - assert "::FULL::" in xpl.list(1) - - -def test_help(xpl): - assert "SAVE" in xpl.help() - - -def test_step_where(xpl): - xpl.step("MASS") - assert "FULL::MASS" in xpl.where() - - with pytest.raises(MapdlRuntimeError): - xpl.step("notarecord") - - -def test_info(xpl): - assert "Record Size" in xpl.info("NGPH") - - -def test_print(xpl): - assert "10" in xpl.print("MASS") - - -def test_json(xpl): - json_out = xpl.json() - assert json_out["name"] == "FULL" - assert "children" in json_out - - -def test_up(xpl): - xpl.step("MASS") - xpl.up() - assert "Current Location : FULL" in xpl.where() - - xpl.up("TOP") - assert "Current Location : FULL" in xpl.where() - - -def test_goto(xpl): - xpl.goto("MASS") - assert "Current Location : FULL::MASS" in xpl.where() - - -@requires("ansys-math-core") -@pytest.mark.usefixtures("check_supports_extract") -def test_extract(xpl): - # expecting fixture to already have a non-result file open - assert xpl._filename[-3:] != "rst" - with pytest.raises(MapdlRuntimeError, match="result files"): mat = xpl.extract("NSL") - - xpl.open("file.rst") - - with pytest.raises(ValueError, match="the only supported recordname is 'NSL'"): - xpl.extract("NOD") - - mat = xpl.extract("NSL") - assert mat.shape == (243, 10) + assert mat.shape == (243, 10)