Skip to content

Commit 71b63df

Browse files
committed
Updated executor handling and back up conf file if updating.
1 parent 1a8b1ad commit 71b63df

File tree

5 files changed

+151
-33
lines changed

5 files changed

+151
-33
lines changed

.flake8

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,5 +78,5 @@ ignore = B902, D205, D400, D401, D100, W503
7878

7979
per-file-ignores =
8080
tests/*: D103
81-
tests/installer/*: D101, D102, D103, D104, D107
81+
tests/installer/*: D101, D102, D103, D104, D105, D107
8282
tests/installer/main.py: E402, D101, D102, D103, D107

tests/installer/main.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -235,14 +235,21 @@ async def _confirm_quit(self) -> None:
235235

236236
async def action_quit(self) -> None:
237237
install_method = self.state.install_method
238+
messages = []
239+
if self.state.conf_backed_up:
240+
messages.append('Your previous configuration was saved in "testing.conf.bk"')
238241
if install_method is not None and install_method.name == 'custom':
239-
self.exit(message=f'Tests can be run with the following command:\n'
240-
f'\t{install_method.preview}')
242+
messages.append(f'Tests can be run with the following command:\n'
243+
f'\t{install_method.preview}')
244+
if len(messages) > 0:
245+
self.exit(message='\n\n'.join(messages))
241246
else:
242247
self.exit()
243248

244249
def set_default_executor(self) -> None:
245-
label, name = self.state.get_executor()
250+
label, name = self.state.get_batch_executor()
251+
if name is None:
252+
name = 'none'
246253

247254
batch_warner = self.get_widget_by_id('warn-no-batch')
248255
if name == 'none':

tests/installer/panels/batch_scheduler_panel.py

Lines changed: 45 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from .panel import Panel
77
from ..dialogs import TestJobsDialog
88
from ..log import log
9-
from ..state import Attr
9+
from ..state import Attr, State
1010
from ..widgets import MSelect, ShortcutButton
1111

1212
from textual import on
@@ -220,6 +220,10 @@ class BatchSchedulerPanel(Panel):
220220
('alt+t', 'toggle_test_job')
221221
]
222222

223+
def __init__(self, state: State) -> None:
224+
super().__init__(state)
225+
self._auto_scheduler: Optional[str] = None
226+
223227
def _build_widgets(self) -> Widget:
224228
return Vertical(
225229
Label('Select and configure a batch system.', classes='header'),
@@ -258,7 +262,7 @@ def _build_widgets(self) -> Widget:
258262
Input(id='mqueue-input'),
259263
classes='bs-col-2 form-row batch-valid'
260264
),
261-
Checkbox('Run [b bright_yellow]t[/b bright_yellow]est job',
265+
Checkbox('Run [b bright_yellow]t[/b bright_yellow]est job', value=False,
262266
id='cb-run-test-job', classes='bs-col-3 m-t-1 batch-valid'),
263267
classes='w-100 form-row', id='batch-system-group-2'
264268
),
@@ -278,7 +282,11 @@ def _build_widgets(self) -> Widget:
278282
)
279283

280284
async def validate(self) -> bool:
281-
if self.state.run_test_job and self.state.scheduler is not None:
285+
run_test_job = self.get_widget_by_id('cb-run-test-job')
286+
assert isinstance(run_test_job, Checkbox)
287+
scheduler = self._get_scheduler()
288+
289+
if run_test_job.value and scheduler != 'none' and scheduler != 'local':
282290
return await self.run_test_jobs()
283291
else:
284292
return True
@@ -291,14 +299,19 @@ def label(self) -> str:
291299
def name(self) -> str:
292300
return 'scheduler'
293301

294-
async def activate(self) -> None:
295-
self.update_attrs()
302+
def _get_scheduler(self) -> str:
296303
selector = self.get_widget_by_id('batch-selector')
297304
assert isinstance(selector, Select)
298-
sched = selector.selection
299-
if sched is None or sched == 'none':
300-
selector.focus(False)
301-
elif sched == 'local':
305+
value = selector.selection
306+
assert isinstance(value, str)
307+
return value
308+
309+
async def activate(self) -> None:
310+
self.update_attrs()
311+
scheduler = self._get_scheduler()
312+
if scheduler is None or scheduler == 'none':
313+
self.get_widget_by_id('batch-selector').focus(False)
314+
elif scheduler == 'local':
302315
self.app._focus_next() # type: ignore
303316
else:
304317
self.get_widget_by_id('account-input').focus(False)
@@ -316,40 +329,48 @@ def update_attrs(self) -> None:
316329

317330
@on(Select.Changed, '#batch-selector')
318331
def batch_system_selected(self) -> None:
319-
selector = cast(Select[str], self.get_widget_by_id('batch-selector'))
320-
sched = selector.selection
321-
disabled = (sched is None or sched == 'none' or sched == 'local')
322-
if disabled:
323-
self.state.scheduler = None
324-
else:
325-
self.state.scheduler = sched
332+
scheduler = self._get_scheduler()
333+
334+
self.state.set_batch_executor(scheduler)
335+
336+
disabled = (scheduler is None or scheduler == 'none' or scheduler == 'local')
326337
for widget in self.query('.batch-valid'):
327338
widget.disabled = disabled
328-
if sched == 'local':
339+
run_test_job = self.get_widget_by_id('cb-run-test-job')
340+
assert isinstance(run_test_job, Checkbox)
341+
run_test_job.value = not disabled
342+
if scheduler == 'local':
329343
self.app._focus_next() # type: ignore
330344
else:
331345
self.get_widget_by_id('account-input').focus(False)
332346

333347
def set_scheduler(self, name: str) -> None:
348+
if name == '':
349+
name = 'none'
334350
selector = self.get_widget_by_id('batch-selector')
335351
assert isinstance(selector, Select)
336352
selector.value = name
353+
self._auto_scheduler = name
337354

338355
@on(Input.Submitted, '#account-input')
339-
def account_submitted(self) -> None:
356+
def account_submitted(self, event: Input.Submitted) -> None:
357+
self.state.update_conf('account', event.value)
340358
next = self.get_widget_by_id('queue-input')
341359
next.focus(False)
342360

343361
@on(Input.Submitted, '#queue-input')
344362
def queue_submitted(self, event: Input.Submitted) -> None:
363+
self.state.update_conf('queue_name', event.value)
345364
next = self.get_widget_by_id('mqueue-input')
346365
assert isinstance(next, Input)
347366
if next.value == '':
348367
next.value = event.input.value
368+
self.state.update_conf('multi_node_queue_name', event.value)
349369
next.focus(False)
350370

351371
@on(Input.Submitted, '#mqueue-input')
352-
def mqueue_submitted(self) -> None:
372+
def mqueue_submitted(self, event: Input.Submitted) -> None:
373+
self.state.update_conf('multi_node_queue_name', event.value)
353374
self.app._focus_next() # type: ignore
354375

355376
@on(Button.Pressed, '#btn-edit-attrs')
@@ -405,12 +426,13 @@ async def _run_test_job(self, jd: TestJobsDialog, job_no: int, label: str,
405426
return False
406427

407428
def _launch_job(self, test_name: str, rspec: Optional[ResourceSpecV1]) -> Job:
408-
s = self.state.scheduler
409-
assert s is not None
429+
scheduler = self._get_scheduler()
430+
assert scheduler is not None
410431

411-
ex = JobExecutor.get_instance(s)
432+
ex = JobExecutor.get_instance(scheduler)
412433

413434
attrs = JobAttributes()
435+
414436
account = self.state.conf.get('account', '')
415437
if rspec is not None and rspec.computed_node_count > 1:
416438
queue = self.state.conf.get('multi_node_queue_name', '')
@@ -423,7 +445,7 @@ def _launch_job(self, test_name: str, rspec: Optional[ResourceSpecV1]) -> Job:
423445
for attr in self.state.attrs:
424446
if re.match(attr.filter, test_name):
425447
attrs.set_custom_attribute(attr.name, attr.value)
426-
448+
log.write(f'attrs: {attrs}, rspec: {rspec}\n')
427449
job = Job(JobSpec('/bin/date', attributes=attrs, resources=rspec))
428450
ex.submit(job)
429451
log.write('Job submitted\n')

tests/installer/panels/intro_panel.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,5 @@ async def activate(self) -> None:
4141
if result == 'quit':
4242
self.app.exit()
4343
if result == 'update':
44+
self.state.backup_conf()
4445
self.app.disable_install() # type: ignore

tests/installer/state.py

Lines changed: 94 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import json
44
import os
55
import pathlib
6+
import shutil
67
import types
78

89
import requests
@@ -19,6 +20,10 @@
1920
Attr = namedtuple('Attr', ['filter', 'name', 'value'])
2021

2122

23+
_EXECUTOR_LABELS = {'slurm': 'Slurm', 'pbs': 'PBS', 'cobalt': 'Cobalt', 'lsf': 'LSF',
24+
'none': 'None'}
25+
26+
2227
async def _to_thread(func, /, *args, **kwargs): # type: ignore
2328
loop = asyncio.get_running_loop()
2429
func_call = functools.partial(func, *args, **kwargs)
@@ -43,10 +48,33 @@ def getoption(self, name: str) -> Union[str, int, bool, None]:
4348
return self.dict[name]
4449

4550

51+
class Exec:
52+
def __init__(self, pair: str) -> None:
53+
array = pair.split(':')
54+
assert len(array) > 0 and len(array) <= 3
55+
self.name = array[0]
56+
self.launcher = ''
57+
self.url = ''
58+
if len(array) > 1:
59+
self.launcher = array[1]
60+
if len(array) > 2:
61+
self.url = array[2]
62+
63+
def __str__(self) -> str:
64+
if self.launcher == '':
65+
return self.name
66+
else:
67+
return f'{self.name}:{self.launcher}'
68+
69+
def __repr__(self) -> str:
70+
return str(self)
71+
72+
4673
class State:
4774
def __init__(self, conftest: types.ModuleType, ci_runner: types.ModuleType):
4875
self.conf = ci_runner.read_conf('testing.conf')
4976
self.env = conftest._discover_environment(ConfWrapper(self.conf))
77+
self.translate_launcher = conftest._translate_launcher
5078
log.write('Conf: ' + str(self.conf) + '\n')
5179
log.write(str(self.env) + '\n')
5280
self.disable_install = False
@@ -56,6 +84,9 @@ def __init__(self, conftest: types.ModuleType, ci_runner: types.ModuleType):
5684
self.attrs = self._parse_attributes()
5785
self.run_test_job = True
5886
self.has_key = KEY_PATH.exists()
87+
self.conf_backed_up = False
88+
self.execs, self.batch_exec = self._parse_executors()
89+
log.write(f'execs: {self.execs}, bexec: {self.batch_exec}\n')
5990
if self.has_key:
6091
with open(KEY_PATH, 'r') as f:
6192
self.key = f.read().strip()
@@ -77,6 +108,7 @@ def _parse_attributes(self) -> List[Attr]:
77108
def _write_conf_value(self, name: str, value: str) -> None:
78109
self.conf[name] = value
79110
nlen = len(name)
111+
found = False
80112
with open('testing.conf', 'r') as old:
81113
with open('testing.conf.new', 'w') as new:
82114
for line in old:
@@ -85,8 +117,11 @@ def _write_conf_value(self, name: str, value: str) -> None:
85117
new.write(line)
86118
elif sline.startswith(name) and sline[nlen] in [' ', '\t', '=']:
87119
new.write(f'{name} = {value}\n')
120+
found = True
88121
else:
89122
new.write(line)
123+
if not found:
124+
new.write(line)
90125
os.rename('testing.conf.new', 'testing.conf')
91126

92127
def update_conf(self, name: str, value: str) -> None:
@@ -95,6 +130,10 @@ def update_conf(self, name: str, value: str) -> None:
95130
else:
96131
self._write_conf_value(name, value)
97132

133+
def backup_conf(self) -> None:
134+
shutil.copyfile('testing.conf', 'testing.conf.bk')
135+
self.conf_backed_up = True
136+
98137
async def request(self, query: str, data: Dict[str, object], title: str,
99138
error_cb: Callable[[str, str], Awaitable[Optional[Dict[str, object]]]]) \
100139
-> Optional[Dict[str, object]]:
@@ -166,14 +205,63 @@ def _write_custom_attrs(self, attrs: List[Attr]) -> None:
166205
new.write(line)
167206
os.rename('testing.conf.new', 'testing.conf')
168207

169-
def get_executor(self) -> Tuple[str, str]:
208+
def _executor_label(self, name: Optional[str]) -> Optional[str]:
209+
if name in _EXECUTOR_LABELS:
210+
return _EXECUTOR_LABELS[name]
211+
else:
212+
return name
213+
214+
def get_auto_executor(self) -> Optional[str]:
170215
if self.env['has_slurm']:
171-
return ('Slurm', 'slurm')
216+
return 'slurm'
172217
if self.env['has_pbs']:
173-
return ('PBS', 'pbs')
218+
return 'pbs'
174219
if self.env['has_lsf']:
175-
return ('LSF', 'lsf')
220+
return 'lsf'
176221
if self.env['has_cobalt']:
177-
return ('Cobalt', 'cobalt')
222+
return 'cobalt'
223+
224+
return 'none'
225+
226+
def get_batch_executor(self) -> Tuple[Optional[str], Optional[str]]:
227+
return self._executor_label(self.batch_exec), self.batch_exec
228+
229+
def _parse_executors(self) -> Tuple[List[Exec], Optional[str]]:
230+
conf_exec = self.conf.get('executors', '')
231+
execs = []
232+
for exec in conf_exec.split(','):
233+
exec = exec.strip()
234+
if exec != '':
235+
execs.append(Exec(exec))
236+
if len(execs) == 0:
237+
execs = [Exec('auto')]
238+
batch = None
239+
for exec in execs:
240+
if exec.name == 'auto' or exec.name == 'auto_q':
241+
batch = self.get_auto_executor()
242+
elif exec.name not in ['local', 'batch-test']:
243+
batch = exec.name
244+
245+
return execs, batch
246+
247+
def _executors_str(self) -> str:
248+
return ', '.join([str(x) for x in self.execs])
249+
250+
def set_batch_executor(self, scheduler: str) -> None:
251+
auto_exec = self.get_auto_executor()
252+
if auto_exec == scheduler or scheduler == 'none':
253+
self.execs = [Exec('auto')]
254+
elif scheduler == 'local' and auto_exec == 'none':
255+
self.execs = [Exec('auto')]
256+
else:
257+
existing = None
258+
for exec in self.execs:
259+
if exec.name not in ['auto', 'auto_q', 'local', 'batch-test']:
260+
exec.name = scheduler
261+
exec.launcher = 'auto_l'
262+
if not existing:
263+
self.execs.append(Exec(scheduler))
264+
265+
log.write(f'set_be({scheduler}): execs: {self.execs}, str: {self._executors_str()}\n')
178266

179-
return ('None', 'none')
267+
self._write_conf_value('executors', self._executors_str())

0 commit comments

Comments
 (0)