99from unittest .mock import MagicMock , patch
1010import os
1111import shutil
12+ import tempfile
1213from types import SimpleNamespace
1314
1415
@@ -75,105 +76,125 @@ class TestScheduler(unittest.TestCase):
7576 """
7677 Contains unit tests for the Scheduler class
7778 """
78- @classmethod
79- def setUpClass (cls ):
79+ def make_project_directory (self , name ):
8080 """
81- A method that is run before all unit tests in this class.
81+ Create a unique project directory for the running test and schedule its removal.
82+
83+ Args:
84+ name (str): A descriptive name, used as the directory name prefix.
85+
86+ Returns:
87+ str: The path of the created directory.
8288 """
83- cls .maxDiff = None
84- cls .ess_settings = {'gaussian' : ['server1' ], 'molpro' : ['server2' , 'server1' ], 'qchem' : ['server1' ]}
85- cls .project_directory = os .path .join (ARC_PATH , 'Projects' , 'arc_project_for_testing_delete_after_usage3' )
89+ projects_path = os .path .join (ARC_PATH , 'Projects' )
90+ os .makedirs (projects_path , exist_ok = True )
91+ project_directory = tempfile .mkdtemp (prefix = f'{ name } _' , dir = projects_path )
92+ self .addCleanup (shutil .rmtree , project_directory , ignore_errors = True )
93+ return project_directory
94+
95+ def setUp (self ):
96+ """
97+ A method that is run before each unit test in this class.
98+
99+ The fixtures below are rebuilt per test: many of these tests mutate the schedulers,
100+ the species and the jobs they are handed, so a fixture shared across tests (or across
101+ xdist workers, each of which runs its own class-level setup) would make the outcome of
102+ one test depend on which other tests ran before it.
103+ """
104+ self .maxDiff = None
105+ self .ess_settings = {'gaussian' : ['server1' ], 'molpro' : ['server2' , 'server1' ], 'qchem' : ['server1' ]}
106+ self .project_directory = self .make_project_directory ('arc_project_for_testing_delete_after_usage3' )
86107 xyz1 = str_to_xyz ("""C -0.57422867 -0.01669771 0.01229213
87108N 0.82084044 0.08279104 -0.37769346
88109H -1.05737005 -0.84067772 -0.52007494
89110H -1.10211468 0.90879867 -0.23383011
90111H -0.66133128 -0.19490562 1.08785111
91112H 0.88047852 0.26966160 -1.37780789
92113H 1.27889520 -0.81548721 -0.22940984""" )
93- cls .spc1 = ARCSpecies (label = 'methylamine' , smiles = 'CN' , xyz = xyz1 )
94- cls .spc2 = ARCSpecies (label = 'C2H6' , smiles = 'CC' )
114+ self .spc1 = ARCSpecies (label = 'methylamine' , smiles = 'CN' , xyz = xyz1 )
115+ self .spc2 = ARCSpecies (label = 'C2H6' , smiles = 'CC' )
95116 xyz3 = """C 1.11424367 -0.01231165 -0.11493630
96117C -0.07257945 -0.17830906 -0.16010022
97118O -1.38500471 -0.36381519 -0.20928090
98119H 2.16904830 0.12689206 -0.07152274
99120H -1.82570782 0.42754384 -0.56130718"""
100- cls .spc3 = ARCSpecies (label = 'CtripCO' , smiles = 'C#CO' , xyz = xyz3 )
101- cls .job1 = job_factory (job_adapter = 'gaussian' , project = 'project_test' , ess_settings = cls .ess_settings ,
102- species = [cls .spc1 ], xyz = xyz1 , job_type = 'conf_opt' ,
103- conformer = 0 , level = Level (repr = {'method' : 'b97-d3' , 'basis' : '6-311+g(d,p)' }),
104- project_directory = cls .project_directory , job_num = 101 )
105- cls .job2 = job_factory (job_adapter = 'gaussian' , project = 'project_test' , ess_settings = cls .ess_settings ,
106- species = [cls .spc1 ], xyz = xyz1 , job_type = 'conf_opt' ,
107- conformer = 1 , level = Level (repr = {'method' : 'b97-d3' , 'basis' : '6-311+g(d,p)' }),
108- project_directory = cls .project_directory , job_num = 102 )
109- cls .job3 = job_factory (job_adapter = 'qchem' , project = 'project_test' , ess_settings = cls .ess_settings ,
110- species = [cls .spc2 ], job_type = 'freq' ,
111- level = Level (repr = {'method' : 'wb97x-d3' , 'basis' : '6-311+g(d,p)' }),
112- project_directory = cls .project_directory , job_num = 103 )
113- cls .job4 = job_factory (job_adapter = 'gaussian' , project = 'project_test_4' , ess_settings = cls .ess_settings ,
114- species = [cls .spc1 ], xyz = xyz1 , job_type = 'scan' , torsions = [[3 , 1 , 2 , 6 ]], rotor_index = 0 ,
115- level = Level (repr = {'method' : 'b3lyp' , 'basis' : 'cbsb7' }),
116- project_directory = cls .project_directory , job_num = 104 )
117- cls .job_types1 = {'conf_opt' : True ,
118- 'conf_sp' : False ,
119- 'opt' : True ,
120- 'fine' : False ,
121- 'freq' : True ,
122- 'sp' : True ,
123- 'rotors' : False ,
124- 'orbitals' : False ,
125- 'lennard_jones' : False ,
126- }
127- cls .job_types2 = {'conf_opt' : True ,
128- 'conf_sp' : False ,
129- 'opt' : True ,
130- 'fine' : False ,
131- 'freq' : True ,
132- 'sp' : True ,
133- 'rotors' : True ,
134- }
135- cls .sched1 = Scheduler (project = 'project_test_1' , ess_settings = cls .ess_settings ,
136- species_list = [cls .spc1 , cls .spc2 , cls .spc3 ],
137- composite_method = None ,
138- conformer_opt_level = Level (repr = default_levels_of_theory ['conformer' ]),
139- opt_level = Level (repr = default_levels_of_theory ['opt' ]),
140- freq_level = Level (repr = default_levels_of_theory ['freq' ]),
141- sp_level = Level (repr = default_levels_of_theory ['sp' ]),
142- scan_level = Level (repr = default_levels_of_theory ['scan' ]),
143- ts_guess_level = Level (repr = default_levels_of_theory ['ts_guesses' ]),
144- project_directory = cls .project_directory ,
145- testing = True ,
146- job_types = cls .job_types1 ,
147- orbitals_level = default_levels_of_theory ['orbitals' ],
148- adaptive_levels = None ,
149- )
150- cls .sched2 = Scheduler (project = 'project_test_2' , ess_settings = cls .ess_settings ,
151- species_list = [cls .spc1 , cls .spc2 , cls .spc3 ],
152- composite_method = None ,
153- conformer_opt_level = Level (repr = default_levels_of_theory ['conformer' ]),
154- opt_level = Level (repr = default_levels_of_theory ['opt' ]),
155- freq_level = Level (repr = default_levels_of_theory ['freq' ]),
156- sp_level = Level (repr = default_levels_of_theory ['sp' ]),
157- scan_level = Level (repr = default_levels_of_theory ['scan' ]),
158- ts_guess_level = Level (repr = default_levels_of_theory ['ts_guesses' ]),
159- project_directory = cls .project_directory ,
160- testing = True ,
161- job_types = cls .job_types1 ,
162- orbitals_level = default_levels_of_theory ['orbitals' ],
163- adaptive_levels = None ,
164- )
165- cls .sched3 = Scheduler (project = 'project_test_4' , ess_settings = cls .ess_settings ,
166- species_list = [cls .spc1 ],
167- composite_method = Level (repr = 'CBS-QB3' ),
168- conformer_opt_level = Level (repr = default_levels_of_theory ['conformer' ]),
169- opt_level = Level (repr = default_levels_of_theory ['freq_for_composite' ]),
170- freq_level = Level (repr = default_levels_of_theory ['freq_for_composite' ]),
171- scan_level = Level (repr = default_levels_of_theory ['scan_for_composite' ]),
172- ts_guess_level = Level (repr = default_levels_of_theory ['ts_guesses' ]),
173- project_directory = cls .project_directory ,
174- testing = True ,
175- job_types = cls .job_types2 ,
176- )
121+ self .spc3 = ARCSpecies (label = 'CtripCO' , smiles = 'C#CO' , xyz = xyz3 )
122+ self .job1 = job_factory (job_adapter = 'gaussian' , project = 'project_test' , ess_settings = self .ess_settings ,
123+ species = [self .spc1 ], xyz = xyz1 , job_type = 'conf_opt' ,
124+ conformer = 0 , level = Level (repr = {'method' : 'b97-d3' , 'basis' : '6-311+g(d,p)' }),
125+ project_directory = self .project_directory , job_num = 101 )
126+ self .job2 = job_factory (job_adapter = 'gaussian' , project = 'project_test' , ess_settings = self .ess_settings ,
127+ species = [self .spc1 ], xyz = xyz1 , job_type = 'conf_opt' ,
128+ conformer = 1 , level = Level (repr = {'method' : 'b97-d3' , 'basis' : '6-311+g(d,p)' }),
129+ project_directory = self .project_directory , job_num = 102 )
130+ self .job3 = job_factory (job_adapter = 'qchem' , project = 'project_test' , ess_settings = self .ess_settings ,
131+ species = [self .spc2 ], job_type = 'freq' ,
132+ level = Level (repr = {'method' : 'wb97x-d3' , 'basis' : '6-311+g(d,p)' }),
133+ project_directory = self .project_directory , job_num = 103 )
134+ self .job4 = job_factory (job_adapter = 'gaussian' , project = 'project_test_4' , ess_settings = self .ess_settings ,
135+ species = [self .spc1 ], xyz = xyz1 , job_type = 'scan' , torsions = [[3 , 1 , 2 , 6 ]], rotor_index = 0 ,
136+ level = Level (repr = {'method' : 'b3lyp' , 'basis' : 'cbsb7' }),
137+ project_directory = self .project_directory , job_num = 104 )
138+ self .job_types1 = {'conf_opt' : True ,
139+ 'conf_sp' : False ,
140+ 'opt' : True ,
141+ 'fine' : False ,
142+ 'freq' : True ,
143+ 'sp' : True ,
144+ 'rotors' : False ,
145+ 'orbitals' : False ,
146+ 'lennard_jones' : False ,
147+ }
148+ self .job_types2 = {'conf_opt' : True ,
149+ 'conf_sp' : False ,
150+ 'opt' : True ,
151+ 'fine' : False ,
152+ 'freq' : True ,
153+ 'sp' : True ,
154+ 'rotors' : True ,
155+ }
156+ self .sched1 = Scheduler (project = 'project_test_1' , ess_settings = self .ess_settings ,
157+ species_list = [self .spc1 , self .spc2 , self .spc3 ],
158+ composite_method = None ,
159+ conformer_opt_level = Level (repr = default_levels_of_theory ['conformer' ]),
160+ opt_level = Level (repr = default_levels_of_theory ['opt' ]),
161+ freq_level = Level (repr = default_levels_of_theory ['freq' ]),
162+ sp_level = Level (repr = default_levels_of_theory ['sp' ]),
163+ scan_level = Level (repr = default_levels_of_theory ['scan' ]),
164+ ts_guess_level = Level (repr = default_levels_of_theory ['ts_guesses' ]),
165+ project_directory = self .project_directory ,
166+ testing = True ,
167+ job_types = self .job_types1 ,
168+ orbitals_level = default_levels_of_theory ['orbitals' ],
169+ adaptive_levels = None ,
170+ )
171+ self .sched2 = Scheduler (project = 'project_test_2' , ess_settings = self .ess_settings ,
172+ species_list = [self .spc1 , self .spc2 , self .spc3 ],
173+ composite_method = None ,
174+ conformer_opt_level = Level (repr = default_levels_of_theory ['conformer' ]),
175+ opt_level = Level (repr = default_levels_of_theory ['opt' ]),
176+ freq_level = Level (repr = default_levels_of_theory ['freq' ]),
177+ sp_level = Level (repr = default_levels_of_theory ['sp' ]),
178+ scan_level = Level (repr = default_levels_of_theory ['scan' ]),
179+ ts_guess_level = Level (repr = default_levels_of_theory ['ts_guesses' ]),
180+ project_directory = self .project_directory ,
181+ testing = True ,
182+ job_types = self .job_types1 ,
183+ orbitals_level = default_levels_of_theory ['orbitals' ],
184+ adaptive_levels = None ,
185+ )
186+ self .sched3 = Scheduler (project = 'project_test_4' , ess_settings = self .ess_settings ,
187+ species_list = [self .spc1 ],
188+ composite_method = Level (repr = 'CBS-QB3' ),
189+ conformer_opt_level = Level (repr = default_levels_of_theory ['conformer' ]),
190+ opt_level = Level (repr = default_levels_of_theory ['freq_for_composite' ]),
191+ freq_level = Level (repr = default_levels_of_theory ['freq_for_composite' ]),
192+ scan_level = Level (repr = default_levels_of_theory ['scan_for_composite' ]),
193+ ts_guess_level = Level (repr = default_levels_of_theory ['ts_guesses' ]),
194+ project_directory = self .project_directory ,
195+ testing = True ,
196+ job_types = self .job_types2 ,
197+ )
177198
178199 def test_conformers (self ):
179200 """Test the parse_conformer_energy() and determine_most_stable_conformer() methods"""
@@ -410,6 +431,7 @@ def test_determine_adaptive_level(self):
410431
411432 def test_initialize_output_dict (self ):
412433 """Test Scheduler.initialize_output_dict"""
434+ self .sched1 .output ['C2H6' ]['info' ] = 'some text'
413435 self .assertTrue (self .sched1 ._does_output_dict_contain_info ())
414436 self .sched1 .output = dict ()
415437 self .assertEqual (self .sched1 .output , dict ())
@@ -851,7 +873,7 @@ def test_check_rxn_e0_by_spc(self):
851873 'job_types' : {'conf_opt' : True , 'conf_sp' : False , 'opt' : True , 'freq' : True , 'sp' : True , 'rotors' : True , 'irc' : True , 'fine' : True },
852874 },
853875 }
854- project_directory = os . path . join ( ARC_PATH , 'Projects' , 'arc_project_for_testing_delete_after_usage6' )
876+ project_directory = self . make_project_directory ( 'arc_project_for_testing_delete_after_usage6' )
855877 os .makedirs (os .path .join (project_directory , 'output' , 'Species' , 'nC3H7' , 'geometry' ), exist_ok = True )
856878 os .makedirs (os .path .join (project_directory , 'output' , 'Species' , 'iC3H7' , 'geometry' ), exist_ok = True )
857879 os .makedirs (os .path .join (project_directory , 'output' , 'rxns' , 'TS0' , 'geometry' ), exist_ok = True )
@@ -863,7 +885,7 @@ def test_check_rxn_e0_by_spc(self):
863885 dst = os .path .join (project_directory , 'output' , 'rxns' , 'TS0' , 'geometry' , 'freq.out' ))
864886 sched = Scheduler (project = 'test_rxn_e0_check' ,
865887 ess_settings = self .ess_settings ,
866- project_directory = os . path . join ( ARC_PATH , 'Projects' , 'arc_project_for_testing_delete_after_usage6' ) ,
888+ project_directory = project_directory ,
867889 rxn_list = [rxn ],
868890 species_list = rxn .r_species + rxn .p_species + [rxn .ts_species ],
869891 kinetics_adapter = 'arkane' ,
@@ -880,20 +902,16 @@ def test_check_rxn_e0_by_spc(self):
880902 job_type = 'freq' ,
881903 level = Level (repr = 'B3LYP/6-31G(d,p)' ),
882904 project = 'test_project' ,
883- project_directory = os .path .join (ARC_PATH ,
884- 'Projects' ,
885- 'arc_project_for_testing_delete_after_usage6' ),
905+ project_directory = project_directory ,
886906 )
887907 job_1 .local_path_to_output_file = os .path .join (ARC_TESTING_PATH , 'freq' , 'TS_nC3H7-iC3H7.out' )
888908 check_ts (reaction = rxn , verbose = True , job = job_1 , checks = ['NMD' ])
889909 self .assertEqual (rxn .ts_species .ts_checks , {'E0' : None , 'e_elect' : True , 'IRC' : None , 'freq' : True , 'NMD' : True , 'warnings' : '' })
890910
891911 def test_save_e_elect (self ):
892912 """Test the save_e_elect() method."""
893- project_directory = os . path . join ( ARC_PATH , 'Projects' , 'save_e_elect' )
913+ project_directory = self . make_project_directory ( 'save_e_elect' )
894914 e_elect_summary_path = os .path .join (project_directory , 'output' , 'e_elect_summary.yml' )
895- if os .path .isfile (os .path .join (project_directory , 'output' , 'e_elect_summary.yml' )):
896- os .remove (os .path .join (project_directory , 'output' , 'e_elect_summary.yml' ))
897915 sched = Scheduler (project = 'test_save_e_elect' ,
898916 ess_settings = self .ess_settings ,
899917 project_directory = project_directory ,
@@ -917,7 +935,6 @@ def test_save_e_elect(self):
917935 sp_path = os .path .join (ARC_TESTING_PATH , 'sp' , 'mehylamine_CCSD(T).out' ))
918936 content = read_yaml_file (e_elect_summary_path )
919937 self .assertEqual (content , {'formaldehyde' : - 300621.95378630824 , 'mehylamine' : - 251360.00924747565 })
920- shutil .rmtree (project_directory , ignore_errors = True )
921938
922939 def test_species_has_geo_sp_freq (self ):
923940 """Test the species_has_geo() / species_has_sp() / species_has_freq() functions."""
@@ -2060,17 +2077,6 @@ def test_run_job_does_not_alias_level_args(self, mock_job_factory):
20602077 args ['keyword' ]['dft_grid' ] = 'defgrid2'
20612078 self .assertEqual (level .args , {'keyword' : {'opt' : 'opt=(verytight)' }, 'block' : dict ()})
20622079
2063- @classmethod
2064- def tearDownClass (cls ):
2065- """
2066- A function that is run ONCE after all unit tests in this class.
2067- Delete all project directories created during these unit tests
2068- """
2069- projects = ['arc_project_for_testing_delete_after_usage3' , 'arc_project_for_testing_delete_after_usage6' ]
2070- for project in projects :
2071- project_directory = os .path .join (ARC_PATH , 'Projects' , project )
2072- shutil .rmtree (project_directory , ignore_errors = True )
2073-
20742080
20752081class TestSpawnTsJobsAdmission (unittest .TestCase ):
20762082 """
0 commit comments