From eaa72666caa1fa5ba53471dfb4143ff60c41a4b2 Mon Sep 17 00:00:00 2001 From: Calvin Pieters Date: Sat, 22 Aug 2026 11:05:19 +0300 Subject: [PATCH 1/7] Isolate ESS job adapter test scratch in per-test temporary directories ARC_TESTING_PATH holds read-only fixture inputs. Passing a subdirectory of it as a project_directory made every test class in these modules write into one shared, fixed path, and tearing that path down with shutil.rmtree at the end of the class. Under pytest-xdist a single TestCase class is split across workers, so every worker runs tearDownClass. One worker therefore deletes the shared directory while another is still writing input files into it, which surfaces as FileExistsError / FileNotFoundError on an unrelated test. A crashed run also leaves the directory behind inside the repository. Each class now allocates its scratch with tempfile.mkdtemp() in setUpClass and releases it with addClassCleanup. mkdtemp is unique per setUpClass invocation, so concurrent workers never share a path and nothing is ever written under arc/testing. Read-only fixture reads (the PBS time-limit err.txt in adapter_test) still come from ARC_TESTING_PATH and are unchanged. --- arc/job/adapter_test.py | 16 ++++++------- arc/job/adapters/cfour_test.py | 14 ++++------- arc/job/adapters/mockter_test.py | 26 +++++++-------------- arc/job/adapters/molpro_test.py | 28 ++++++++-------------- arc/job/adapters/obabel_test.py | 24 +++++++------------ arc/job/adapters/orca_test.py | 40 ++++++++++++++------------------ arc/job/adapters/xtb_test.py | 39 +++++++++++++------------------ 7 files changed, 73 insertions(+), 114 deletions(-) diff --git a/arc/job/adapter_test.py b/arc/job/adapter_test.py index 2691aa129a..bdde72263b 100644 --- a/arc/job/adapter_test.py +++ b/arc/job/adapter_test.py @@ -89,13 +89,13 @@ def setUpClass(cls): A method that is run before all unit tests in this class. """ cls.maxDiff = None - for dir_name in ('test_JobAdapter', 'test_JobAdapter_scan', 'test_JobAdapter_ServerTimeLimit'): - cls.addClassCleanup(shutil.rmtree, os.path.join(ARC_TESTING_PATH, dir_name), ignore_errors=True) + cls.scratch_dir = tempfile.mkdtemp(prefix='arc_test_job_adapter_') + cls.addClassCleanup(shutil.rmtree, cls.scratch_dir, ignore_errors=True) cls.job_1 = GaussianAdapter(execution_type='queue', job_type='conf_opt', level=Level(method='cbs-qb3'), project='test', - project_directory=os.path.join(ARC_TESTING_PATH, 'test_JobAdapter'), + project_directory=os.path.join(cls.scratch_dir, 'test_JobAdapter'), species=[ARCSpecies(label='spc1', xyz=['O 0 0 1', 'O 0 0 2', @@ -124,7 +124,7 @@ def setUpClass(cls): job_type='opt', level=Level(method='cbs-qb3'), project='test', - project_directory=os.path.join(ARC_TESTING_PATH, 'test_JobAdapter'), + project_directory=os.path.join(cls.scratch_dir, 'test_JobAdapter'), species=[ARCSpecies(label='spc1', xyz=['O 0 0 1'])], testing=True, ) @@ -151,7 +151,7 @@ def setUpClass(cls): torsions=[[1, 2, 3, 4]], level=Level(method='wb97xd', basis='def2-tzvp'), project='test_scans', - project_directory=os.path.join(ARC_TESTING_PATH, 'test_JobAdapter_scan'), + project_directory=os.path.join(cls.scratch_dir, 'test_JobAdapter_scan'), species=[cls.spc_3a, cls.spc_3b, cls.spc_3c, cls.spc_3d, cls.spc_3e, cls.spc_3f], testing=True, ) @@ -159,12 +159,12 @@ def setUpClass(cls): job_type='opt', level=Level(method='cbs-qb3'), project='test', - project_directory=os.path.join(ARC_TESTING_PATH, 'test_JobAdapter'), + project_directory=os.path.join(cls.scratch_dir, 'test_JobAdapter'), species=[ARCSpecies(label='spc1', xyz=['O 0 0 1'])], testing=True, ) # Copy the PBS time limit fixture into the directory structure the adapter expects. - stl_dir = os.path.join(ARC_TESTING_PATH, 'test_JobAdapter_ServerTimeLimit') + stl_dir = os.path.join(cls.scratch_dir, 'test_JobAdapter_ServerTimeLimit') err_dest = os.path.join(stl_dir, 'calcs', 'Species', 'spc1', 'opt_101') os.makedirs(err_dest, exist_ok=True) shutil.copy(os.path.join(ARC_TESTING_PATH, 'server', 'pbs', 'timelimit', 'err.txt'), @@ -288,7 +288,7 @@ def test_add_to_args(self): job_type='opt', level=Level(method='cbs-qb3'), project='test', - project_directory=os.path.join(ARC_TESTING_PATH, 'test_JobAdapter'), + project_directory=os.path.join(self.scratch_dir, 'test_JobAdapter'), species=[ARCSpecies(label='spc1', xyz=['O 0 0 1'])], testing=True, args={'keyword': {'general': 'val_tst_1 val_tst_2 val_tst_3'}, diff --git a/arc/job/adapters/cfour_test.py b/arc/job/adapters/cfour_test.py index d2bcde5b0a..70a0b7eaf6 100644 --- a/arc/job/adapters/cfour_test.py +++ b/arc/job/adapters/cfour_test.py @@ -8,9 +8,9 @@ import math import os import shutil +import tempfile import unittest -from arc.common import ARC_TESTING_PATH from arc.job.adapters.cfour import CFourAdapter from arc.level import Level from arc.settings.settings import input_filenames, output_filenames @@ -27,6 +27,8 @@ def setUpClass(cls): A method that is run before all unit tests in this class. """ cls.maxDiff = None + cls.scratch_dir = tempfile.mkdtemp(prefix='arc_test_cfour_') + cls.addClassCleanup(shutil.rmtree, cls.scratch_dir, ignore_errors=True) xyz = {'symbols': ('O', 'C', 'C', 'C', 'H', 'H', 'H', 'H', 'H', 'H', 'H', 'H'), 'isotopes': (16, 12, 12, 12, 1, 1, 1, 1, 1, 1, 1, 1), 'coords': ((2.094965350070438, -0.6820312883655302, 0.41738812543556636), @@ -45,7 +47,7 @@ def setUpClass(cls): job_type='sp', level=Level(method='CCSD(T)', basis='cc-pVTZ'), project='test', - project_directory=os.path.join(ARC_TESTING_PATH, 'test_CFourAdapter'), + project_directory=os.path.join(cls.scratch_dir, 'test_CFourAdapter'), species=[ARCSpecies(label='spc1', xyz=xyz)], testing=True, ) @@ -138,14 +140,6 @@ def test_set_files(self): self.assertEqual(self.job_1.files_to_upload, job_1_files_to_upload) self.assertEqual(self.job_1.files_to_download, job_1_files_to_download) - @classmethod - def tearDownClass(cls): - """ - A function that is run ONCE after all unit tests in this class. - Delete all project directories created during these unit tests - """ - shutil.rmtree(os.path.join(ARC_TESTING_PATH, 'test_CFourAdapter'), ignore_errors=True) - if __name__ == '__main__': unittest.main(testRunner=unittest.TextTestRunner(verbosity=2)) diff --git a/arc/job/adapters/mockter_test.py b/arc/job/adapters/mockter_test.py index 3dd796091f..39abbe75b6 100644 --- a/arc/job/adapters/mockter_test.py +++ b/arc/job/adapters/mockter_test.py @@ -7,9 +7,10 @@ import os import shutil +import tempfile import unittest -from arc.common import ARC_TESTING_PATH, read_yaml_file +from arc.common import read_yaml_file from arc.job.adapters.mockter import MockAdapter from arc.level import Level from arc.reaction.reaction import ARCReaction @@ -26,32 +27,34 @@ def setUpClass(cls): """ A method that is run before all unit tests in this class. """ + cls.scratch_dir = tempfile.mkdtemp(prefix='arc_test_mockter_') + cls.addClassCleanup(shutil.rmtree, cls.scratch_dir, ignore_errors=True) cls.job_1 = MockAdapter(execution_type='incore', job_type='sp', level=Level(method='CCMockSD(T)', basis='cc-pVmockZ'), project='test', - project_directory=os.path.join(ARC_TESTING_PATH, 'test_MockAdapter_1'), + project_directory=os.path.join(cls.scratch_dir, 'test_MockAdapter_1'), species=[ARCSpecies(label='spc1', xyz=['O 0 0 1'], multiplicity=3)], testing=True, ) cls.job_2 = MockAdapter(job_type='opt', level=Level(method='CCMockSD(T)', basis='cc-pVmockZ'), project='test', - project_directory=os.path.join(ARC_TESTING_PATH, 'test_MockAdapter_2'), + project_directory=os.path.join(cls.scratch_dir, 'test_MockAdapter_2'), species=[ARCSpecies(label='spc2', xyz=['O 0 0 1'], multiplicity=3)], testing=True, ) cls.job_3 = MockAdapter(job_type='freq', level=Level(method='CCMockSD(T)', basis='cc-pVmockZ'), project='test', - project_directory=os.path.join(ARC_TESTING_PATH, 'test_MockAdapter_3'), + project_directory=os.path.join(cls.scratch_dir, 'test_MockAdapter_3'), species=[ARCSpecies(label='spc3', xyz=['O 0 0 1\nH 0 0 0\nH 1 0 0'], is_ts=True)], testing=True, ) cls.job_4 = MockAdapter(job_type='tsg', level=Level(method='mock)', basis='cc-pVmockZ'), project='test', - project_directory=os.path.join(ARC_TESTING_PATH, 'test_MockAdapter_4'), + project_directory=os.path.join(cls.scratch_dir, 'test_MockAdapter_4'), reactions=[ARCReaction(r_species=[ARCSpecies(label='O', smiles='[O]'), ARCSpecies(label='CCC', smiles='CCC')], p_species=[ARCSpecies(label='OH', smiles='[OH]'), @@ -180,7 +183,7 @@ def test_mockter_freq_count_matches_geometry(self): job = MockAdapter(job_type='freq', level=Level(method='mock', basis='cc-pVmockZ'), project='test', - project_directory=os.path.join(ARC_TESTING_PATH, f'test_MockAdapter_freq_{name}'), + project_directory=os.path.join(self.scratch_dir, f'test_MockAdapter_freq_{name}'), species=[ARCSpecies(label=name, xyz=[xyz], multiplicity=multiplicity)], testing=True, ) @@ -190,17 +193,6 @@ def test_mockter_freq_count_matches_geometry(self): msg=f'Expected {expected_num_freqs} freqs for the {name} case, ' f'got {output["freqs"]}.') - @classmethod - def tearDownClass(cls): - """ - A function that is run ONCE after all unit tests in this class. - Delete all project directories created during these unit tests - """ - for folder in ['test_MockAdapter_1', 'test_MockAdapter_2', 'test_MockAdapter_3', 'test_MockAdapter_4', - 'test_MockAdapter_freq_mono', 'test_MockAdapter_freq_diatomic', - 'test_MockAdapter_freq_nonlinear', 'test_MockAdapter_freq_linear']: - shutil.rmtree(os.path.join(ARC_TESTING_PATH, folder), ignore_errors=True) - if __name__ == '__main__': unittest.main(testRunner=unittest.TextTestRunner(verbosity=2)) diff --git a/arc/job/adapters/molpro_test.py b/arc/job/adapters/molpro_test.py index 9649875558..80b27592c8 100644 --- a/arc/job/adapters/molpro_test.py +++ b/arc/job/adapters/molpro_test.py @@ -7,9 +7,9 @@ import os import shutil +import tempfile import unittest -from arc.common import ARC_TESTING_PATH from arc.job.adapters.molpro import MolproAdapter from arc.level import Level from arc.settings.settings import input_filenames, output_filenames @@ -26,11 +26,13 @@ def setUpClass(cls): A method that is run before all unit tests in this class. """ cls.maxDiff = None + cls.scratch_dir = tempfile.mkdtemp(prefix='arc_test_molpro_') + cls.addClassCleanup(shutil.rmtree, cls.scratch_dir, ignore_errors=True) cls.job_1 = MolproAdapter(execution_type='queue', job_type='sp', level=Level(method='CCSD(T)-F12', basis='cc-pVTZ-f12'), project='test', - project_directory=os.path.join(ARC_TESTING_PATH, 'test_MolproAdapter_1'), + project_directory=os.path.join(cls.scratch_dir, 'test_MolproAdapter_1'), species=[ARCSpecies(label='spc1', xyz=['O 0 0 1'], multiplicity=3)], testing=True, ) @@ -38,7 +40,7 @@ def setUpClass(cls): job_type='opt', level=Level(method='CCSD(T)', basis='cc-pVQZ'), project='test', - project_directory=os.path.join(ARC_TESTING_PATH, 'test_MolproAdapter_2'), + project_directory=os.path.join(cls.scratch_dir, 'test_MolproAdapter_2'), species=[ARCSpecies(label='spc1', xyz=['O 0 0 1'], multiplicity=3)], testing=True, ) @@ -46,7 +48,7 @@ def setUpClass(cls): job_type='sp', level=Level(method='MRCI', basis='aug-cc-pvtz-f12'), project='test', - project_directory=os.path.join(ARC_TESTING_PATH, 'test_MolproAdapter_3'), + project_directory=os.path.join(cls.scratch_dir, 'test_MolproAdapter_3'), species=[ARCSpecies(label='HNO_t', xyz=["""N -0.08142 0.37454 0.00000 O 1.01258 -0.17285 0.00000 H -0.93116 -0.20169 0.00000"""], @@ -57,7 +59,7 @@ def setUpClass(cls): job_type='sp', level=Level(method='MRCI-F12', basis='aug-cc-pvtz-f12'), project='test', - project_directory=os.path.join(ARC_TESTING_PATH, 'test_MolproAdapter_4'), + project_directory=os.path.join(cls.scratch_dir, 'test_MolproAdapter_4'), species=[ARCSpecies(label='HNO_t', xyz=["""N -0.08142 0.37454 0.00000 O 1.01258 -0.17285 0.00000 H -0.93116 -0.20169 0.00000"""], @@ -68,7 +70,7 @@ def setUpClass(cls): job_type='sp', level=Level(method='MP2_CASSCF_MRCI-F12', basis='aug-cc-pVTZ-F12'), project='test', - project_directory=os.path.join(ARC_TESTING_PATH, 'test_MolproAdapter_5'), + project_directory=os.path.join(cls.scratch_dir, 'test_MolproAdapter_5'), species=[ARCSpecies(label='HNO_t', xyz=["""N -0.08142 0.37454 0.00000 O 1.01258 -0.17285 0.00000 H -0.93116 -0.20169 0.00000"""], @@ -79,7 +81,7 @@ def setUpClass(cls): job_type='sp', level=Level(method='MP2_CASSCF_RS2C', basis='aug-cc-pVTZ'), # CASPT2 project='test', - project_directory=os.path.join(ARC_TESTING_PATH, 'test_MolproAdapter_6'), + project_directory=os.path.join(cls.scratch_dir, 'test_MolproAdapter_6'), species=[ARCSpecies(label='HNO_t', xyz=["""N -0.08142 0.37454 0.00000 O 1.01258 -0.17285 0.00000 H -0.93116 -0.20169 0.00000"""], @@ -90,7 +92,7 @@ def setUpClass(cls): job_type='sp', level=Level(method='MP2_CASSCF_RS2C', basis='aug-cc-pVTZ'), # CASPT2 project='test', - project_directory=os.path.join(ARC_TESTING_PATH, 'test_MolproAdapter_7'), + project_directory=os.path.join(cls.scratch_dir, 'test_MolproAdapter_7'), species=[ARCSpecies(label='N', xyz=["""N 0.0 0.0 0.0"""], multiplicity=4, active={'occ': [3, 1, 1, 0, 1, 0, 0, 0], @@ -462,16 +464,6 @@ def test_set_files(self): self.assertEqual(self.job_1.files_to_upload, job_1_files_to_upload) self.assertEqual(self.job_1.files_to_download, job_1_files_to_download) - @classmethod - def tearDownClass(cls): - """ - A function that is run ONCE after all unit tests in this class. - Delete all project directories created during these unit tests - """ - for attr in vars(cls).values(): - if isinstance(attr, MolproAdapter): - shutil.rmtree(attr.project_directory, ignore_errors=True) - if __name__ == '__main__': unittest.main(testRunner=unittest.TextTestRunner(verbosity=2)) diff --git a/arc/job/adapters/obabel_test.py b/arc/job/adapters/obabel_test.py index 71be5de02c..cf4a09b4bd 100644 --- a/arc/job/adapters/obabel_test.py +++ b/arc/job/adapters/obabel_test.py @@ -7,9 +7,10 @@ import os import shutil +import tempfile import unittest -from arc.common import ARC_TESTING_PATH, read_yaml_file +from arc.common import read_yaml_file from arc.job.adapters.obabel import OpenbabelAdapter from arc.level import Level from arc.settings.settings import ob_default_settings @@ -27,17 +28,19 @@ def setUpClass(cls): A method that is run before all unit tests in this class. """ cls.maxDiff = None + cls.scratch_dir = tempfile.mkdtemp(prefix='arc_test_obabel_') + cls.addClassCleanup(shutil.rmtree, cls.scratch_dir, ignore_errors=True) cls.job_1 = OpenbabelAdapter(execution_type='incore', job_type='sp', project='test_1', - project_directory=os.path.join(ARC_TESTING_PATH, 'test_OpenbabelAdapter_1'), + project_directory=os.path.join(cls.scratch_dir, 'test_OpenbabelAdapter_1'), species=[ARCSpecies(label='EtOH', smiles='CCO')], level=Level(method="MMFF94") ) cls.job_2 = OpenbabelAdapter(execution_type='incore', job_type='opt', project='test_2', - project_directory=os.path.join(ARC_TESTING_PATH, 'test_OpenbabelAdapter_2'), + project_directory=os.path.join(cls.scratch_dir, 'test_OpenbabelAdapter_2'), species=[ARCSpecies(label='EtOH', smiles='CCO')], level=Level(method="MMFF94s") ) @@ -53,7 +56,7 @@ def setUpClass(cls): cls.job_3 = OpenbabelAdapter(execution_type='incore', job_type='opt', project='test_3', - project_directory=os.path.join(ARC_TESTING_PATH, 'test_OpenbabelAdapter_3'), + project_directory=os.path.join(cls.scratch_dir, 'test_OpenbabelAdapter_3'), species=[ARCSpecies(label='EtOH', smiles='CCO', xyz=etoh_xyz)], constraints=[([1, 2, 3], 109), ([2, 3], 1.4), [(3, 2, 1, 5), 179.8]], level=Level(method="ghemical") @@ -61,14 +64,14 @@ def setUpClass(cls): cls.job_4 = OpenbabelAdapter(execution_type='incore', job_type='opt', project='test_4', - project_directory=os.path.join(ARC_TESTING_PATH, 'test_OpenbabelAdapter_4'), + project_directory=os.path.join(cls.scratch_dir, 'test_OpenbabelAdapter_4'), species=[ARCSpecies(label='EtOH', smiles='CCO', xyz=etoh_xyz)], level=Level(method="gaff") ) cls.job_5 = OpenbabelAdapter(execution_type='incore', job_type='sp', project='test_9', - project_directory=os.path.join(ARC_TESTING_PATH, 'test_OpenbabelAdapter_5'), + project_directory=os.path.join(cls.scratch_dir, 'test_OpenbabelAdapter_5'), species=[ARCSpecies(label='EtOH', smiles='CCO')], level=Level(method="MMFF94s") ) @@ -151,15 +154,6 @@ def test_write_input_file(self): self.assertIn('C', content['xyz']) self.assertTrue(content['xyz'].strip().startswith('9')) - @classmethod - def tearDownClass(cls): - """ - A function that is run ONCE after all unit tests in this class. - Delete all project directories created during these unit tests - """ - for i in list(range(1, 6)): - path = os.path.join(ARC_TESTING_PATH, f'test_OpenbabelAdapter_{i}') - shutil.rmtree(path, ignore_errors=True) if __name__ == '__main__': unittest.main(testRunner=unittest.TextTestRunner(verbosity=2)) diff --git a/arc/job/adapters/orca_test.py b/arc/job/adapters/orca_test.py index c0ce422c89..fcf956b364 100644 --- a/arc/job/adapters/orca_test.py +++ b/arc/job/adapters/orca_test.py @@ -9,9 +9,9 @@ import math import os import shutil +import tempfile import unittest -from arc.common import ARC_TESTING_PATH from arc.job.adapters.orca import (OrcaAdapter, _format_orca_basis, _format_orca_basis_token, @@ -32,11 +32,13 @@ def setUpClass(cls): A method that is run before all unit tests in this class. """ cls.maxDiff = None + cls.scratch_dir = tempfile.mkdtemp(prefix='arc_test_orca_') + cls.addClassCleanup(shutil.rmtree, cls.scratch_dir, ignore_errors=True) cls.job_1 = OrcaAdapter(execution_type='queue', job_type='sp', level=Level(method='DLPNO-CCSD(T)', basis='def2-tzvp', auxiliary_basis='def2-tzvp/c'), project='test', - project_directory=os.path.join(ARC_TESTING_PATH, 'test_OrcaAdapter'), + project_directory=os.path.join(cls.scratch_dir, 'test_OrcaAdapter'), species=[ARCSpecies(label='CH3O', xyz="""C 0.03807240 0.00035621 -0.00484242 O 1.35198769 0.01264937 -0.17195885 @@ -50,7 +52,7 @@ def setUpClass(cls): level=Level(method='DLPNO-CCSD(T)', basis='def2-tzvp', auxiliary_basis='def2-tzvp/c', solvation_method='SMD', solvent='DMSO'), project='test', - project_directory=os.path.join(ARC_TESTING_PATH, 'test_OrcaAdapter'), + project_directory=os.path.join(cls.scratch_dir, 'test_OrcaAdapter'), species=[ARCSpecies(label='CH3O', xyz="""C 0.03807240 0.00035621 -0.00484242 O 1.35198769 0.01264937 -0.17195885 @@ -64,7 +66,7 @@ def setUpClass(cls): level=Level(method='DLPNO-CCSD(T)', basis='def2-tzvp', auxiliary_basis='def2-tzvp/c', solvation_method='cpcm', solvent='water'), project='test', - project_directory=os.path.join(ARC_TESTING_PATH, 'test_OrcaAdapter'), + project_directory=os.path.join(cls.scratch_dir, 'test_OrcaAdapter'), species=[ARCSpecies(label='CH3O', xyz="""C 0.03807240 0.00035621 -0.00484242 O 1.35198769 0.01264937 -0.17195885 @@ -77,7 +79,7 @@ def setUpClass(cls): job_type='sp', level=Level(method='MP2_CASSCF_MRCI', basis='aug-cc-pVTZ'), project='test4', - project_directory=os.path.join(ARC_TESTING_PATH, 'test_OrcaAdapter'), + project_directory=os.path.join(cls.scratch_dir, 'test_OrcaAdapter'), species=[ARCSpecies(label='CH3O', active=(14, 7), xyz="""C 0.03807240 0.00035621 -0.00484242 @@ -197,7 +199,7 @@ def test_write_input_file_f12_with_cabs(self): auxiliary_basis='aug-cc-pVTZ/C', cabs='cc-pVTZ-F12-CABS'), project='test_f12', - project_directory=os.path.join(ARC_TESTING_PATH, 'test_OrcaAdapter'), + project_directory=os.path.join(self.scratch_dir, 'test_OrcaAdapter'), species=[ARCSpecies(label='O_atom', smiles='[O]', xyz='O 0.0 0.0 0.0')], testing=True, @@ -223,7 +225,7 @@ def test_write_input_file_f12_without_cabs_raises(self): basis='cc-pVTZ-F12', auxiliary_basis='aug-cc-pVTZ/C'), project='test_f12_bad', - project_directory=os.path.join(ARC_TESTING_PATH, 'test_OrcaAdapter'), + project_directory=os.path.join(self.scratch_dir, 'test_OrcaAdapter'), species=[ARCSpecies(label='O_atom', smiles='[O]', xyz='O 0.0 0.0 0.0')], testing=True, @@ -321,7 +323,7 @@ def test_dft_grid_regular_opt(self): job_type='opt', level=Level(method='wb97x-d3', basis='def2-tzvp'), project='test_dft_grid', - project_directory=os.path.join(ARC_TESTING_PATH, 'test_OrcaAdapter'), + project_directory=os.path.join(self.scratch_dir, 'test_OrcaAdapter'), species=[ARCSpecies(label='CH3O', xyz="""C 0.03807240 0.00035621 -0.00484242 O 1.35198769 0.01264937 -0.17195885 @@ -343,7 +345,7 @@ def test_dft_grid_fine_opt(self): job_type='opt', level=Level(method='wb97x-d3', basis='def2-tzvp'), project='test_dft_grid_fine', - project_directory=os.path.join(ARC_TESTING_PATH, 'test_OrcaAdapter'), + project_directory=os.path.join(self.scratch_dir, 'test_OrcaAdapter'), species=[ARCSpecies(label='CH3O', xyz="""C 0.03807240 0.00035621 -0.00484242 O 1.35198769 0.01264937 -0.17195885 @@ -364,7 +366,7 @@ def test_dft_grid_freq(self): job_type='freq', level=Level(method='wb97x-d3', basis='def2-tzvp'), project='test_dft_grid_freq', - project_directory=os.path.join(ARC_TESTING_PATH, 'test_OrcaAdapter'), + project_directory=os.path.join(self.scratch_dir, 'test_OrcaAdapter'), species=[ARCSpecies(label='CH3O', xyz="""C 0.03807240 0.00035621 -0.00484242 O 1.35198769 0.01264937 -0.17195885 @@ -385,7 +387,7 @@ def test_dft_grid_optfreq(self): job_type='optfreq', level=Level(method='wb97x-d3', basis='def2-tzvp'), project='test_dft_grid_optfreq', - project_directory=os.path.join(ARC_TESTING_PATH, 'test_OrcaAdapter'), + project_directory=os.path.join(self.scratch_dir, 'test_OrcaAdapter'), species=[ARCSpecies(label='CH3O', xyz="""C 0.03807240 0.00035621 -0.00484242 O 1.35198769 0.01264937 -0.17195885 @@ -406,7 +408,7 @@ def test_fine_opt_convergence_tightopt(self): job_type='opt', level=Level(method='wb97x-d3', basis='def2-tzvp'), project='test_fine_opt_conv', - project_directory=os.path.join(ARC_TESTING_PATH, 'test_OrcaAdapter'), + project_directory=os.path.join(self.scratch_dir, 'test_OrcaAdapter'), species=[ARCSpecies(label='CH3O', xyz="""C 0.03807240 0.00035621 -0.00484242 O 1.35198769 0.01264937 -0.17195885 @@ -428,7 +430,7 @@ def test_recalc_hess_in_optts(self): job_type='opt', level=Level(method='wb97x-d3', basis='def2-tzvp'), project='test_optts_hess', - project_directory=os.path.join(ARC_TESTING_PATH, 'test_OrcaAdapter'), + project_directory=os.path.join(self.scratch_dir, 'test_OrcaAdapter'), species=[ARCSpecies(label='TS_example', xyz="""C 0.03807240 0.00035621 -0.00484242 O 1.35198769 0.01264937 -0.17195885 @@ -454,7 +456,7 @@ def test_recalc_hess_not_in_regular_opt(self): job_type='opt', level=Level(method='wb97x-d3', basis='def2-tzvp'), project='test_opt_no_hess', - project_directory=os.path.join(ARC_TESTING_PATH, 'test_OrcaAdapter'), + project_directory=os.path.join(self.scratch_dir, 'test_OrcaAdapter'), species=[ARCSpecies(label='CH3O', xyz="""C 0.03807240 0.00035621 -0.00484242 O 1.35198769 0.01264937 -0.17195885 @@ -483,7 +485,7 @@ def test_writing_input_does_not_pollute_level_args(self): job_type='opt', level=level, project='test', - project_directory=os.path.join(ARC_TESTING_PATH, 'test_OrcaAdapter'), + project_directory=os.path.join(self.scratch_dir, 'test_OrcaAdapter'), species=[ARCSpecies(label='CH3O', xyz="""C 0.03807240 0.00035621 -0.00484242 O 1.35198769 0.01264937 -0.17195885 @@ -497,14 +499,6 @@ def test_writing_input_does_not_pollute_level_args(self): self.assertEqual(level.args, {'keyword': dict(), 'block': dict()}) self.assertNotIn('args', level.as_dict()) - @classmethod - def tearDownClass(cls): - """ - A function that is run ONCE after all unit tests in this class. - Delete all project directories created during these unit tests - """ - shutil.rmtree(os.path.join(ARC_TESTING_PATH, 'test_OrcaAdapter'), ignore_errors=True) - if __name__ == '__main__': unittest.main(testRunner=unittest.TextTestRunner(verbosity=2)) diff --git a/arc/job/adapters/xtb_test.py b/arc/job/adapters/xtb_test.py index b4b4937f14..ffc803cb93 100644 --- a/arc/job/adapters/xtb_test.py +++ b/arc/job/adapters/xtb_test.py @@ -7,9 +7,10 @@ import os import shutil +import tempfile import unittest -from arc.common import ARC_TESTING_PATH, almost_equal_coords +from arc.common import almost_equal_coords from arc.job.adapters.xtb_adapter import xTBAdapter from arc.level import Level from arc.parser.parser import parse_e_elect, parse_frequencies, parse_geometry @@ -27,11 +28,13 @@ def setUpClass(cls): A method that is run before all unit tests in this class. """ cls.maxDiff = None + cls.scratch_dir = tempfile.mkdtemp(prefix='arc_test_xtb_') + cls.addClassCleanup(shutil.rmtree, cls.scratch_dir, ignore_errors=True) cls.job_1 = xTBAdapter(execution_type='queue', job_type='sp', project='test_1', level=Level(method='gfn1', args={'keyword': {'parallel': 'no'}}), - project_directory=os.path.join(ARC_TESTING_PATH, 'test_xTBAdapter_1'), + project_directory=os.path.join(cls.scratch_dir, 'test_xTBAdapter_1'), species=[ARCSpecies(label='spc1', xyz=['O 0 0 1'], multiplicity=3)], testing=True, ) @@ -39,7 +42,7 @@ def setUpClass(cls): job_type='opt', project='test_2', fine=True, - project_directory=os.path.join(ARC_TESTING_PATH, 'test_xTBAdapter_2'), + project_directory=os.path.join(cls.scratch_dir, 'test_xTBAdapter_2'), species=[ARCSpecies(label='spc2', smiles='CC[O]')], testing=True, ) @@ -47,7 +50,7 @@ def setUpClass(cls): job_type='scan', project='test_3', torsions=[[0, 1, 2, 3]], - project_directory=os.path.join(ARC_TESTING_PATH, 'test_xTBAdapter_3'), + project_directory=os.path.join(cls.scratch_dir, 'test_xTBAdapter_3'), species=[ARCSpecies(label='spc1', xyz="""O 1.09412318 -0.31048292 0.52221323 N -0.00341051 0.01666411 0.06406731 O -0.88296129 -0.72127547 -0.38686359 @@ -60,27 +63,27 @@ def setUpClass(cls): torsions=[[5, 6, 1, 4]], project='test_4', fine=True, - project_directory=os.path.join(ARC_TESTING_PATH, 'test_xTBAdapter_4'), + project_directory=os.path.join(cls.scratch_dir, 'test_xTBAdapter_4'), species=[ARCSpecies(label='EtOH', smiles='CCO')], testing=True, ) cls.job_5 = xTBAdapter(execution_type='incore', job_type='sp', project='test_5', - project_directory=os.path.join(ARC_TESTING_PATH, 'test_xTBAdapter_5'), + project_directory=os.path.join(cls.scratch_dir, 'test_xTBAdapter_5'), species=[ARCSpecies(label='NCC', smiles='NCC')] ) cls.job_6 = xTBAdapter(execution_type='incore', job_type='freq', project='test_6', - project_directory=os.path.join(ARC_TESTING_PATH, 'test_xTBAdapter_6'), + project_directory=os.path.join(cls.scratch_dir, 'test_xTBAdapter_6'), species=[ARCSpecies(label='HO2', smiles='O[O]')] ) cls.job_7 = xTBAdapter(execution_type='queue', job_type='opt', project='test_7', fine=True, - project_directory=os.path.join(ARC_TESTING_PATH, 'test_xTBAdapter_7'), + project_directory=os.path.join(cls.scratch_dir, 'test_xTBAdapter_7'), species=[ARCSpecies(label='spc7', xyz='O 0 0 1', multiplicity=3)], testing=True, ) @@ -89,7 +92,7 @@ def setUpClass(cls): cls.job_8 = xTBAdapter(job_type='scan', level=Level(method='xtb'), project='test_8', - project_directory=os.path.join(ARC_TESTING_PATH, 'test_xTBAdapter_8'), + project_directory=os.path.join(cls.scratch_dir, 'test_xTBAdapter_8'), species=[spc_8], rotor_index=0, ) @@ -97,14 +100,14 @@ def setUpClass(cls): job_type='opt', project='test_9', level=Level(method='xtb', solvent='water', solvation_method="alpb"), - project_directory=os.path.join(ARC_TESTING_PATH, 'test_xTBAdapter_9'), + project_directory=os.path.join(cls.scratch_dir, 'test_xTBAdapter_9'), species=[ARCSpecies(label='spc2', smiles='CC[O]')], ) cls.job_10 = xTBAdapter(execution_type='incore', job_type='opt', project='test_10', level=Level(method='gfn2'), - project_directory=os.path.join(ARC_TESTING_PATH, 'test_xTBAdapter_10'), + project_directory=os.path.join(cls.scratch_dir, 'test_xTBAdapter_10'), species=[ARCSpecies(label='TS', is_ts=True, xyz="""C -1.317288 -0.260819 -0.032638 C -0.041481 0.531329 0.023478 @@ -134,14 +137,14 @@ def setUpClass(cls): job_type='freq', project='test_11', level=Level(method='gfn2'), - project_directory=os.path.join(ARC_TESTING_PATH, 'test_xTBAdapter_11'), + project_directory=os.path.join(cls.scratch_dir, 'test_xTBAdapter_11'), species=[ARCSpecies(label='TS', is_ts=True, xyz=cls.ts_xyz)], ) cls.job_12 = xTBAdapter(execution_type='incore', job_type='freq', project='test_12', level=Level(method='gfn2'), - project_directory=os.path.join(ARC_TESTING_PATH, 'test_xTBAdapter_12'), + project_directory=os.path.join(cls.scratch_dir, 'test_xTBAdapter_12'), species=[ARCSpecies(label='H2', smiles='[H][H]')], ) @@ -287,16 +290,6 @@ def test_scan(self): self.job_8.execute() self.assertTrue(os.path.isfile(os.path.join(self.job_8.local_path, 'xtbscan.log'))) - @classmethod - def tearDownClass(cls): - """ - A function that is run ONCE after all unit tests in this class. - Delete all project directories created during these unit tests - """ - for i in range(20): - path = os.path.join(ARC_TESTING_PATH, f'test_xTBAdapter_{i}') - shutil.rmtree(path, ignore_errors=True) - if __name__ == '__main__': unittest.main(testRunner=unittest.TextTestRunner(verbosity=2)) From 3bb0f253a80ed1c5df02a9be19bd18078c391db3 Mon Sep 17 00:00:00 2001 From: Calvin Pieters Date: Sat, 22 Aug 2026 11:05:19 +0300 Subject: [PATCH 2/7] Isolate TS adapter test scratch in per-test temporary directories Same change as for the ESS job adapters: the TS search adapter tests wrote their project directories into ARC_TESTING_PATH and removed them from tearDownClass or from ad-hoc per-test cleanup helpers. Scratch now comes from tempfile.mkdtemp() and is released with addClassCleanup / addCleanup. The KinBot helper that removed a single test's subdirectory and then attempted to rmdir the shared parent existed only to make the shared parent safe under xdist; with a per-class temporary root the whole tree is released at once and the helper is dropped. gcn_test.py also drops the `from arc.job.adapters.ts.gcn_ts import GCNAdapter` line, which duplicated the `import arc.job.adapters.ts.gcn_ts as ts_gcn` already present; kinbot_test.py imports unittest.mock as mock rather than `from unittest import mock` alongside `import unittest`. --- arc/job/adapters/ts/gcn_test.py | 24 ++++----- arc/job/adapters/ts/heuristics_test.py | 70 ++++++++++++-------------- arc/job/adapters/ts/kinbot_test.py | 22 ++------ arc/job/adapters/ts/orca_neb_test.py | 7 +-- arc/job/adapters/ts/xtbgsm_test.py | 11 ++-- 5 files changed, 55 insertions(+), 79 deletions(-) diff --git a/arc/job/adapters/ts/gcn_test.py b/arc/job/adapters/ts/gcn_test.py index 27498871fd..ff8255182d 100644 --- a/arc/job/adapters/ts/gcn_test.py +++ b/arc/job/adapters/ts/gcn_test.py @@ -9,12 +9,12 @@ import os import shutil import subprocess +import tempfile import unittest from unittest.mock import patch from arc.common import ARC_PATH, ARC_TESTING_PATH, read_yaml_file import arc.job.adapters.ts.gcn_ts as ts_gcn -from arc.job.adapters.ts.gcn_ts import GCNAdapter from arc.reaction import ARCReaction from arc.species.converter import str_to_xyz from arc.species.species import ARCSpecies, TSGuess @@ -72,8 +72,7 @@ def setUp(self): Tests run in parallel (pytest-xdist), so each test gets its own directory. """ self.maxDiff = None - self.output_dir = os.path.join(ARC_TESTING_PATH, f'GCN_{self._testMethodName}') - os.makedirs(self.output_dir, exist_ok=True) + self.output_dir = tempfile.mkdtemp(prefix='arc_test_gcn_') self.addCleanup(shutil.rmtree, self.output_dir, ignore_errors=True) self.reactant_path = os.path.join(self.output_dir, 'react.sdf') self.product_path = os.path.join(self.output_dir, 'prod.sdf') @@ -85,16 +84,16 @@ def get_reaction() -> ARCReaction: return ARCReaction(r_species=[ARCSpecies(label='nC3H7', smiles='[CH2]CC')], p_species=[ARCSpecies(label='iC3H7', smiles='C[CH]C')]) - def get_adapter(self, rxn: ARCReaction) -> GCNAdapter: + def get_adapter(self, rxn: ARCReaction) -> ts_gcn.GCNAdapter: """Get a GCNAdapter instance for testing.""" project_dir = os.path.join(self.output_dir, 'project') - return GCNAdapter(job_type='tsg', - reactions=[rxn], - testing=True, - project='test_GCNAdapter', - project_directory=project_dir, - dihedral_increment=1, - ) + return ts_gcn.GCNAdapter(job_type='tsg', + reactions=[rxn], + testing=True, + project='test_GCNAdapter', + project_directory=project_dir, + dihedral_increment=1, + ) def test_gcn_available(self): """Test the gcn_available() function.""" @@ -238,8 +237,7 @@ def setUp(self): A method that is run before each unit test in this class. """ self.maxDiff = None - self.output_dir = os.path.join(ARC_TESTING_PATH, f'GCN_script_{self._testMethodName}') - os.makedirs(self.output_dir, exist_ok=True) + self.output_dir = tempfile.mkdtemp(prefix='arc_test_gcn_script_') self.addCleanup(shutil.rmtree, self.output_dir, ignore_errors=True) self.gcn_script = load_gcn_script() diff --git a/arc/job/adapters/ts/heuristics_test.py b/arc/job/adapters/ts/heuristics_test.py index ea7de2d8b5..8369ee95c2 100644 --- a/arc/job/adapters/ts/heuristics_test.py +++ b/arc/job/adapters/ts/heuristics_test.py @@ -9,9 +9,10 @@ import itertools import os import shutil +import tempfile import unittest -from arc.common import ARC_TESTING_PATH, almost_equal_coords +from arc.common import almost_equal_coords from arc.family import get_reaction_family_products from arc.job.adapters.ts.heuristics import (HeuristicsAdapter, are_h_abs_wells_reversed, @@ -53,6 +54,8 @@ def setUpClass(cls): A method that is run before all unit tests in this class. """ cls.maxDiff = None + cls.scratch_dir = tempfile.mkdtemp(prefix='arc_test_heuristics_') + cls.addClassCleanup(shutil.rmtree, cls.scratch_dir, ignore_errors=True) cls.oh_xyz = """O 0.0000000 0.0000000 0.1078170 H 0.0000000 0.0000000 -0.8625320""" cls.h2o_xyz = """O -0.00032832 0.39781490 0.00000000 @@ -468,7 +471,7 @@ def test_heuristics_for_h_abstraction_1(self): reactions=[rxn1], testing=True, project='test', - project_directory=os.path.join(ARC_TESTING_PATH, 'heuristics'), + project_directory=os.path.join(self.scratch_dir, 'heuristics'), dihedral_increment=10, ) heuristics_1.execute_incore() @@ -487,7 +490,7 @@ def test_heuristics_for_h_abstraction_1(self): reactions=[rxn2], testing=True, project='test', - project_directory=os.path.join(ARC_TESTING_PATH, 'heuristics'), + project_directory=os.path.join(self.scratch_dir, 'heuristics'), dihedral_increment=10, ) heuristics_2.execute_incore() @@ -505,7 +508,7 @@ def test_heuristics_for_h_abstraction_1(self): reactions=[rxn3], testing=True, project='test', - project_directory=os.path.join(ARC_TESTING_PATH, 'heuristics'), + project_directory=os.path.join(self.scratch_dir, 'heuristics'), dihedral_increment=10, ) heuristics_3.execute_incore() @@ -531,7 +534,7 @@ def test_heuristics_for_h_abstraction_1(self): reactions=[rxn4], testing=True, project='test', - project_directory=os.path.join(ARC_TESTING_PATH, 'heuristics'), + project_directory=os.path.join(self.scratch_dir, 'heuristics'), dihedral_increment=120, ) heuristics_4.execute_incore() @@ -601,7 +604,7 @@ def test_heuristics_for_h_abstraction_2(self): reactions=[rxn5], testing=True, project='test', - project_directory=os.path.join(ARC_TESTING_PATH, 'heuristics'), + project_directory=os.path.join(self.scratch_dir, 'heuristics'), dihedral_increment=120, ) heuristics_5.execute_incore() @@ -653,7 +656,7 @@ def test_heuristics_for_h_abstraction_3(self): reactions=[rxn6], testing=True, project='test', - project_directory=os.path.join(ARC_TESTING_PATH, 'heuristics'), + project_directory=os.path.join(self.scratch_dir, 'heuristics'), dihedral_increment=20, ) heuristics_6.execute_incore() @@ -688,7 +691,7 @@ def test_heuristics_for_h_abstraction_4(self): reactions=[rxn7], testing=True, project='test', - project_directory=os.path.join(ARC_TESTING_PATH, 'heuristics'), + project_directory=os.path.join(self.scratch_dir, 'heuristics'), dihedral_increment=120, ) heuristics_7.execute_incore() @@ -722,7 +725,7 @@ def test_heuristics_for_h_abstraction_5(self): reactions=[rxn8], testing=True, project='test', - project_directory=os.path.join(ARC_TESTING_PATH, 'heuristics'), + project_directory=os.path.join(self.scratch_dir, 'heuristics'), dihedral_increment=120, ) heuristics_8.execute_incore() @@ -861,7 +864,7 @@ def test_heuristics_for_h_abstraction_6(self): reactions=[rxn9], testing=True, project='test', - project_directory=os.path.join(ARC_TESTING_PATH, 'heuristics'), + project_directory=os.path.join(self.scratch_dir, 'heuristics'), dihedral_increment=120, ) heuristics_9.execute_incore() @@ -877,7 +880,7 @@ def test_heuristics_for_h_abstraction_6(self): reactions=[rxn10], testing=True, project='test', - project_directory=os.path.join(ARC_TESTING_PATH, 'heuristics'), + project_directory=os.path.join(self.scratch_dir, 'heuristics'), dihedral_increment=120, ) heuristics_10.execute_incore() @@ -930,7 +933,7 @@ def test_heuristics_for_h_abstraction_7(self): reactions=[rxn11], testing=True, project='test', - project_directory=os.path.join(ARC_TESTING_PATH, 'heuristics'), + project_directory=os.path.join(self.scratch_dir, 'heuristics'), dihedral_increment=30, ) heuristics_11.execute_incore() @@ -953,7 +956,7 @@ def test_heuristics_for_h_abstraction_8(self): reactions=[rxn12], testing=True, project='test', - project_directory=os.path.join(ARC_TESTING_PATH, 'heuristics'), + project_directory=os.path.join(self.scratch_dir, 'heuristics'), dihedral_increment=60, ) heuristics_12.execute_incore() @@ -973,7 +976,7 @@ def test_heuristics_for_h_abstraction_8(self): reactions=[rxn13], testing=True, project='test', - project_directory=os.path.join(ARC_TESTING_PATH, 'heuristics'), + project_directory=os.path.join(self.scratch_dir, 'heuristics'), dihedral_increment=180, ) heuristics_13.execute_incore() @@ -991,7 +994,7 @@ def test_heuristics_for_h_abstraction_8(self): reactions=[rxn14], testing=True, project='test', - project_directory=os.path.join(ARC_TESTING_PATH, 'heuristics'), + project_directory=os.path.join(self.scratch_dir, 'heuristics'), dihedral_increment=180, ) heuristics_14.execute_incore() @@ -1009,7 +1012,7 @@ def test_heuristics_for_h_abstraction_8(self): reactions=[rxn15], testing=True, project='test', - project_directory=os.path.join(ARC_TESTING_PATH, 'heuristics'), + project_directory=os.path.join(self.scratch_dir, 'heuristics'), dihedral_increment=180, ) heuristics_15.execute_incore() @@ -1034,7 +1037,7 @@ def test_heuristics_for_h_abstraction_9(self): reactions=[rxn16], testing=True, project='test', - project_directory=os.path.join(ARC_TESTING_PATH, 'heuristics'), + project_directory=os.path.join(self.scratch_dir, 'heuristics'), dihedral_increment=360, ) heuristics_16.execute_incore() @@ -1061,7 +1064,7 @@ def test_heuristics_for_h_abstraction_10(self): reactions=[rxn17], testing=True, project='test', - project_directory=os.path.join(ARC_TESTING_PATH, 'heuristics'), + project_directory=os.path.join(self.scratch_dir, 'heuristics'), dihedral_increment=60, ) heuristics_16.execute_incore() @@ -1081,7 +1084,7 @@ def test_heuristics_for_h_abstraction_11(self): reactions=[rxn1], testing=True, project='test', - project_directory=os.path.join(ARC_TESTING_PATH, 'heuristics'), + project_directory=os.path.join(self.scratch_dir, 'heuristics'), dihedral_increment=60, ) heuristics_1.execute_incore() @@ -1100,7 +1103,7 @@ def test_heuristics_for_h_abstraction_12(self): reactions=[rxn1], testing=True, project='test', - project_directory=os.path.join(ARC_TESTING_PATH, 'heuristics'), + project_directory=os.path.join(self.scratch_dir, 'heuristics'), dihedral_increment=60, ) heuristics_1.execute_incore() @@ -1119,7 +1122,7 @@ def test_heuristics_for_h_abstraction_13(self): reactions=[rxn1], testing=True, project='test', - project_directory=os.path.join(ARC_TESTING_PATH, 'heuristics'), + project_directory=os.path.join(self.scratch_dir, 'heuristics'), dihedral_increment=60, ) heuristics_1.execute_incore() @@ -1135,7 +1138,7 @@ def test_heuristics_for_h_abstraction_13(self): reactions=[rxn1], testing=True, project='test', - project_directory=os.path.join(ARC_TESTING_PATH, 'heuristics'), + project_directory=os.path.join(self.scratch_dir, 'heuristics'), dihedral_increment=120, ) heuristics_1.execute_incore() @@ -1152,7 +1155,7 @@ def test_heuristics_for_carbonyl_based_hydrolysis(self): reactions=[rxn], testing=True, project='test', - project_directory=os.path.join(ARC_TESTING_PATH, 'heuristics_carbonyl')) + project_directory=os.path.join(self.scratch_dir, 'heuristics_carbonyl')) adapter.execute_incore() self.assertEqual(rxn.family, 'carbonyl_based_hydrolysis') self.assertTrue(rxn.ts_species.is_ts) @@ -1170,7 +1173,7 @@ def test_heuristics_for_ether_hydrolysis(self): reactions=[rxn], testing=True, project='test', - project_directory=os.path.join(ARC_TESTING_PATH, 'heuristics_ether')) + project_directory=os.path.join(self.scratch_dir, 'heuristics_ether')) adapter.execute_incore() self.assertEqual(rxn.family, 'ether_hydrolysis') self.assertTrue(rxn.ts_species.is_ts) @@ -1188,7 +1191,7 @@ def test_heuristics_for_nitrile_hydrolysis(self): reactions=[rxn], testing=True, project='test', - project_directory=os.path.join(ARC_TESTING_PATH, 'heuristics_nitrile')) + project_directory=os.path.join(self.scratch_dir, 'heuristics_nitrile')) adapter.execute_incore() self.assertEqual(rxn.family, 'nitrile_hydrolysis') self.assertTrue(rxn.ts_species.is_ts) @@ -1213,7 +1216,7 @@ def test_keeping_atom_order_in_ts(self): reactions=[rxn_1], testing=True, project='test_1', - project_directory=os.path.join(ARC_TESTING_PATH, 'heuristics_1'), + project_directory=os.path.join(self.scratch_dir, 'heuristics_1'), dihedral_increment=120, ) heuristics_1.execute_incore() @@ -1237,7 +1240,7 @@ def test_keeping_atom_order_in_ts(self): reactions=[rxn_2], testing=True, project='test_1', - project_directory=os.path.join(ARC_TESTING_PATH, 'heuristics_1'), + project_directory=os.path.join(self.scratch_dir, 'heuristics_1'), dihedral_increment=120, ) heuristics_2.execute_incore() @@ -1261,7 +1264,7 @@ def test_keeping_atom_order_in_ts(self): reactions=[rxn_3], testing=True, project='test_1', - project_directory=os.path.join(ARC_TESTING_PATH, 'heuristics_1'), + project_directory=os.path.join(self.scratch_dir, 'heuristics_1'), dihedral_increment=120, ) heuristics_3.execute_incore() @@ -1284,7 +1287,7 @@ def test_keeping_atom_order_in_ts(self): reactions=[rxn_4], testing=True, project='test_1', - project_directory=os.path.join(ARC_TESTING_PATH, 'heuristics_1'), + project_directory=os.path.join(self.scratch_dir, 'heuristics_1'), dihedral_increment=120, ) heuristics_4.execute_incore() @@ -2255,15 +2258,6 @@ def test_check_ts_bonds(self): result = check_ts_bonds(initial_xyz, [7, 8, 9, 2, 4]) self.assertTrue(result) - @classmethod - def tearDownClass(cls): - """ - A function that is run ONCE after all unit tests in this class. - Delete all project directories created during these unit tests. - """ - for sub in ('heuristics', 'heuristics_1', 'heuristics_carbonyl', 'heuristics_ether', 'heuristics_nitrile'): - shutil.rmtree(os.path.join(ARC_TESTING_PATH, sub), ignore_errors=True) - if __name__ == '__main__': unittest.main(testRunner=unittest.TextTestRunner(verbosity=2)) diff --git a/arc/job/adapters/ts/kinbot_test.py b/arc/job/adapters/ts/kinbot_test.py index 5b8ddaded3..d2616097c3 100644 --- a/arc/job/adapters/ts/kinbot_test.py +++ b/arc/job/adapters/ts/kinbot_test.py @@ -9,16 +9,15 @@ import os import shutil import subprocess +import tempfile import unittest -from unittest import mock +import unittest.mock as mock -from arc.common import ARC_TESTING_PATH, get_logger, read_yaml_file, save_yaml_file +from arc.common import read_yaml_file, save_yaml_file import arc.job.adapters.ts.kinbot_ts as kinbot_ts from arc.reaction import ARCReaction from arc.species import ARCSpecies -logger = get_logger() - def kinbot_list_to_coords(structure: list) -> list: """A helper function to convert a flat KinBot structure list into an N x 3 coordinates list.""" @@ -39,7 +38,8 @@ def setUpClass(cls): A method that is run before all unit tests in this class. """ cls.maxDiff = None - cls.project_dir = os.path.join(ARC_TESTING_PATH, 'test_KinBot') + cls.project_dir = tempfile.mkdtemp(prefix='arc_test_kinbot_') + cls.addClassCleanup(shutil.rmtree, cls.project_dir, ignore_errors=True) def setUp(self): """ @@ -49,21 +49,9 @@ def setUp(self): r_species=[ARCSpecies(label='CC[O]', smiles='CC[O]')], p_species=[ARCSpecies(label='[CH2]CO', smiles='[CH2]CO')]) - def _remove_test_dir(self, path: str): - """A helper function to remove a single test's project directory (and the shared - parent directory if it is empty). Tests may run in parallel (pytest-xdist), so - each test must only ever remove its own subdirectory.""" - shutil.rmtree(path, ignore_errors=True) - try: - os.rmdir(self.project_dir) - except OSError: - logger.debug(f'Could not remove shared parent dir {self.project_dir} during teardown ' - f'(non-empty or already removed by a parallel worker).') - def get_adapter(self, dir_name: str) -> kinbot_ts.KinBotAdapter: """A helper function to instantiate a KinBotAdapter instance.""" project_directory = os.path.join(self.project_dir, dir_name) - self.addCleanup(self._remove_test_dir, project_directory) return kinbot_ts.KinBotAdapter(job_type='tsg', reactions=[self.rxn_1], testing=True, diff --git a/arc/job/adapters/ts/orca_neb_test.py b/arc/job/adapters/ts/orca_neb_test.py index 1930c58dc6..468344dc4b 100644 --- a/arc/job/adapters/ts/orca_neb_test.py +++ b/arc/job/adapters/ts/orca_neb_test.py @@ -8,11 +8,11 @@ import os import shutil import datetime +import tempfile import unittest import unittest.mock import pytest -from arc.common import ARC_TESTING_PATH from arc.job.adapters.ts.orca_neb import OrcaNEBAdapter from arc.level import Level from arc.reaction import ARCReaction @@ -31,11 +31,8 @@ def setUpClass(cls): """ cls.maxDiff = None - cls.project_directory = os.path.join(ARC_TESTING_PATH, 'test_OrcaNEBAdapter') - if os.path.exists(cls.project_directory): - shutil.rmtree(cls.project_directory) + cls.project_directory = tempfile.mkdtemp(prefix='arc_test_orca_neb_') cls.addClassCleanup(shutil.rmtree, cls.project_directory, ignore_errors=True) - os.makedirs(cls.project_directory) # Mock objects for both orca_neb and orca/adapter modules mock_input_filenames = {'orca_neb': 'input.in', 'orca': 'input.in'} diff --git a/arc/job/adapters/ts/xtbgsm_test.py b/arc/job/adapters/ts/xtbgsm_test.py index 4355fab580..4a06fbdb27 100644 --- a/arc/job/adapters/ts/xtbgsm_test.py +++ b/arc/job/adapters/ts/xtbgsm_test.py @@ -7,9 +7,9 @@ import os import shutil +import tempfile import unittest -from arc.common import ARC_TESTING_PATH from arc.job.adapters.ts.xtb_gsm import xTBGSMAdapter from arc.level import Level from arc.parser.parser import parse_trajectory @@ -28,12 +28,11 @@ def setUpClass(cls): A method that is run before all unit tests in this class. """ cls.maxDiff = None - for i in range(10): - cls.addClassCleanup(shutil.rmtree, os.path.join(ARC_TESTING_PATH, f'test_xTBAGSMdapter_{i}'), - ignore_errors=True) + cls.scratch_dir = tempfile.mkdtemp(prefix='arc_test_xtb_gsm_') + cls.addClassCleanup(shutil.rmtree, cls.scratch_dir, ignore_errors=True) cls.job_1 = xTBGSMAdapter(project='test_1', job_type='tsg', - project_directory=os.path.join(ARC_TESTING_PATH, 'test_xTBAGSMdapter_1'), + project_directory=os.path.join(cls.scratch_dir, 'test_xTBAGSMdapter_1'), reactions=[ARCReaction(r_species=[ARCSpecies(label='HNO', smiles='N=O')], p_species=[ARCSpecies(label='HON', smiles='[N-]=[OH+]')])], ) @@ -46,7 +45,7 @@ def setUpClass(cls): 'add_node_tol': 0.5, 'final_opt': 10, 'nnodes': 9}}), - project_directory=os.path.join(ARC_TESTING_PATH, 'test_xTBAGSMdapter_2'), + project_directory=os.path.join(cls.scratch_dir, 'test_xTBAGSMdapter_2'), reactions=[ARCReaction(r_species=[ARCSpecies(label='HNO', smiles='N=O')], p_species=[ARCSpecies(label='HON', smiles='[N-]=[OH+]')])], ) From 891b2f0fd52036ee2691aa62de7de93d60f77d14 Mon Sep 17 00:00:00 2001 From: Calvin Pieters Date: Sat, 22 Aug 2026 11:05:19 +0300 Subject: [PATCH 3/7] Isolate ASE, PySCF and UMA adapter test scratch in temporary directories These modules built their project directories under ARC_TESTING_PATH and removed them in tearDownClass, which is not safe when xdist splits the class across workers. Both UMA test classes now allocate their own mkdtemp root, so the env-independent and model-dependent classes can no longer reach into each other's tree either. --- arc/job/adapters/ase_test.py | 16 ++++------------ arc/job/adapters/pyscf_test.py | 16 ++++------------ arc/job/adapters/uma_test.py | 20 ++++++-------------- 3 files changed, 14 insertions(+), 38 deletions(-) diff --git a/arc/job/adapters/ase_test.py b/arc/job/adapters/ase_test.py index d0e6a542fc..aba745e373 100644 --- a/arc/job/adapters/ase_test.py +++ b/arc/job/adapters/ase_test.py @@ -8,11 +8,12 @@ import os import shutil +import tempfile import unittest from unittest.mock import patch import numpy as np -from arc.common import ARC_TESTING_PATH, read_yaml_file, save_yaml_file +from arc.common import read_yaml_file, save_yaml_file from arc.job.adapters.ase_adapter import ASEAdapter from arc.species.species import ARCSpecies from arc.job.adapters.scripts.ase_script import to_kJmol, numpy_vibrational_analysis, is_linear @@ -29,9 +30,8 @@ def setUpClass(cls): A method that is run before all unit tests in this class. """ cls.maxDiff = None - cls.project_directory = os.path.join(ARC_TESTING_PATH, 'test_ASEAdapter') - if not os.path.exists(cls.project_directory): - os.makedirs(cls.project_directory) + cls.project_directory = tempfile.mkdtemp(prefix='arc_test_ase_') + cls.addClassCleanup(shutil.rmtree, cls.project_directory, ignore_errors=True) xyz = {'symbols': ('O', 'H', 'H'), 'isotopes': (16, 1, 1), @@ -204,14 +204,6 @@ def test_numpy_vibrational_analysis(self): for i, val in enumerate(freqs[5:]): self.assertAlmostEqual(results['freqs'][i], val, delta=1e-3) - @classmethod - def tearDownClass(cls): - """ - A function that is run ONCE after all unit tests in this class. - Delete all project directories created during these unit tests - """ - shutil.rmtree(cls.project_directory, ignore_errors=True) - if __name__ == '__main__': unittest.main(testRunner=unittest.TextTestRunner(verbosity=2)) diff --git a/arc/job/adapters/pyscf_test.py b/arc/job/adapters/pyscf_test.py index 42253850ca..fca1b242b7 100644 --- a/arc/job/adapters/pyscf_test.py +++ b/arc/job/adapters/pyscf_test.py @@ -12,10 +12,11 @@ import importlib.util import os import shutil +import tempfile import unittest from unittest.mock import patch -from arc.common import ARC_TESTING_PATH, read_yaml_file, save_yaml_file +from arc.common import read_yaml_file, save_yaml_file from arc.job.adapters.pyscf_adapter import PySCFAdapter from arc.level import Level from arc.parser.adapters.yaml import YAMLParser @@ -35,9 +36,8 @@ def setUpClass(cls): A method that is run before all unit tests in this class. """ cls.maxDiff = None - cls.project_directory = os.path.join(ARC_TESTING_PATH, 'test_PySCFAdapter') - if not os.path.exists(cls.project_directory): - os.makedirs(cls.project_directory) + cls.project_directory = tempfile.mkdtemp(prefix='arc_test_pyscf_') + cls.addClassCleanup(shutil.rmtree, cls.project_directory, ignore_errors=True) water_xyz = {'symbols': ('O', 'H', 'H'), 'isotopes': (16, 1, 1), @@ -242,14 +242,6 @@ def test_normalize_basis(self): self.assertEqual(normalize_basis('def2SVP'), 'def2-svp') self.assertEqual(normalize_basis('cc-pvtz'), 'cc-pvtz') - @classmethod - def tearDownClass(cls): - """ - A function that is run ONCE after all unit tests in this class. - Delete all project directories created during these unit tests. - """ - shutil.rmtree(cls.project_directory, ignore_errors=True) - if __name__ == '__main__': unittest.main(testRunner=unittest.TextTestRunner(verbosity=2)) diff --git a/arc/job/adapters/uma_test.py b/arc/job/adapters/uma_test.py index bea1775271..7e937a5c01 100644 --- a/arc/job/adapters/uma_test.py +++ b/arc/job/adapters/uma_test.py @@ -12,10 +12,11 @@ import os import shutil import sys +import tempfile import unittest import unittest.mock -from arc.common import ARC_TESTING_PATH, almost_equal_coords, read_yaml_file, save_yaml_file +from arc.common import almost_equal_coords, read_yaml_file, save_yaml_file from arc.job.adapters.ase_adapter import ASEAdapter from arc.level import Level from arc.parser.parser import (parse_1d_scan_coords, parse_e_elect, parse_frequencies, @@ -51,8 +52,8 @@ class TestUMAViaASEAdapter(unittest.TestCase): def setUpClass(cls): """A method that is run before all unit tests in this class.""" cls.maxDiff = None - cls.base = os.path.join(ARC_TESTING_PATH, 'test_UMA_via_ASE') - os.makedirs(cls.base, exist_ok=True) + cls.base = tempfile.mkdtemp(prefix='arc_test_uma_') + cls.addClassCleanup(shutil.rmtree, cls.base, ignore_errors=True) # UMA selected implicitly via the level method. cls.job_method = ASEAdapter(execution_type='incore', job_type='sp', project='p', project_directory=os.path.join(cls.base, 'method'), @@ -66,11 +67,6 @@ def setUpClass(cls): for job in (cls.job_method, cls.job_args): os.makedirs(job.local_path, exist_ok=True) - @classmethod - def tearDownClass(cls): - """A method that is run after all unit tests in this class.""" - shutil.rmtree(cls.base, ignore_errors=True) - def test_determine_calculator_name(self): """Test that the UMA calculator is detected from the level method or from args.""" self.assertEqual(self.job_method.determine_calculator_name(), 'uma') @@ -153,12 +149,8 @@ class TestUMAViaASEWithModel(unittest.TestCase): @classmethod def setUpClass(cls): """A method that is run before all unit tests in this class.""" - cls.base = os.path.join(ARC_TESTING_PATH, 'test_UMA_via_ASE_model') - - @classmethod - def tearDownClass(cls): - """A method that is run after all unit tests in this class.""" - shutil.rmtree(cls.base, ignore_errors=True) + cls.base = tempfile.mkdtemp(prefix='arc_test_uma_model_') + cls.addClassCleanup(shutil.rmtree, cls.base, ignore_errors=True) def _job(self, label, job_type, species, **kwargs): """Build an incore UMA-via-ASE job.""" From a023ec48f71e6faced6e6a95cb64a5f6f6410683 Mon Sep 17 00:00:00 2001 From: Calvin Pieters Date: Sat, 22 Aug 2026 11:05:19 +0300 Subject: [PATCH 4/7] Stop tests writing into the read-only arc/testing fixture tree Four tests treated a fixture directory as writable scratch: - common_test.test_globalize_paths wrote restart_paths_globalized.yml into arc/testing/restart/4_globalized_paths, which needed a self-healing cleanup helper called from both setUpClass and tearDownClass to keep the artifact from leaking into the repo. The fixture is now copied into a temporary directory and globalized there, and the two assertions that hard-coded the arc/testing path now assert against the temporary project directory. - common_test.test_safe_copy_file copied its destination file next to the source fixture. - processor_test.test_compare_rates wrote rate plots into arc/testing/process_kinetics. - reaction_test.test_load_ts_xyz_user_guess_from_files passed the fixture directory itself as an ARC project_directory, so a Scheduler run created arc.log, output/ and log_and_restart_archive/ inside arc/testing/reactions/methanoate_hydrolysis. The fixture is now copied to a temporary directory first; the TS guess filenames in input_1.yml and input_2.yml resolve relative to project_directory, so the copy keeps them working. - arkane_test.TestArkaneAdapter released its mkdtemp from tearDownClass rather than addClassCleanup, and wrote generate_arkane_input output into arc/testing. --- arc/common_test.py | 43 ++++++++--------------------------- arc/processor_test.py | 19 ++++------------ arc/reaction/reaction_test.py | 14 ++++-------- arc/statmech/arkane_test.py | 11 ++------- 4 files changed, 20 insertions(+), 67 deletions(-) diff --git a/arc/common_test.py b/arc/common_test.py index b4b6832faa..d3c9528512 100644 --- a/arc/common_test.py +++ b/arc/common_test.py @@ -8,6 +8,7 @@ import copy import datetime import os +import shutil import tempfile import time import unittest @@ -32,33 +33,11 @@ class TestCommon(unittest.TestCase): """ Contains unit tests for ARC's common module """ - @classmethod - def _clean_globalized_restart_artifact(cls): - """Remove the globalized restart-paths artifact written by - :meth:`test_globalize_paths`. - - Called from BOTH ``setUpClass`` (defensive: wipes a stale - artifact left behind by a previously interrupted run) and - ``tearDownClass`` (the normal cleanup path). This makes the - cleanup self-healing: a Ctrl+C, ``kill``, or hard error during - a previous run cannot leave the next run inheriting the prior - ``restart_paths_globalized.yml``. - """ - globalized_restart_path = os.path.join( - common.ARC_TESTING_PATH, 'restart', '4_globalized_paths', - 'restart_paths_globalized.yml') - if os.path.isfile(globalized_restart_path): - try: - os.remove(path=globalized_restart_path) - except OSError as e: - print(f'Could not remove stale globalized restart artifact {globalized_restart_path}: {e}') - @classmethod def setUpClass(cls): """ A method that is run before all unit tests in this class. """ - cls._clean_globalized_restart_artifact() cls.maxDiff = None cls.default_job_types = {'conf_opt': True, 'opt': True, @@ -991,19 +970,21 @@ def test_get_bonds_from_dmat(self): def test_globalize_paths(self): """Test modifying a file's contents to correct absolute file paths""" - project_directory = os.path.join(common.ARC_TESTING_PATH, 'restart', '4_globalized_paths') + project_directory = os.path.join(tempfile.mkdtemp(prefix='arc_test_globalize_'), '4_globalized_paths') + self.addCleanup(shutil.rmtree, os.path.dirname(project_directory), ignore_errors=True) + shutil.copytree(os.path.join(common.ARC_TESTING_PATH, 'restart', '4_globalized_paths'), project_directory) restart_path = os.path.join(project_directory, 'restart_paths.yml') common.globalize_paths(file_path=restart_path, project_directory=project_directory) globalized_restart_path = os.path.join(project_directory, 'restart_paths_globalized.yml') content = common.read_yaml_file(globalized_restart_path) self.assertEqual(content['output']['restart'], 'Restarted ARC at 2020-02-28 12:51:14.446086; ') - self.assertIn('arc/testing/restart/4_globalized_paths/calcs/Species/HCN/freq_a38229/output.out', + self.assertIn(os.path.join(project_directory, 'calcs', 'Species', 'HCN', 'freq_a38229', 'output.out'), content['output']['spc']['paths']['freq']) self.assertNotIn('gpfs/workspace/users/user', content['output']['spc']['paths']['freq']) path = '/home/user/runs/ARC/ARC_Project/calcs/Species/H/sp_a4339/output.out' new_path = common.globalize_path(path, project_directory) - self.assertIn('arc/testing/restart/4_globalized_paths/calcs/Species/H/sp_a4339/output.out', new_path) + self.assertIn(os.path.join(project_directory, 'calcs', 'Species', 'H', 'sp_a4339', 'output.out'), new_path) def test_globalize_path(self): """Test rebasing a single path to the current ARC project""" @@ -1346,13 +1327,14 @@ def test_calc_rmsd(self): def test_safe_copy_file(self): """tests the safe_copy_file() function.""" + scratch_dir = tempfile.mkdtemp(prefix='arc_test_safe_copy_') + self.addCleanup(shutil.rmtree, scratch_dir, ignore_errors=True) source_path = os.path.join(common.ARC_TESTING_PATH, 'freq', 'CO2_xtb.out') - destination_path = os.path.join(common.ARC_TESTING_PATH, 'freq', 'CO2_xtb_copy.out') + destination_path = os.path.join(scratch_dir, 'CO2_xtb_copy.out') common.safe_copy_file(source=source_path, destination=destination_path) self.assertTrue(os.path.isfile(destination_path)) # Check that no error is being raised if we attempt to copy to the same destination. common.safe_copy_file(source=source_path, destination=destination_path) - os.remove(destination_path) def test_sort_atoms_in_descending_label_order(self): """tests the sort_atoms_in_descending_label_order function""" @@ -1520,13 +1502,6 @@ def test_calculate_arrhenius_rate_coefficient(self): with self.assertRaises(ValueError): common.calculate_arrhenius_rate_coefficient(A=1e12, n=0.5, Ea=10, T=bad_T, Ea_units='kJ/mol') - @classmethod - def tearDownClass(cls): - """ - A function that is run ONCE after all unit tests in this class. - """ - cls._clean_globalized_restart_artifact() - class TestInitializeLogDeferredWarnings(unittest.TestCase): """initialize_log() must flush any deferred import-time warnings diff --git a/arc/processor_test.py b/arc/processor_test.py index cdbaced736..cb35c0439a 100644 --- a/arc/processor_test.py +++ b/arc/processor_test.py @@ -7,11 +7,12 @@ import os import shutil +import tempfile import unittest import arc.processor as processor from arc.checks.common import TS_IRC_FAILED_MARKER -from arc.common import ARC_TESTING_PATH, read_yaml_file +from arc.common import read_yaml_file from arc.reaction import ARCReaction from arc.species import ARCSpecies @@ -27,6 +28,8 @@ def setUpClass(cls): A method that is run before all unit tests in this class. """ cls.maxDiff = None + cls.scratch_dir = tempfile.mkdtemp(prefix='arc_test_processor_') + cls.addClassCleanup(shutil.rmtree, cls.scratch_dir, ignore_errors=True) cls.ch4 = ARCSpecies(label='CH4', smiles='C') cls.nh3 = ARCSpecies(label='NH3', smiles='N') cls.h = ARCSpecies(label='H', smiles='[H]') @@ -57,7 +60,7 @@ def test_compare_rates(self): kinetics={'A': 7.18e5, 'n': 2.05, 'Ea': 151.88}, ) rxn_2.ts_species = ARCSpecies(label='TS2', is_ts=True) - output_directory = os.path.join(ARC_TESTING_PATH, 'process_kinetics') + output_directory = os.path.join(self.scratch_dir, 'process_kinetics') reactions_to_compare = processor.compare_rates(rxns_for_kinetics_lib=[rxn_1, rxn_2], output_directory=output_directory, ) @@ -72,17 +75,5 @@ def test_compare_rates(self): self.assertNotIn('ts_validation', content[1]) - @classmethod - def tearDownClass(cls): - """ - A function that is run ONCE after all unit tests in this class. - """ - directories = [os.path.join(ARC_TESTING_PATH, 'process_kinetics'), - ] - for dir_path in directories: - if os.path.isdir(dir_path): - shutil.rmtree(dir_path) - - if __name__ == '__main__': unittest.main(testRunner=unittest.TextTestRunner(verbosity=2)) diff --git a/arc/reaction/reaction_test.py b/arc/reaction/reaction_test.py index 81f31d3628..bf245d2915 100644 --- a/arc/reaction/reaction_test.py +++ b/arc/reaction/reaction_test.py @@ -8,6 +8,7 @@ from itertools import permutations import os import shutil +import tempfile import time import unittest @@ -1048,7 +1049,9 @@ def test_multi_reactants(self): def test_load_ts_xyz_user_guess_from_files(self): """Test various loading a reaction and populating the TS ARCSpecies with user xyz guesses from files""" - project_directory = os.path.join(ARC_TESTING_PATH, 'reactions', 'methanoate_hydrolysis') + project_directory = os.path.join(tempfile.mkdtemp(prefix='arc_test_reaction_'), 'methanoate_hydrolysis') + self.addCleanup(shutil.rmtree, os.path.dirname(project_directory), ignore_errors=True) + shutil.copytree(os.path.join(ARC_TESTING_PATH, 'reactions', 'methanoate_hydrolysis'), project_directory) input_dict = read_yaml_file(path=os.path.join(project_directory, 'input_1.yml')) input_dict['project_directory'] = project_directory arc_object = ARC(**input_dict) @@ -1124,15 +1127,6 @@ def test_atom_map_property(self): @classmethod def tearDownClass(cls): """A function that is run ONCE after all unit tests in this class.""" - project_directory = os.path.join(ARC_TESTING_PATH, 'reactions', 'methanoate_hydrolysis') - sub_folders = ['log_and_restart_archive', 'output'] - files_to_remove = ['arc.log'] - for sub_folder in sub_folders: - shutil.rmtree(os.path.join(project_directory, sub_folder), ignore_errors=True) - for file_path in files_to_remove: - full_file_path = os.path.join(project_directory, file_path) - if os.path.isfile(full_file_path): - os.remove(full_file_path) file_paths = [os.path.join(ARC_PATH, 'arc', 'reaction', 'nul'), os.path.join(ARC_PATH, 'arc', 'reaction', 'run.out')] for file_path in file_paths: if os.path.isfile(file_path): diff --git a/arc/statmech/arkane_test.py b/arc/statmech/arkane_test.py index ffaf90827e..82a642f195 100644 --- a/arc/statmech/arkane_test.py +++ b/arc/statmech/arkane_test.py @@ -67,6 +67,7 @@ def setUpClass(cls): A method that is run before all unit tests in this class. """ cls.tmpdir = tempfile.mkdtemp(prefix='test_Arkane_') + cls.addClassCleanup(shutil.rmtree, cls.tmpdir, ignore_errors=True) output_path_1 = os.path.join(cls.tmpdir, 'output_1') calcs_path_1 = os.path.join(cls.tmpdir, 'calcs_1') output_path_2 = os.path.join(cls.tmpdir, 'output_2') @@ -390,7 +391,7 @@ def test_warn_no_match_logs(self): def test_generate_arkane_input(self): """Test generating Arkane input""" - statmech_dir = os.path.join(ARC_TESTING_PATH, 'arkane_input_tests_delete') + statmech_dir = os.path.join(self.tmpdir, 'arkane_input_tests_delete') os.makedirs(statmech_dir, exist_ok=True) self.arkane_1.generate_arkane_input(statmech_dir=statmech_dir) input_path = os.path.join(statmech_dir, 'input.py') @@ -426,14 +427,6 @@ def test_lone_pair_species_uses_adjacency_list(self): self.assertNotIn("SMILES('[CH2]')", content) # must NOT use the lossy SMILES self.assertIn("structure=SMILES('O')", content) # normal species unchanged - @classmethod - def tearDownClass(cls): - """ - A function that is run ONCE after all unit tests in this class. - """ - shutil.rmtree(cls.tmpdir, ignore_errors=True) - shutil.rmtree(os.path.join(ARC_TESTING_PATH, 'arkane_input_tests_delete'), ignore_errors=True) - class TestArkaneOutputParsing(unittest.TestCase): """Tests for parsing functions that read Arkane output.py content.""" From 2093297ee868d77351192a01c5d948ce949d9b4c Mon Sep 17 00:00:00 2001 From: Calvin Pieters Date: Sat, 22 Aug 2026 12:17:18 +0300 Subject: [PATCH 5/7] Isolate NMD and TS check test scratch in temporary directories The TestNMD and TestTSChecks classes built their job project directories under ARC's shared Projects folder and deleted them in tearDownClass. pytest-xdist may split a single TestCase class across workers, so each worker runs setUpClass and tearDownClass independently and one worker's teardown removes a directory another worker is still using. Allocate the scratch with tempfile.mkdtemp() in setUpClass and release it with addClassCleanup instead. --- arc/checks/nmd_test.py | 11 +++++------ arc/checks/ts_test.py | 15 ++++++--------- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/arc/checks/nmd_test.py b/arc/checks/nmd_test.py index 5fcd89bcfc..07f5b3ee14 100644 --- a/arc/checks/nmd_test.py +++ b/arc/checks/nmd_test.py @@ -8,6 +8,7 @@ import unittest import os import shutil +import tempfile import numpy as np @@ -32,12 +33,14 @@ def setUpClass(cls): A method that is run before all unit tests in this class. """ cls.maxDiff = None + cls.scratch_dir = tempfile.mkdtemp(prefix='arc_test_nmd_') + cls.addClassCleanup(shutil.rmtree, cls.scratch_dir, ignore_errors=True) cls.generic_job = job_factory(job_adapter='gaussian', species=[ARCSpecies(label='SPC', smiles='C')], job_type='composite', level=Level(method='CBS-QB3'), project='test_project', - project_directory=os.path.join(ARC_PATH, 'Projects', 'tmp_nmd_project'), + project_directory=os.path.join(cls.scratch_dir, 'tmp_nmd_project'), ) cls.xyz_1 = {'symbols': ('C', 'N', 'H', 'H', 'H', 'H'), 'isotopes': (13, 14, 1, 1, 1, 1), @@ -939,12 +942,8 @@ def test_analyze_ts_normal_mode_displacement_unmapped_reaction(self): def tearDownClass(cls): """ A function that is run ONCE after all unit tests in this class. - Delete all project directories created during these unit tests + Delete files created during these unit tests """ - projects = ['tmp_nmd_project'] - for project in projects: - project_directory = os.path.join(ARC_PATH, 'Projects', project) - shutil.rmtree(project_directory, ignore_errors=True) file_paths = [os.path.join(ARC_PATH, 'arc', 'checks', 'nul'), os.path.join(ARC_PATH, 'arc', 'checks', 'run.out')] for file_path in file_paths: if os.path.isfile(file_path): diff --git a/arc/checks/ts_test.py b/arc/checks/ts_test.py index d3ee018c30..f97f15b2c7 100644 --- a/arc/checks/ts_test.py +++ b/arc/checks/ts_test.py @@ -8,6 +8,7 @@ import unittest import os import shutil +import tempfile from unittest.mock import patch import numpy as np @@ -33,6 +34,8 @@ def setUpClass(cls): A method that is run before all unit tests in this class. """ cls.maxDiff = None + cls.scratch_dir = tempfile.mkdtemp(prefix='arc_test_ts_checks_') + cls.addClassCleanup(shutil.rmtree, cls.scratch_dir, ignore_errors=True) cls.rms_list_1 = [0.01414213562373095, 0.05, 0.04, 0.5632938842203065, 0.7993122043357026, 0.08944271909999159, 0.10677078252031312, 0.09000000000000001, 0.05, 0.09433981132056604] @@ -105,9 +108,7 @@ def setUpClass(cls): job_type='composite', level=Level(method='CBS-QB3'), project='test_project', - project_directory=os.path.join(ARC_PATH, - 'Projects', - 'arc_project_for_testing_delete_after_usage4'), + project_directory=os.path.join(cls.scratch_dir, 'job1_project'), ) cls.rxn_3 = ARCReaction(r_species=[ARCSpecies(label='NH3', smiles='N'), ARCSpecies(label='H', smiles='[H]')], @@ -215,7 +216,7 @@ def setUpClass(cls): (-1.1265684046717404, -0.2344009055503307, -1.0127644068816903))} cls.species_dict_8 = {spc.label: spc for spc in cls.rxn_8.r_species + cls.rxn_8.p_species + [cls.rxn_8.ts_species]} - cls.project_directory_8 = os.path.join(ts.ARC_PATH, 'Projects', 'arc_project_for_testing_delete_after_usage5') + cls.project_directory_8 = os.path.join(cls.scratch_dir, 'rxn_8_project') cls.output_dict_8 = {'iC3H7': {'paths': {'freq': os.path.join(ARC_TESTING_PATH, 'freq', 'iC3H7.out'), 'sp': os.path.join(ARC_TESTING_PATH, 'opt', 'iC3H7.out'), 'opt': os.path.join(ARC_TESTING_PATH, 'opt', 'iC3H7.out'), @@ -950,12 +951,8 @@ def test_check_irc_isomorphism_mismatch_alone_is_not_a_failure(self): def tearDownClass(cls): """ A function that is run ONCE after all unit tests in this class. - Delete all project directories created during these unit tests + Delete files created during these unit tests """ - projects = ['arc_project_for_testing_delete_after_usage4', 'arc_project_for_testing_delete_after_usage5'] - for project in projects: - project_directory = os.path.join(ARC_PATH, 'Projects', project) - shutil.rmtree(project_directory, ignore_errors=True) file_paths = [os.path.join(ARC_PATH, 'arc', 'checks', 'nul'), os.path.join(ARC_PATH, 'arc', 'checks', 'run.out')] for file_path in file_paths: if os.path.isfile(file_path): From 4998bba548291149b3265a53fd94eb1f1b6e7587 Mon Sep 17 00:00:00 2001 From: Calvin Pieters Date: Sat, 22 Aug 2026 12:17:18 +0300 Subject: [PATCH 6/7] Isolate ARC, plotter, species and scaling test scratch in temporary directories TestARC instantiated ARC without a project directory, so every instance created a directory named after its project under ARC's shared Projects folder, several of which were never deleted. TestPlotter additionally wrote its multi-species logs, its BDE report and its IRC animation into the read-only arc/testing fixture tree, where one test asserted the absence of files another test had created. Give each of them a tempfile.mkdtemp() scratch directory released through addCleanup or addClassCleanup, and drop the tearDownClass removals of shared paths. The gcn_tst cleanup entry in species_test is dropped as well; nothing creates that directory any more. --- arc/main_test.py | 152 +++++++++++++++++++++++------------- arc/plotter_test.py | 69 +++++++--------- arc/species/species_test.py | 11 +-- arc/utils/scale_test.py | 15 +--- 4 files changed, 136 insertions(+), 111 deletions(-) diff --git a/arc/main_test.py b/arc/main_test.py index 64282cc7fc..4e23f081ea 100644 --- a/arc/main_test.py +++ b/arc/main_test.py @@ -11,9 +11,9 @@ import subprocess import tempfile import unittest -from unittest import mock +import unittest.mock as mock -from arc.common import ARC_PATH, get_logger +from arc.common import get_logger from arc.exceptions import InputError from arc.imports import settings from arc.job.adapters.gaussian import GaussianAdapter @@ -36,6 +36,8 @@ def setUpClass(cls): A method that is run before all unit tests in this class. """ cls.maxDiff = None + cls.scratch_dir = tempfile.mkdtemp(prefix='arc_test_main_') + cls.addClassCleanup(shutil.rmtree, cls.scratch_dir, ignore_errors=True) cls.servers = servers.keys() cls.job_types1 = {'conf_opt': True, 'opt': True, @@ -48,12 +50,6 @@ def setUpClass(cls): 'lennard_jones': False, 'bde': True, } - projects = ['arc_project_for_testing_delete_after_usage_test_from_dict', - 'arc_model_chemistry_test', 'arc_test', 'test', 'unit_test_specific_job', 'wrong'] - for project in projects: - project_directory = os.path.join(ARC_PATH, 'Projects', project) - if os.path.isdir(project_directory): - shutil.rmtree(project_directory, ignore_errors=True) def test_as_dict(self): """Test the as_dict() method of ARC""" @@ -62,6 +58,7 @@ def test_as_dict(self): compute_thermo=False, ) arc0 = ARC(project='arc_test', + project_directory=os.path.join(self.scratch_dir, 'arc_test'), job_types=self.job_types1, species=[spc1], level_of_theory='ccsd(t)-f12/cc-pvdz-f12//b3lyp/6-311+g(3df,2p)', @@ -178,10 +175,12 @@ def test_from_dict(self): 'optical_isomers': 1, 'rotors_dict': {}, 'xyzs': []}], - 'project_directory': os.path.join(ARC_PATH, 'Projects', - 'arc_project_for_testing_delete_after_usage_test_from_dict'), + 'project_directory': os.path.join( + self.scratch_dir, 'arc_project_for_testing_delete_after_usage_test_from_dict'), } - arc1 = ARC(project='wrong', freq_scale_factor=0.95) + arc1 = ARC(project='wrong', + project_directory=os.path.join(self.scratch_dir, 'wrong'), + freq_scale_factor=0.95) self.assertEqual(arc1.freq_scale_factor, 0.95) # user input arc2 = ARC(**restart_dict) self.assertEqual(arc2.freq_scale_factor, 0.96) # loaded from the restart dict @@ -199,7 +198,7 @@ def test_from_dict_specific_job(self): """Test the from_dict() method of ARC""" restart_dict = {'specific_job_type': 'bde', 'project': 'unit_test_specific_job', - 'project_directory': os.path.join(ARC_PATH, 'Projects', 'unit_test_specific_job'), + 'project_directory': os.path.join(self.scratch_dir, 'unit_test_specific_job'), } arc1 = ARC(**restart_dict) job_type_expected = {'conf_opt': False, 'conf_sp': False, 'opt': True, 'freq': True, 'sp': True, 'rotors': False, @@ -219,21 +218,27 @@ def test_check_project_name(self): def test_determine_model_chemistry_and_freq_scale_factor(self): """Test determining the model chemistry and the frequency scaling factor""" - arc0 = ARC(project='arc_model_chemistry_test', level_of_theory='CBS-QB3') + arc0 = ARC(project='arc_model_chemistry_test', + project_directory=os.path.join(self.scratch_dir, 'arc_model_chemistry_test'), + level_of_theory='CBS-QB3') self.assertEqual(str(arc0.arkane_level_of_theory), "cbs-qb3, software: gaussian") self.assertEqual(arc0.freq_scale_factor, 1.004) - arc1 = ARC(project='arc_model_chemistry_test', level_of_theory='cbs-qb3-paraskevas') + arc1 = ARC(project='arc_model_chemistry_test', + project_directory=os.path.join(self.scratch_dir, 'arc_model_chemistry_test'), + level_of_theory='cbs-qb3-paraskevas') self.assertEqual(str(arc1.arkane_level_of_theory), 'cbs-qb3-paraskevas, software: gaussian') self.assertEqual(arc1.freq_scale_factor, 1.004) self.assertEqual(arc1.bac_type, 'p') arc2 = ARC(project='arc_model_chemistry_test', + project_directory=os.path.join(self.scratch_dir, 'arc_model_chemistry_test'), level_of_theory='ccsd(t)-f12/cc-pvtz-f12//m062x/cc-pvtz') self.assertEqual(str(arc2.arkane_level_of_theory), 'ccsd(t)-f12/cc-pvtz-f12, software: molpro') self.assertEqual(arc2.freq_scale_factor, 0.955) arc3 = ARC(project='arc_model_chemistry_test', + project_directory=os.path.join(self.scratch_dir, 'arc_model_chemistry_test'), sp_level='ccsd(t)-f12/cc-pvtz-f12', opt_level='wb97xd/def2tzvp') self.assertEqual(str(arc3.arkane_level_of_theory), 'ccsd(t)-f12/cc-pvtz-f12, software: molpro') self.assertEqual(arc3.freq_scale_factor, 0.988) @@ -242,33 +247,46 @@ def test_determine_model_chemistry_for_job_types(self): """Test determining the model chemistry specification dictionary for job types""" # Test conflicted inputs: specify both level_of_theory and composite_method with self.assertRaises(InputError): - ARC(project='test', level_of_theory='ccsd(t)-f12/cc-pvtz-f12//wb97x-d/aug-cc-pvtz', + ARC(project='test', + project_directory=os.path.join(self.scratch_dir, 'test'), + level_of_theory='ccsd(t)-f12/cc-pvtz-f12//wb97x-d/aug-cc-pvtz', composite_method='cbs-qb3') # Test illegal level of theory specification (method contains multiple slashes) with self.assertRaises(ValueError): - ARC(project='test', level_of_theory='dlpno-mp2-f12/D/cc-pVDZ(fi/sf/fw)//b3lyp/G/def2svp') + ARC(project='test', + project_directory=os.path.join(self.scratch_dir, 'test'), + level_of_theory='dlpno-mp2-f12/D/cc-pVDZ(fi/sf/fw)//b3lyp/G/def2svp') # Test illegal job level specification (method contains multiple slashes) with self.assertRaises(ValueError): - ARC(project='test', opt_level='b3lyp/d/def2tzvp/def2tzvp/c') + ARC(project='test', + project_directory=os.path.join(self.scratch_dir, 'test'), + opt_level='b3lyp/d/def2tzvp/def2tzvp/c') # Test illegal job level specification (method contains empty space) with self.assertRaises(ValueError): - ARC(project='test', opt_level='b3lyp/def2tzvp def2tzvp/c') + ARC(project='test', + project_directory=os.path.join(self.scratch_dir, 'test'), + opt_level='b3lyp/def2tzvp def2tzvp/c') # Test direct job level specification conflicts with level of theory specification with self.assertRaises(InputError): - ARC(project='test', level_of_theory='b3lyp/sto-3g', opt_level='wb97xd/def2tzvp') + ARC(project='test', + project_directory=os.path.join(self.scratch_dir, 'test'), + level_of_theory='b3lyp/sto-3g', opt_level='wb97xd/def2tzvp') # Test deduce levels from default method from settings.py - arc1 = ARC(project='test') + arc1 = ARC(project='test', + project_directory=os.path.join(self.scratch_dir, 'test')) self.assertEqual(arc1.opt_level.simple(), 'wb97xd/def2tzvp') self.assertEqual(arc1.freq_level.simple(), 'wb97xd/def2tzvp') self.assertEqual(arc1.sp_level.simple(), 'ccsd(t)-f12/cc-pvtz-f12') # Test deduce levels from composite method specification - arc2 = ARC(project='test', composite_method='cbs-qb3') + arc2 = ARC(project='test', + project_directory=os.path.join(self.scratch_dir, 'test'), + composite_method='cbs-qb3') self.assertIsNotNone(arc2.opt_level) self.assertIsNone(arc2.sp_level) self.assertIsNone(arc2.orbitals_level) @@ -277,35 +295,47 @@ def test_determine_model_chemistry_for_job_types(self): self.assertEqual(arc2.composite_method.simple(), 'cbs-qb3') # Test deduce levels from level of theory specification - arc3 = ARC(project='test', level_of_theory='ccsd(t)-f12/cc-pvtz-f12//wb97m-v/def2tzvpd', freq_scale_factor=1) + arc3 = ARC(project='test', + project_directory=os.path.join(self.scratch_dir, 'test'), + level_of_theory='ccsd(t)-f12/cc-pvtz-f12//wb97m-v/def2tzvpd', freq_scale_factor=1) self.assertEqual(arc3.opt_level.simple(), 'wb97m-v/def2tzvpd') self.assertEqual(arc3.freq_level.simple(), 'wb97m-v/def2tzvpd') self.assertEqual(arc3.sp_level.simple(), 'ccsd(t)-f12/cc-pvtz-f12') self.assertEqual(arc3.scan_level.simple(), 'wb97m-v/def2tzvpd') self.assertIsNone(arc3.orbitals_level) - arc4 = ARC(project='test', opt_level='wb97x-d3/6-311++G(3df,3pd)', freq_level='m062x/def2-tzvpp', + arc4 = ARC(project='test', + project_directory=os.path.join(self.scratch_dir, 'test'), + opt_level='wb97x-d3/6-311++G(3df,3pd)', freq_level='m062x/def2-tzvpp', sp_level='ccsd(t)f12/aug-cc-pvqz', calc_freq_factor=False, compute_thermo=False) self.assertEqual(arc4.opt_level.simple(), 'wb97x-d3/6-311++g(3df,3pd)') self.assertEqual(arc4.freq_level.simple(), 'm062x/def2-tzvpp') self.assertEqual(arc4.sp_level.simple(), 'ccsd(t)f12/aug-cc-pvqz') # Test deduce freq level from opt level - arc7 = ARC(project='test', opt_level='wb97xd/aug-cc-pvtz', calc_freq_factor=False) + arc7 = ARC(project='test', + project_directory=os.path.join(self.scratch_dir, 'test'), + opt_level='wb97xd/aug-cc-pvtz', calc_freq_factor=False) self.assertEqual(arc7.opt_level.simple(), 'wb97xd/aug-cc-pvtz') self.assertEqual(arc7.freq_level.simple(), 'wb97xd/aug-cc-pvtz') # Test a level not supported by Arkane does not raise error if compute_thermo is False - arc8 = ARC(project='test', sp_level='method/unsupported', calc_freq_factor=False, compute_thermo=False) + arc8 = ARC(project='test', + project_directory=os.path.join(self.scratch_dir, 'test'), + sp_level='method/unsupported', calc_freq_factor=False, compute_thermo=False) self.assertEqual(arc8.sp_level.simple(), 'method/unsupported') self.assertEqual(arc8.freq_level.simple(), 'wb97xd/def2tzvp') # Test that a level not supported by Arkane does raise an error if compute_thermo is True (default) with self.assertRaises(ValueError): - ARC(project='test', sp_level='method/unsupported', calc_freq_factor=False) + ARC(project='test', + project_directory=os.path.join(self.scratch_dir, 'test'), + sp_level='method/unsupported', calc_freq_factor=False) # Test dictionary format specification with auxiliary basis and DFT dispersion - arc9 = ARC(project='test', opt_level={}, + arc9 = ARC(project='test', + project_directory=os.path.join(self.scratch_dir, 'test'), + opt_level={}, freq_level={'method': 'B3LYP/G', 'basis': 'cc-pVDZ(fi/sf/fw)', 'auxiliary_basis': 'def2-svp/C', 'dispersion': 'DEF2-tzvp/c'}, sp_level={'method': 'DLPNO-CCSD(T)-F12', 'basis': 'cc-pVTZ-F12', @@ -319,33 +349,43 @@ def test_determine_model_chemistry_for_job_types(self): 'cabs: cc-pvtz-f12-cabs, software: orca') # Test using default frequency and orbital level for composite job, also forbid rotors job - arc10 = ARC(project='test', composite_method='cbs-qb3', calc_freq_factor=False, + arc10 = ARC(project='test', + project_directory=os.path.join(self.scratch_dir, 'test'), + composite_method='cbs-qb3', calc_freq_factor=False, job_types={'rotors': False, 'orbitals': True}) self.assertEqual(arc10.freq_level.simple(), 'b3lyp/cbsb7') self.assertIsNone(arc10.scan_level) self.assertEqual(arc10.orbitals_level.simple(), 'b3lyp/cbsb7') # Test using specified frequency, scan, and orbital for composite job - arc11 = ARC(project='test', composite_method='cbs-qb3', freq_level='wb97xd/6-311g', scan_level='apfd/def2svp', + arc11 = ARC(project='test', + project_directory=os.path.join(self.scratch_dir, 'test'), + composite_method='cbs-qb3', freq_level='wb97xd/6-311g', scan_level='apfd/def2svp', orbitals_level='hf/sto-3g', job_types={'orbitals': True}, calc_freq_factor=False) self.assertEqual(arc11.scan_level.simple(), 'apfd/def2svp') self.assertEqual(arc11.freq_level.simple(), 'wb97xd/6-311g') self.assertEqual(arc11.orbitals_level.simple(), 'hf/sto-3g') # Test using default frequency and orbital level for job specified from level of theory, also forbid rotors job - arc12 = ARC(project='test', level_of_theory='b3lyp/sto-3g', calc_freq_factor=False, + arc12 = ARC(project='test', + project_directory=os.path.join(self.scratch_dir, 'test'), + level_of_theory='b3lyp/sto-3g', calc_freq_factor=False, job_types={'rotors': False, 'orbitals': True}, compute_thermo=False) self.assertIsNone(arc12.scan_level) self.assertEqual(arc12.freq_level.simple(), 'b3lyp/sto-3g') self.assertEqual(arc12.orbitals_level.simple(), 'wb97x-d3/def2tzvp') # Test using specified scan level - arc13 = ARC(project='test', level_of_theory='b3lyp/sto-3g', calc_freq_factor=False, scan_level='apfd/def2svp', + arc13 = ARC(project='test', + project_directory=os.path.join(self.scratch_dir, 'test'), + level_of_theory='b3lyp/sto-3g', calc_freq_factor=False, scan_level='apfd/def2svp', job_types={'rotors': True}, compute_thermo=False) self.assertEqual(arc13.scan_level.simple(), 'apfd/def2svp') # Test specifying semi-empirical and force-field methods using dictionary - arc14 = ARC(project='test', opt_level={'method': 'AM1'}, freq_level={'method': 'PM6'}, + arc14 = ARC(project='test', + project_directory=os.path.join(self.scratch_dir, 'test'), + opt_level={'method': 'AM1'}, freq_level={'method': 'PM6'}, sp_level={'method': 'AMBER'}, calc_freq_factor=False, compute_thermo=False) self.assertEqual(arc14.opt_level.simple(), 'am1') self.assertEqual(arc14.freq_level.simple(), 'pm6') @@ -353,6 +393,7 @@ def test_determine_model_chemistry_for_job_types(self): # Test explicit year in arkane_level_of_theory dictionary arc15 = ARC(project='test', + project_directory=os.path.join(self.scratch_dir, 'test'), sp_level='wb97xd/def2tzvp', opt_level='wb97xd/def2tzvp', arkane_level_of_theory={'method': 'wb97xd', 'basis': 'def2tzvp', 'year': 2023}, @@ -362,6 +403,7 @@ def test_determine_model_chemistry_for_job_types(self): # Test warning when year is specified on sp_level instead of arkane_level_of_theory arc16 = ARC(project='test', + project_directory=os.path.join(self.scratch_dir, 'test'), sp_level={'method': 'wb97xd', 'basis': 'def2tzvp', 'year': 2023}, opt_level='wb97xd/def2tzvp', calc_freq_factor=False, compute_thermo=False) @@ -374,7 +416,9 @@ def test_determine_unique_species_labels(self): spc0 = ARCSpecies(label='spc0', smiles='CC', compute_thermo=False) spc1 = ARCSpecies(label='spc1', smiles='CC', compute_thermo=False) spc2 = ARCSpecies(label='spc2', smiles='CC', compute_thermo=False) - arc0 = ARC(project='arc_test', job_types=self.job_types1, species=[spc0, spc1, spc2], + arc0 = ARC(project='arc_test', + project_directory=os.path.join(self.scratch_dir, 'arc_test'), + job_types=self.job_types1, species=[spc0, spc1, spc2], level_of_theory='ccsd(t)-f12/cc-pvdz-f12//b3lyp/6-311+g(3df,2p)') self.assertEqual(arc0.unique_species_labels, ['spc0', 'spc1', 'spc2']) spc3 = ARCSpecies(label='spc0', smiles='CC', compute_thermo=False) @@ -385,13 +429,17 @@ def test_determine_unique_species_labels(self): def test_add_hydrogen_for_bde(self): """Test the add_hydrogen_for_bde method""" spc0 = ARCSpecies(label='spc0', smiles='CC', compute_thermo=False) - arc0 = ARC(project='arc_test', job_types=self.job_types1, species=[spc0], + arc0 = ARC(project='arc_test', + project_directory=os.path.join(self.scratch_dir, 'arc_test'), + job_types=self.job_types1, species=[spc0], level_of_theory='ccsd(t)-f12/cc-pvdz-f12//b3lyp/6-311+g(3df,2p)') arc0.add_hydrogen_for_bde() self.assertEqual(len(arc0.species), 1) spc1 = ARCSpecies(label='spc1', smiles='CC', compute_thermo=False, bdes=['all_h']) - arc1 = ARC(project='arc_test', job_types=self.job_types1, species=[spc1], + arc1 = ARC(project='arc_test', + project_directory=os.path.join(self.scratch_dir, 'arc_test'), + job_types=self.job_types1, species=[spc1], level_of_theory='ccsd(t)-f12/cc-pvdz-f12//b3lyp/6-311+g(3df,2p)') arc1.add_hydrogen_for_bde() self.assertEqual(len(arc1.species), 2) @@ -428,7 +476,9 @@ def test_process_adaptive_levels(self): self.assertEqual(processed_2[(1, 'inf')][('sp',)].simple(), 'b3lyp/6-311+g(d,p)') # Restart round-trip: as_dict() must emit the list form and reproduce the same structure. - arc0 = ARC(project='adaptive_levels_test', adaptive_levels=adaptive_levels_1) + arc0 = ARC(project='adaptive_levels_test', + project_directory=os.path.join(self.scratch_dir, 'adaptive_levels_test'), + adaptive_levels=adaptive_levels_1) restart_levels = arc0.as_dict()['adaptive_levels'] self.assertIsInstance(restart_levels, list) reprocessed = process_adaptive_levels(restart_levels) @@ -473,16 +523,22 @@ def test_process_level_of_theory(self): """ Tests the process_level_of_theory function. """ - arc0 = ARC(project='test_0', level_of_theory='ccsd(t)-f12/cc-pvdz-f12//b3lyp/6-311+g(3df,2p)', + arc0 = ARC(project='test_0', + project_directory=os.path.join(self.scratch_dir, 'test_0'), + level_of_theory='ccsd(t)-f12/cc-pvdz-f12//b3lyp/6-311+g(3df,2p)', bac_type=None, freq_scale_factor=1) - arc1 = ARC(project='test_1', level_of_theory='wb97xd/6-311+g(2d,2p)', + arc1 = ARC(project='test_1', + project_directory=os.path.join(self.scratch_dir, 'test_1'), + level_of_theory='wb97xd/6-311+g(2d,2p)', arkane_level_of_theory="b3lyp/6-311+g(3df,2p)", bac_type=None, freq_scale_factor=1, job_types={"freq": True, "sp": True, "opt": False}) - arc2 = ARC(project='test_2', sp_level='wb97xd/6-311+g(2d,2p)', + arc2 = ARC(project='test_2', + project_directory=os.path.join(self.scratch_dir, 'test_2'), + sp_level='wb97xd/6-311+g(2d,2p)', opt_level='wb97xd/6-311+g(2d,2p)', arkane_level_of_theory="b3lyp/6-311+g(3df,2p)", bac_type=None, @@ -490,7 +546,9 @@ def test_process_level_of_theory(self): job_types={"freq": True, "sp": False, "opt": False}) - arc3 = ARC(project='test_3', sp_level='wb97xd/6-311+g(2d,2p)', + arc3 = ARC(project='test_3', + project_directory=os.path.join(self.scratch_dir, 'test_3'), + sp_level='wb97xd/6-311+g(2d,2p)', opt_level='wb97xd/6-311+g(2d,2p)', arkane_level_of_theory="b3lyp/6-311+g(3df,2p)", bac_type=None, @@ -513,25 +571,13 @@ def test_unknown_ts_adapter(self): ) with self.assertRaises(InputError): arc0 = ARC(project='arc_test', + project_directory=os.path.join(self.scratch_dir, 'arc_test'), job_types=self.job_types1, species=[spc1], level_of_theory='ccsd(t)-f12/cc-pvdz-f12//b3lyp/6-311+g(3df,2p)', ts_adapters=['WRONG ADAPTER', 'AutoTST', 'GCN', 'xtb_gsm'], ) - @classmethod - def tearDownClass(cls): - """ - A function that is run ONCE after all unit tests in this class. - Delete all project directories created during these unit tests - """ - projects = ['arc_project_for_testing_delete_after_usage_test_from_dict', - 'arc_model_chemistry_test', 'arc_test', 'test', 'unit_test_specific_job', 'wrong'] - for project in projects: - project_directory = os.path.join(ARC_PATH, 'Projects', project) - if os.path.isdir(project_directory): - shutil.rmtree(project_directory, ignore_errors=True) - class TestCheckFileCleanup(unittest.TestCase): """ diff --git a/arc/plotter_test.py b/arc/plotter_test.py index ba6984dae4..78a62b6c92 100644 --- a/arc/plotter_test.py +++ b/arc/plotter_test.py @@ -7,10 +7,11 @@ import os import shutil +import tempfile import unittest import arc.plotter as plotter -from arc.common import ARC_PATH, ARC_TESTING_PATH, read_yaml_file, safe_copy_file +from arc.common import ARC_TESTING_PATH, read_yaml_file, safe_copy_file from arc.species.converter import str_to_xyz from arc.species.species import ARCSpecies @@ -20,6 +21,12 @@ class TestPlotter(unittest.TestCase): Contains unit tests for the parser functions """ + def setUp(self): + """A method that is run before each unit test in this class.""" + self.scratch_dir = tempfile.mkdtemp(prefix='arc_test_plotter_') + self.addCleanup(shutil.rmtree, self.scratch_dir, ignore_errors=True) + self.project_directory = os.path.join(self.scratch_dir, 'arc_project_for_testing_delete_after_usage') + def test_save_geo(self): """Test saving the geometry files for a species""" spc = ARCSpecies(label='methylamine', smiles='CN', multiplicity=1, charge=0) @@ -31,8 +38,7 @@ def test_save_geo(self): H -1.16115119 0.31478894 0.81506145 H -1.16115119 0.31478894 -0.81506145""") spc.opt_level = 'opt/level' - project = 'arc_project_for_testing_delete_after_usage' - project_directory = os.path.join(ARC_PATH, 'Projects', project) + project_directory = self.project_directory xyz_path = os.path.join(project_directory, 'output', 'Species', spc.label, 'geometry', 'methylamine.xyz') gjf_path = os.path.join(project_directory, 'output', 'Species', spc.label, 'geometry', 'methylamine.gjf') plotter.save_geo(species=spc, project_directory=project_directory) @@ -68,8 +74,7 @@ def test_save_geo(self): def test_augment_arkane_yml_file_with_mol_repr(self): """Test the augment_arkane_yml_file_with_mol_repr() function""" - project = 'arc_project_for_testing_delete_after_usage' - project_directory = os.path.join(ARC_PATH, 'Projects', project) + project_directory = self.project_directory n4h6_yml_path = os.path.join(ARC_TESTING_PATH, 'yml_testing', 'N4H6.yml') n4h6_yml_path_copy = os.path.join(project_directory, 'Species', 'N4H6', 'N4H6.yml') os.makedirs(os.path.join(project_directory, 'Species', 'N4H6'), exist_ok=True) @@ -83,8 +88,7 @@ def test_augment_arkane_yml_file_with_mol_repr(self): def test_save_conformers_file(self): """test the save_conformers_file function""" - project = 'arc_project_for_testing_delete_after_usage' - project_directory = os.path.join(ARC_PATH, 'Projects', project) + project_directory = self.project_directory label = 'butanol' spc1 = ARCSpecies(label=label, smiles='CCCCO') spc1.generate_conformers(n_confs=3) @@ -100,11 +104,10 @@ def test_save_conformers_file(self): def test_save_rotor_text_file(self): """Test the save_rotor_text_file function""" - project = 'arc_project_for_testing_delete_after_usage' angles = [0, 90, 180, 270, 360] energies = [0, 10, 0, 10, 0] pivots = [1, 2] - path = os.path.join(ARC_PATH, 'Projects', project, 'rotors', '{0}_directed_scan.txt'.format(pivots)) + path = os.path.join(self.project_directory, 'rotors', '{0}_directed_scan.txt'.format(pivots)) plotter.save_rotor_text_file(angles, energies, path) self.assertTrue(os.path.isfile(path)) with open(path, 'r') as f: @@ -113,7 +116,7 @@ def test_save_rotor_text_file(self): def test_log_bde_report(self): """Test the log_bde_report() function""" - path = os.path.join(ARC_TESTING_PATH, 'bde_report_test.txt') + path = os.path.join(self.scratch_dir, 'bde_report_test.txt') bde_report = {'aniline': {(1, 2): 431.43, (5, 8): 465.36, (6, 9): 458.70, (3, 10): 463.16, (4, 11): 463.16, (7, 12): 458.70, (1, 13): 372.31, (1, 14): 372.31, (5, 6): 'N/A'}} xyz = """N 2.28116100 -0.20275000 -0.29653100 @@ -178,63 +181,51 @@ def test_clean_scan_results(self): def test_make_multi_species_output_file(self): """Test the make_multi_species_output_file function""" # The xyzs used in the ARCSpecies are dummy xyzs, they are not the actual xyzs used in the output file + path = os.path.join(self.scratch_dir, 'mltspc_output.out') + safe_copy_file(source=os.path.join(ARC_TESTING_PATH, 'mltspc_output.out'), destination=path) plotter.make_multi_species_output_file(species_list=[ARCSpecies(label='water', smiles='O', multi_species='mltspc1'), ARCSpecies(label='acetylene', smiles='C#C', multi_species='mltspc1'), ARCSpecies(label='N-Valeric_Acid', smiles='CCCCC(O)=O', multi_species='mltspc1')], label='mltspc1', - path=os.path.join(ARC_TESTING_PATH, 'mltspc_output.out'), + path=path, ) - self.assertTrue(os.path.isfile(os.path.join(ARC_TESTING_PATH, 'water.log'))) - self.assertTrue(os.path.isfile(os.path.join(ARC_TESTING_PATH, 'acetylene.log'))) - self.assertTrue(os.path.isfile(os.path.join(ARC_TESTING_PATH, 'N-Valeric_Acid.log'))) + self.assertTrue(os.path.isfile(os.path.join(self.scratch_dir, 'water.log'))) + self.assertTrue(os.path.isfile(os.path.join(self.scratch_dir, 'acetylene.log'))) + self.assertTrue(os.path.isfile(os.path.join(self.scratch_dir, 'N-Valeric_Acid.log'))) def test_delete_multi_species_output_file(self): """Test the delete_multi_species_output_file function""" # The xyzs used in the ARCSpecies are dummy xyzs, they are not the actual xyzs used in the output file + path = os.path.join(self.scratch_dir, 'mltspc_output.out') + safe_copy_file(source=os.path.join(ARC_TESTING_PATH, 'mltspc_output.out'), destination=path) species_list = [ARCSpecies(label='water', smiles='O', multi_species='mltspc1'), ARCSpecies(label='acetylene', smiles='C#C', multi_species='mltspc1'), ARCSpecies(label='N-Valeric_Acid', smiles='CCCCC(O)=O', multi_species='mltspc1')] multi_species_path_dict = plotter.make_multi_species_output_file(species_list=species_list, label='mltspc1', - path=os.path.join(ARC_TESTING_PATH, 'mltspc_output.out'), + path=path, ) - self.assertTrue(os.path.isfile(os.path.join(ARC_TESTING_PATH, 'water.log'))) - self.assertTrue(os.path.isfile(os.path.join(ARC_TESTING_PATH, 'acetylene.log'))) - self.assertTrue(os.path.isfile(os.path.join(ARC_TESTING_PATH, 'N-Valeric_Acid.log'))) + self.assertTrue(os.path.isfile(os.path.join(self.scratch_dir, 'water.log'))) + self.assertTrue(os.path.isfile(os.path.join(self.scratch_dir, 'acetylene.log'))) + self.assertTrue(os.path.isfile(os.path.join(self.scratch_dir, 'N-Valeric_Acid.log'))) plotter.delete_multi_species_output_file(species_list=species_list, label='mltspc1', multi_species_path_dict=multi_species_path_dict, ) - self.assertFalse(os.path.isfile(os.path.join(ARC_TESTING_PATH, 'water.log'))) - self.assertFalse(os.path.isfile(os.path.join(ARC_TESTING_PATH, 'acetylene.log'))) - self.assertFalse(os.path.isfile(os.path.join(ARC_TESTING_PATH, 'N-Valeric_Acid.log'))) + self.assertFalse(os.path.isfile(os.path.join(self.scratch_dir, 'water.log'))) + self.assertFalse(os.path.isfile(os.path.join(self.scratch_dir, 'acetylene.log'))) + self.assertFalse(os.path.isfile(os.path.join(self.scratch_dir, 'N-Valeric_Acid.log'))) def test_save_irc_traj_animation(self): """Test the save_irc_traj_animation function""" irc_f_path = os.path.join(ARC_TESTING_PATH, 'irc', 'rxn_1_irc_1.out') irc_r_path = os.path.join(ARC_TESTING_PATH, 'irc', 'rxn_1_irc_2.out') - out_path = os.path.join(ARC_TESTING_PATH, 'irc', 'rxn_1_irc_animation.out') + out_path = os.path.join(self.scratch_dir, 'irc', 'rxn_1_irc_animation.out') + os.makedirs(os.path.dirname(out_path), exist_ok=True) self.assertFalse(os.path.isfile(out_path)) plotter.save_irc_traj_animation(irc_f_path, irc_r_path, out_path) self.assertTrue(os.path.isfile(out_path)) - @classmethod - def tearDownClass(cls): - """A function that is run ONCE after all unit tests in this class.""" - project = 'arc_project_for_testing_delete_after_usage' - project_directory = os.path.join(ARC_PATH, 'Projects', project) - shutil.rmtree(project_directory, ignore_errors=True) - files_to_remove = [os.path.join(ARC_TESTING_PATH, 'bde_report_test.txt'), - os.path.join(ARC_TESTING_PATH, 'water.log'), - os.path.join(ARC_TESTING_PATH, 'acetylene.log'), - os.path.join(ARC_TESTING_PATH, 'N-Valeric_Acid.log'), - os.path.join(ARC_TESTING_PATH, 'irc', 'rxn_1_irc_animation.out'), - ] - for file_path in files_to_remove: - if os.path.isfile(file_path): - os.remove(file_path) - - if __name__ == '__main__': unittest.main(testRunner=unittest.TextTestRunner(verbosity=2)) diff --git a/arc/species/species_test.py b/arc/species/species_test.py index ef48c7efc3..9e20059448 100644 --- a/arc/species/species_test.py +++ b/arc/species/species_test.py @@ -1821,7 +1821,8 @@ def test_preserving_multiplicity(self): def test_append_conformers(self): """Test that ARC correctly parses its own conformer files""" - project_directory = os.path.join(ARC_PATH, 'Projects', 'arc_project_for_testing_delete_after_usage4') + project_directory = tempfile.mkdtemp(prefix='arc_test_species_conformers_') + self.addCleanup(shutil.rmtree, project_directory, ignore_errors=True) xyzs = [{'symbols': ('O', 'C', 'C', 'H', 'H', 'H'), 'isotopes': (16, 12, 12, 1, 1, 1), 'coords': ((1.090687, 0.265168, -0.167063), (2.922041, -1.183357, -0.388849), (2.276555, -0.003739, 0.085435), (2.365448, -1.88781, -0.999146), @@ -3348,14 +3349,8 @@ def test_rmg_mol_to_dict_repr(self): def tearDownClass(cls): """ A function that is run ONCE after all unit tests in this class. - Delete all project directories created during these unit tests + Delete files created during these unit tests """ - projects = ['arc_project_for_testing_delete_after_usage4', - os.path.join(ARC_TESTING_PATH, 'gcn_tst')] - for project in projects: - project_directory = os.path.join(ARC_PATH, 'Projects', project) - shutil.rmtree(project_directory, ignore_errors=True) - file_paths = [os.path.join(ARC_PATH, 'nul'), os.path.join(ARC_PATH, 'run.out'), os.path.join(ARC_PATH, 'arc', 'species', 'nul'), os.path.join(ARC_PATH, 'arc', 'species', 'run.out')] for file_path in file_paths: diff --git a/arc/utils/scale_test.py b/arc/utils/scale_test.py index fb8f1b9602..cc53d1500d 100644 --- a/arc/utils/scale_test.py +++ b/arc/utils/scale_test.py @@ -7,9 +7,10 @@ import os import shutil +import tempfile import unittest -from arc.common import almost_equal_coords_lists, ARC_PATH +from arc.common import almost_equal_coords_lists from arc.level import Level from arc.utils.scale import (calculate_truhlar_scaling_factors, get_species_list, @@ -71,7 +72,8 @@ def test_summarize_results(self): ] times = ['3', '5'] overall_time = '8.5' - base_path = os.path.join(ARC_PATH, 'Projects', 'scaling_factors_arc_testing_delete_after_usage') + base_path = tempfile.mkdtemp(prefix='arc_test_scaling_factors_') + self.addCleanup(shutil.rmtree, base_path, ignore_errors=True) summarize_results(lambda_zpes=lambda_zpes, levels=levels_of_theory, @@ -120,15 +122,6 @@ def test_rename_level(self): self.assertEqual(renamed_level2, 'wb97xd_6-311pGb2d,2pb') self.assertEqual(renamed_level3, 'wb97xd_aug-ccpZQZ,_solvation_method.._SMD,_solvent.._DMSO,_software.._gaussian') - @classmethod - def tearDownClass(cls): - """ - A function that is run ONCE after all unit tests in this class. - Delete all directories created during these unit tests - """ - path = os.path.join(ARC_PATH, 'Projects', 'scaling_factors_arc_testing_delete_after_usage') - shutil.rmtree(path, ignore_errors=True) - if __name__ == '__main__': unittest.main(testRunner=unittest.TextTestRunner(verbosity=2)) From a63e08352d4ec1432a3f8d2a155c0c824a406046 Mon Sep 17 00:00:00 2001 From: Calvin Pieters Date: Sat, 22 Aug 2026 12:17:18 +0300 Subject: [PATCH 7/7] Isolate the restart functional test project directories The restart tests derived their project directories from a per-xdist-worker project name, which does not help when pytest-xdist splits the class across workers. Allocate each project directory with tempfile.mkdtemp() and release it with addCleanup. --- functional/restart_test.py | 44 +++++++++++++++----------------------- 1 file changed, 17 insertions(+), 27 deletions(-) diff --git a/functional/restart_test.py b/functional/restart_test.py index 35594910b8..30cba1d290 100644 --- a/functional/restart_test.py +++ b/functional/restart_test.py @@ -7,6 +7,7 @@ import os import shutil +import tempfile import unittest import warnings @@ -16,14 +17,6 @@ from arc.main import ARC -def _project_name(base: str) -> str: - """Return a per-xdist-worker project name to avoid parallel cleanup collisions.""" - worker_id = os.environ.get('PYTEST_XDIST_WORKER') - if worker_id: - return f'{base}_{worker_id}' - return base - - class TestRestart(unittest.TestCase): """ Contains unit tests for restarting ARC. @@ -37,6 +30,12 @@ def setUpClass(cls): cls.maxDiff = None warnings.filterwarnings(action='ignore', module='.*matplotlib.*') + def make_project_directory(self, project: str) -> str: + """Get a path to a temporary project directory that is deleted when the test ends.""" + project_directory = os.path.join(tempfile.mkdtemp(prefix='arc_test_restart_'), project) + self.addCleanup(shutil.rmtree, os.path.dirname(project_directory), ignore_errors=True) + return project_directory + def test_restart_thermo(self): """ Test restarting ARC through the ARC class in main.py via the input_dict argument of the API @@ -44,8 +43,8 @@ def test_restart_thermo(self): """ restart_dir = os.path.join(ARC_PATH, 'arc', 'testing', 'restart', '1_restart_thermo') restart_path = os.path.join(restart_dir, 'restart.yml') - project = _project_name('arc_project_for_testing_delete_after_usage_restart_thermo') - project_directory = os.path.join(ARC_PATH, 'Projects', project) + project = 'arc_project_for_testing_delete_after_usage_restart_thermo' + project_directory = self.make_project_directory(project) os.makedirs(os.path.dirname(project_directory), exist_ok=True) shutil.copytree(os.path.join(restart_dir, 'calcs'), os.path.join(project_directory, 'calcs', 'Species'), dirs_exist_ok=True) input_dict = read_yaml_file(path=restart_path, project_directory=project_directory) @@ -141,8 +140,8 @@ def test_restart_rate_1(self): """Test restarting ARC and attaining a reaction rate coefficient""" restart_dir = os.path.join(ARC_PATH, 'arc', 'testing', 'restart', '2_restart_rate') restart_path = os.path.join(restart_dir, 'restart.yml') - project = _project_name('arc_project_for_testing_delete_after_usage_restart_rate_1') - project_directory = os.path.join(ARC_PATH, 'Projects', project) + project = 'arc_project_for_testing_delete_after_usage_restart_rate_1' + project_directory = self.make_project_directory(project) os.makedirs(os.path.dirname(project_directory), exist_ok=True) shutil.copytree(os.path.join(restart_dir, 'calcs'), os.path.join(project_directory, 'calcs'), dirs_exist_ok=True) input_dict = read_yaml_file(path=restart_path, project_directory=project_directory) @@ -162,8 +161,8 @@ def test_restart_rate_1(self): def test_restart_rate_2(self): """Test restarting ARC and attaining a reaction rate coefficient""" - project = _project_name('arc_project_for_testing_delete_after_usage_restart_rate_2') - project_directory = os.path.join(ARC_PATH, 'Projects', project) + project = 'arc_project_for_testing_delete_after_usage_restart_rate_2' + project_directory = self.make_project_directory(project) base_path = os.path.join(ARC_PATH, 'arc', 'testing', 'restart', '5_TS1') restart_path = os.path.join(base_path, 'restart.yml') input_dict = read_yaml_file(path=restart_path, project_directory=project_directory) @@ -191,8 +190,8 @@ def test_restart_bde (self): """Test restarting ARC and attaining a BDE for anilino_radical.""" restart_dir = os.path.join(ARC_PATH, 'arc', 'testing', 'restart', '3_restart_bde') restart_path = os.path.join(restart_dir, 'restart.yml') - project = _project_name('test_restart_bde') - project_directory = os.path.join(ARC_PATH, 'Projects', project) + project = 'test_restart_bde' + project_directory = self.make_project_directory(project) os.makedirs(os.path.dirname(project_directory), exist_ok=True) shutil.copytree(os.path.join(restart_dir, 'calcs'), os.path.join(project_directory, 'calcs'), dirs_exist_ok=True) input_dict = read_yaml_file(path=restart_path, project_directory=project_directory) @@ -200,7 +199,7 @@ def test_restart_bde (self): arc1 = ARC(**input_dict) arc1.execute() - report_path = os.path.join(ARC_PATH, 'Projects', project, 'output', 'BDE_report.txt') + report_path = os.path.join(project_directory, 'output', 'BDE_report.txt') with open(report_path, 'r') as f: lines = f.readlines() self.assertIn(' BDE report for anilino_radical:\n', lines) @@ -224,17 +223,8 @@ def test_globalize_paths(self): def tearDownClass(cls): """ A function that is run ONCE after all unit tests in this class. - Delete all project directories created during these unit tests + Delete all files and directories created during these unit tests """ - projects = [_project_name('arc_project_for_testing_delete_after_usage_restart_thermo'), - _project_name('arc_project_for_testing_delete_after_usage_restart_rate_1'), - _project_name('arc_project_for_testing_delete_after_usage_restart_rate_2'), - _project_name('test_restart_bde'), - ] - for project in projects: - project_directory = os.path.join(ARC_PATH, 'Projects', project) - shutil.rmtree(project_directory, ignore_errors=True) - shutil.rmtree(os.path.join(ARC_PATH, 'arc', 'testing', 'restart', '4_globalized_paths', 'log_and_restart_archive'), ignore_errors=True) for file_name in ['arc.log', 'restart_paths_globalized.yml']: