diff --git a/mcstasscript/configuration.yaml b/mcstasscript/configuration.yaml deleted file mode 100644 index 008534d1..00000000 --- a/mcstasscript/configuration.yaml +++ /dev/null @@ -1,7 +0,0 @@ -other: - characters_per_line: 85 -paths: - mcrun_path: /Applications/McStas-2.7.1.app/Contents/Resources/mcstas/2.7.1/bin/ - mcstas_path: /Applications/McStas-2.7.1.app/Contents/Resources/mcstas/2.7.1/ - mcxtrace_path: /Applications/McXtrace-1.5.app/Contents/Resources/mcxtrace/1.5/ - mxrun_path: /Applications/McXtrace-1.5.app/Contents/Resources/mcxtrace/1.5/bin/ diff --git a/mcstasscript/helper/managed_mcrun.py b/mcstasscript/helper/managed_mcrun.py index d63b56c1..6197ecb5 100644 --- a/mcstasscript/helper/managed_mcrun.py +++ b/mcstasscript/helper/managed_mcrun.py @@ -266,6 +266,11 @@ def run_simulation(self, **kwargs): universal_newlines=True, cwd=self.run_path) + if process.returncode != 0: + error_message = f"Executing\n\t{full_command}\nfailed with error code {process.returncode}." + error_message += f"\nCommand output:\n{process.stdout}" + raise RuntimeError(error_message) + if "suppress_output" in kwargs: if kwargs["suppress_output"] is False: print_sim_output(process.stdout) diff --git a/mcstasscript/integration_tests/test_complex_instrument.py b/mcstasscript/integration_tests/test_complex_instrument.py index 380e749c..19d489ef 100644 --- a/mcstasscript/integration_tests/test_complex_instrument.py +++ b/mcstasscript/integration_tests/test_complex_instrument.py @@ -101,10 +101,15 @@ def setup_complex_instrument(): Instr.add_component("done", "Arm", RELATIVE="after_guide") + from packaging.version import Version + bin_attr = 'nx' if Instr.executable_version < Version('3.0.0') else 'nbins' + PSD1 = Instr.add_component("PSD_1D_1", "PSDlin_monitor") PSD1.set_AT([0, 0, 0.2], RELATIVE="after_guide") PSD1.xwidth = 0.1 - PSD1.nx = 100 + if not hasattr(PSD1, bin_attr): + raise RuntimeError(f"Expected {PSD1} to have attribute {bin_attr}") + setattr(PSD1, bin_attr, 100) PSD1.yheight = 0.03 PSD1.filename = "\"PSD1.dat\"" PSD1.restore_neutron = 1 @@ -113,7 +118,7 @@ def setup_complex_instrument(): PSD2 = Instr.add_component("PSD_1D_2", "PSDlin_monitor") PSD2.set_AT([0, 0, 0.2], RELATIVE="after_guide") PSD2.xwidth = 0.1 - PSD2.nx = 100 + setattr(PSD2, bin_attr, 100) PSD2.yheight = 0.03 PSD2.filename = "\"PSD2.dat\"" PSD2.restore_neutron = 1 @@ -122,7 +127,7 @@ def setup_complex_instrument(): PSD = Instr.add_component("PSD_1D", "PSDlin_monitor") PSD.set_AT([0, 0, 0.2], RELATIVE="after_guide") PSD.xwidth = 0.1 - PSD.nx = 100 + setattr(PSD, bin_attr, 100) PSD.yheight = 0.03 PSD.filename = "\"PSD_all.dat\"" PSD.restore_neutron = 1 diff --git a/mcstasscript/integration_tests/test_simple_instrument.py b/mcstasscript/integration_tests/test_simple_instrument.py index 0e99a421..a5e8e734 100644 --- a/mcstasscript/integration_tests/test_simple_instrument.py +++ b/mcstasscript/integration_tests/test_simple_instrument.py @@ -8,6 +8,9 @@ def setup_simple_instrument(): Instr = instr.McStas_instr("integration_test_simple") + from packaging.version import Version + bin_attr = 'nx' if Instr.executable_version < Version('3.0.0') else 'nbins' + source = Instr.add_component("source", "Source_div") source.xwidth = 0.03 @@ -22,7 +25,9 @@ def setup_simple_instrument(): PSD.set_AT([0, 0, 1], RELATIVE="source") PSD.xwidth = 0.1 - PSD.nx = 100 + if not hasattr(PSD, bin_attr): + raise RuntimeError(f"Expected {PSD} to have attribute {bin_attr}") + setattr(PSD, bin_attr, 100) PSD.yheight = 0.03 PSD.filename = "\"PSD.dat\"" PSD.restore_neutron = 1 @@ -37,6 +42,12 @@ def setup_simple_instrument_input_path(): Instr = instr.McStas_instr("integration_test_simple_input", input_path=input_path) + from packaging.version import Version + if Instr.executable_version > Version('3.0.0'): + import warnings + warnings.warn("The version of McStas is not 2.x; this test would have failed if it had not been skipped") + return Instr + source = Instr.add_component("source", "Source_div") source.xwidth = 0.03 @@ -62,6 +73,9 @@ def setup_simple_instrument_input_path(): def setup_simple_slit_instrument(): Instr = instr.McStas_instr("integration_test_simple") + from packaging.version import Version + bin_attr = 'nx' if Instr.executable_version < Version('3.0.0') else 'nbins' + source = Instr.add_component("source", "Source_div") source.xwidth = 0.1 source.yheight = 0.01 @@ -81,7 +95,9 @@ def setup_simple_slit_instrument(): PSD = Instr.add_component("PSD_1D", "PSDlin_monitor") PSD.set_AT([0, 0, 1], RELATIVE="source") PSD.xwidth = 0.1 - PSD.nx = 100 + if not hasattr(PSD, bin_attr): + raise RuntimeError(f"Expected {PSD} to have attribute {bin_attr}") + setattr(PSD, bin_attr, 100) PSD.yheight = 0.03 PSD.filename = "\"PSD.dat\"" PSD.restore_neutron = 1 @@ -137,6 +153,11 @@ def test_simple_instrument_input(self, mock_stdout): Instr = setup_simple_instrument_input_path() + from packaging.version import Version + if Instr.executable_version > Version('3.0.0'): + import warnings + warnings.warn("The version of McStas is not 2.x; this test would have failed if it had not been skipped") + foldername = "integration_test_simple_input" data = Instr.run_full_instrument(foldername=foldername, ncount=1E6, mpi=1, diff --git a/mcstasscript/interface/functions.py b/mcstasscript/interface/functions.py index 7a98fa13..8eb6248a 100644 --- a/mcstasscript/interface/functions.py +++ b/mcstasscript/interface/functions.py @@ -213,19 +213,47 @@ def _create_new_config_file(self): """ Writes a default configuration file to the package root directory """ - - run = "/Applications/McStas-2.5.app/Contents/Resources/mcstas/2.5/bin/" - mcstas = "/Applications/McStas-2.5.app/Contents/Resources/mcstas/2.5/" - - mxrun = "/Applications/McXtrace-1.5.app" \ - + "/Contents/Resources/mcxtrace/1.5/mxrun" - mcxtrace = "/Applications/McXtrace-1.5.app" \ - + "/Contents/Resources/mcxtrace/1.5/" - - default_paths = {"mcrun_path": run, - "mcstas_path": mcstas, - "mxrun_path": mxrun, - "mcxtrace_path": mcxtrace} + items = ('McStas', '2.7.1'), ('McXtrace', '1.5') + def make_mac_defaults(): + base = [f'/Applications/{n}-{v}.app/Contents/Resources/{n.lower()}/{v}/' for n, v in items] + defs = {f'{n}run_path': b+'bin/' for n, b in zip(('mc', 'mx'), base)} + defs.update({f'{n}_path': b for n, b in zip(('mcstas', 'mcxtrace'), base)}) + return defs + + def make_unix_defaults(): + defs = {f'{n.lower()}_path': f'/usr/share/{n.lower()}/{v}' for n, v in items} + defs.update({f'{n}_path': '/usr/bin/' for n in ('mcrun', 'mxrun')}) + return defs + + def make_win_defaults(): + defs = {f'{k}run_path': f'\\{n.lower()}-{v}\\bin\\' for k, (n, v) in zip(('mc', 'mx'), items)} + defs.update({f'{n.lower()}_path': f'\\{n.lower()}-{v}\\lib\\' for n, v in items}) + return defs + + import platform + default_paths = {} + if 'linux' in platform.system().lower(): + default_paths = make_unix_defaults() + elif 'darwin' in platform.system().lower(): + default_paths = make_mac_defaults() + elif 'win' in platform.system().lower(): + default_paths = make_win_defaults() + + # check if McStas or McXtrace are *on* the system path, then default to those: + import shutil + from pathlib import Path + if shutil.which('mcstas') is not None: + binary_path = Path(shutil.which('mcstas')) + if binary_path.is_symlink(): + binary_path = binary_path.readlink() + default_paths['mcrun_path'] = f"{binary_path.parent}" + default_paths['mcstas_path'] = f"{binary_path.parent.parent}" + if shutil.which('mcxtrace') is not None: + binary_path = Path(shutil.which('mcxtrace')) + if binary_path.is_symlink(): + binary_path = binary_path.readlink() + default_paths['mxrun_path'] = f'{binary_path.parent}' + default_paths['mcxtrace_path'] = f'{binary_path.parent.parent}' default_other = {"characters_per_line": 85} diff --git a/mcstasscript/interface/instr.py b/mcstasscript/interface/instr.py index 7b3cf699..fa3706bd 100644 --- a/mcstasscript/interface/instr.py +++ b/mcstasscript/interface/instr.py @@ -426,6 +426,22 @@ def output_path(self) -> str: def output_path(self, value: str) -> None: self.calculator_base_dir = value + @property + def executable_version(self): + from subprocess import check_output + from pathlib import Path + from os import access, X_OK + from packaging.version import parse + if 'executable_path' not in self._run_settings or 'executable' not in self._run_settings: + return '0.0.0' + torun = Path(self._run_settings['executable_path']).joinpath(self._run_settings['executable']) + if not torun.exists() or not access(torun, X_OK): + return '0.0.0' + version_info = check_output([torun, '--version']).decode('utf-8') + version_string = [x for x in version_info.split('\n') if len(x)][-1] + return parse(version_string) + + def init_parameters(self): """ Create empty ParameterContainer for new instrument @@ -2351,14 +2367,8 @@ def __init__(self, name, **kwargs): super().__init__(name, executable=executable, **kwargs) def _read_calibration(self): - this_dir = os.path.dirname(os.path.abspath(__file__)) - configuration_file_name = os.path.join(this_dir, "..", - "configuration.yaml") - if not os.path.isfile(configuration_file_name): - raise NameError("Could not find configuration file!") - with open(configuration_file_name, 'r') as ymlfile: - config = yaml.safe_load(ymlfile) - + from .. import Configurator + config = Configurator()._read_yaml() if type(config) is dict: self._run_settings["executable_path"] = config["paths"]["mcrun_path"] self._run_settings["package_path"] = config["paths"]["mcstas_path"] @@ -2574,14 +2584,8 @@ def __init__(self, name, **kwargs): super().__init__(name, executable=executable, **kwargs) def _read_calibration(self): - this_dir = os.path.dirname(os.path.abspath(__file__)) - configuration_file_name = os.path.join(this_dir, "..", - "configuration.yaml") - if not os.path.isfile(configuration_file_name): - raise NameError("Could not find configuration file!") - with open(configuration_file_name, 'r') as ymlfile: - config = yaml.safe_load(ymlfile) - + from .. import Configurator + config = Configurator()._read_yaml() if type(config) is dict: self._run_settings["executable_path"] = config["paths"]["mxrun_path"] self._run_settings["package_path"] = config["paths"]["mcxtrace_path"] diff --git a/mcstasscript/tests/test_Configurator.py b/mcstasscript/tests/test_Configurator.py index 42f68557..11fc400c 100644 --- a/mcstasscript/tests/test_Configurator.py +++ b/mcstasscript/tests/test_Configurator.py @@ -47,38 +47,6 @@ def test_simple_initialize(self): if os.path.isfile(expected_file): os.remove(expected_file) - def test_default_config(self): - """ - This tests confirms the content of the default configuration file - """ - - test_name = "test_configuration" - expected_file = setup_expected_file(test_name) - - # check the file did not exist before testing - self.assertFalse(os.path.isfile(expected_file)) - - my_configurator = Configurator(test_name) - - default_config = my_configurator._read_yaml() - - run = "/Applications/McStas-2.5.app/Contents/Resources/mcstas/2.5/bin/" - mcstas = "/Applications/McStas-2.5.app/Contents/Resources/mcstas/2.5/" - mxrun = "/Applications/McXtrace-1.5.app" \ - + "/Contents/Resources/mcxtrace/1.5/mxrun" - mcxtrace = "/Applications/McXtrace-1.5.app" \ - + "/Contents/Resources/mcxtrace/1.5/" - - self.assertEqual(default_config["paths"]["mcrun_path"], run) - self.assertEqual(default_config["paths"]["mcstas_path"], mcstas) - self.assertEqual(default_config["paths"]["mxrun_path"], mxrun) - self.assertEqual(default_config["paths"]["mcxtrace_path"], mcxtrace) - self.assertEqual(default_config["other"]["characters_per_line"], 85) - - # remove the testing configuration file - if os.path.isfile(expected_file): - os.remove(expected_file) - def test_yaml_write(self): """ This test checks that writing to the configuration file works diff --git a/mcstasscript/tests/test_Instr.py b/mcstasscript/tests/test_Instr.py index 8309314f..7aab2989 100644 --- a/mcstasscript/tests/test_Instr.py +++ b/mcstasscript/tests/test_Instr.py @@ -1702,6 +1702,11 @@ def test_x_ray_run_full_instrument_basic(self, mock_sub, mock_stdout): existing file so no error is thrown. """ + # The mocked process needs to set a return-code of zero to avoid the process error checking + process_mock = unittest.mock.Mock() + process_mock.configure_mock(returncode=0) + mock_sub.return_value = process_mock + THIS_DIR = os.path.dirname(os.path.abspath(__file__)) executable_path = os.path.join(THIS_DIR, "dummy_mcstas") @@ -1768,6 +1773,11 @@ def test_run_backengine_basic(self, mock_sub, mock_stdout): existing file so no error is thrown. """ + # The mocked process needs to set a return-code of zero to avoid the process error checking + process_mock = unittest.mock.Mock() + process_mock.configure_mock(returncode=0) + mock_sub.return_value = process_mock + THIS_DIR = os.path.dirname(os.path.abspath(__file__)) executable_path = os.path.join(THIS_DIR, "dummy_mcstas") @@ -1814,6 +1824,11 @@ def test_run_full_instrument_complex(self, mock_sub, mock_stdout): existing file so no error is thrown. """ + # The mocked process needs to set a return-code of zero to avoid the process error checking + process_mock = unittest.mock.Mock() + process_mock.configure_mock(returncode=0) + mock_sub.return_value = process_mock + THIS_DIR = os.path.dirname(os.path.abspath(__file__)) executable_path = os.path.join(THIS_DIR, "dummy_mcstas") @@ -1867,6 +1882,11 @@ def test_run_full_instrument_overwrite_default(self, mock_sub, parameters in run_full_instrument. """ + # The mocked process needs to set a return-code of zero to avoid the process error checking + process_mock = unittest.mock.Mock() + process_mock.configure_mock(returncode=0) + mock_sub.return_value = process_mock + THIS_DIR = os.path.dirname(os.path.abspath(__file__)) executable_path = os.path.join(THIS_DIR, "dummy_mcstas") @@ -1921,6 +1941,11 @@ def test_run_full_instrument_x_ray_basic(self, mock_sub, mock_stdout): existing file so no error is thrown. """ + # The mocked process needs to set a return-code of zero to avoid the process error checking + process_mock = unittest.mock.Mock() + process_mock.configure_mock(returncode=0) + mock_sub.return_value = process_mock + THIS_DIR = os.path.dirname(os.path.abspath(__file__)) executable_path = os.path.join(THIS_DIR, "dummy_mcstas") diff --git a/mcstasscript/tests/test_ManagedMcrun.py b/mcstasscript/tests/test_ManagedMcrun.py index 70e14255..5957c1c6 100644 --- a/mcstasscript/tests/test_ManagedMcrun.py +++ b/mcstasscript/tests/test_ManagedMcrun.py @@ -167,6 +167,11 @@ def test_ManagedMcrun_run_simulation_basic(self, mock_sub): Check a basic system call is correct """ + # The mocked process needs to set a return-code of zero to avoid the process error checking + process_mock = unittest.mock.Mock() + process_mock.configure_mock(returncode=0) + mock_sub.return_value = process_mock + THIS_DIR = os.path.dirname(os.path.abspath(__file__)) executable_path = os.path.join(THIS_DIR, "dummy_mcstas") @@ -199,6 +204,11 @@ def test_ManagedMcrun_run_simulation_basic_path(self, mock_sub): Check a basic system call is correct, with different path format """ + # The mocked process needs to set a return-code of zero to avoid the process error checking + process_mock = unittest.mock.Mock() + process_mock.configure_mock(returncode=0) + mock_sub.return_value = process_mock + THIS_DIR = os.path.dirname(os.path.abspath(__file__)) executable_path = os.path.join(THIS_DIR, "dummy_mcstas", "") @@ -233,6 +243,11 @@ def test_ManagedMcrun_run_simulation_no_standard(self, mock_sub): be rounded by the class. """ + # The mocked process needs to set a return-code of zero to avoid the process error checking + process_mock = unittest.mock.Mock() + process_mock.configure_mock(returncode=0) + mock_sub.return_value = process_mock + THIS_DIR = os.path.dirname(os.path.abspath(__file__)) executable_path = os.path.join(THIS_DIR, "dummy_mcstas") @@ -270,6 +285,11 @@ def test_ManagedMcrun_run_simulation_with_gravity(self, mock_sub): be rounded by the class. """ + # The mocked process needs to set a return-code of zero to avoid the process error checking + process_mock = unittest.mock.Mock() + process_mock.configure_mock(returncode=0) + mock_sub.return_value = process_mock + THIS_DIR = os.path.dirname(os.path.abspath(__file__)) executable_path = os.path.join(THIS_DIR, "dummy_mcstas") @@ -305,6 +325,11 @@ def test_ManagedMcrun_run_simulation_parameters(self, mock_sub): Check a run with parameters is correct """ + # The mocked process needs to set a return-code of zero to avoid the process error checking + process_mock = unittest.mock.Mock() + process_mock.configure_mock(returncode=0) + mock_sub.return_value = process_mock + THIS_DIR = os.path.dirname(os.path.abspath(__file__)) executable_path = os.path.join(THIS_DIR, "dummy_mcstas") @@ -343,6 +368,11 @@ def test_ManagedMcrun_run_simulation_compile(self, mock_sub): Check run with force_compile set to False works """ + # The mocked process needs to set a return-code of zero to avoid the process error checking + process_mock = unittest.mock.Mock() + process_mock.configure_mock(returncode=0) + mock_sub.return_value = process_mock + THIS_DIR = os.path.dirname(os.path.abspath(__file__)) executable_path = os.path.join(THIS_DIR, "dummy_mcstas") diff --git a/mcstasscript/tests/test_instrument.instr b/mcstasscript/tests/test_instrument.instr index 145148d7..4a761bdb 100644 --- a/mcstasscript/tests/test_instrument.instr +++ b/mcstasscript/tests/test_instrument.instr @@ -15,7 +15,7 @@ * * %Identification * Written by: Python McXtrace Instrument Generator -* Date: 10:17:47 on December 14, 2021 +* Date: 15:04:59 on October 03, 2022 * Origin: ESS DMSC * %INSTRUMENT_SITE: Generated_instruments *