Skip to content

Commit 4f78f56

Browse files
authored
Save and use last test/series ID for relevant commands (#880)
* Pass some unit tests * Pass some more unit tests * Add unit tests for test ids * Add range expansion unit tests for test ids. * Pass some more unit tests * Fix log command * Fix ls command * Fix cat command * Fix style issues * Pass some more unit tests * Fix auto_last option * Fix unmatched paren * Fix status summary unit test * Fix pav wait command
1 parent 1091a5f commit 4f78f56

File tree

18 files changed

+531
-140
lines changed

18 files changed

+531
-140
lines changed

lib/pavilion/cmd_utils.py

Lines changed: 57 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import sys
99
import time
1010
from pathlib import Path
11-
from typing import List, TextIO, Union, Iterator
11+
from typing import List, TextIO, Union, Iterator, Optional
1212
from collections import defaultdict
1313

1414
from pavilion import config
@@ -22,74 +22,28 @@
2222
from pavilion.errors import TestRunError, CommandError, TestSeriesError, \
2323
PavilionError, TestGroupError
2424
from pavilion.test_run import TestRun, load_tests, TestAttributes
25+
from pavilion.test_ids import TestID, SeriesID
2526
from pavilion.types import ID_Pair
2627
from pavilion.micro import flatten
2728

2829
LOGGER = logging.getLogger(__name__)
2930

3031

31-
def expand_range(test_range: str) -> List[str]:
32-
"""Expand a given test or series range into a list of the individual
33-
tests or series in that range"""
34-
35-
tests = []
36-
37-
if test_range == "all":
38-
return ["all"]
39-
40-
elif '-' in test_range:
41-
id_start, id_end = test_range.split('-', 1)
42-
43-
if id_start.startswith('s'):
44-
series_range_start = int(id_start.replace('s',''))
45-
46-
if id_end.startswith('s'):
47-
series_range_end = int(id_end.replace('s',''))
48-
else:
49-
series_range_end = int(id_end)
50-
51-
series_ids = range(series_range_start, series_range_end+1)
52-
53-
for sid in series_ids:
54-
tests.append('s' + str(sid))
55-
else:
56-
test_range_start = int(id_start)
57-
test_range_end = int(id_end)
58-
test_ids = range(test_range_start, test_range_end+1)
59-
60-
for tid in test_ids:
61-
tests.append(str(tid))
62-
else:
63-
tests.append(test_range)
64-
65-
return tests
66-
67-
68-
def expand_ranges(ranges: Iterator[str]) -> Iterator[str]:
69-
"""Given a sequence of test and series ranges, expand them
70-
into a sequence of individual tests and series."""
71-
72-
return flatten(map(expand_range, ranges))
73-
74-
75-
#pylint: disable=C0103
76-
def is_series_id(id: str) -> bool:
77-
"""Determine whether the given ID is a series ID."""
78-
79-
return len(id) > 0 and id[0].lower() == 's'
80-
81-
8232
def load_last_series(pav_cfg, errfile: TextIO) -> Union[series.TestSeries, None]:
8333
"""Load the series object for the last series run by this user on this system."""
8434

8535
try:
8636
series_id = series.load_user_series_id(pav_cfg)
8737
except series.TestSeriesError as err:
88-
output.fprint("Failed to find last series: {}".format(err.args[0]), file=errfile)
38+
output.fprint(errfile, "Failed to find last series: {}".format(err.args[0]))
39+
return None
40+
41+
if series_id is None:
42+
output.fprint(errfile, "Failed to find last series.")
8943
return None
9044

9145
try:
92-
return series.TestSeries.load(pav_cfg, series_id)
46+
return series.TestSeries.load(pav_cfg, series_id.id_str)
9347
except series.TestSeriesError as err:
9448
output.fprint(errfile, "Failed to load last series: {}".format(err.args[0]))
9549
return None
@@ -133,32 +87,14 @@ def arg_filtered_tests(pav_cfg: "PavConfig", args: argparse.Namespace,
13387
sys_name = getattr(args, 'sys_name', sys_vars.get_vars(defer=True).get('sys_name'))
13488
sort_by = getattr(args, 'sort_by', 'created')
13589

136-
ids = []
137-
138-
for test_range in args.tests:
139-
ids.extend(expand_range(test_range))
140-
141-
args.tests = ids
142-
14390
has_filter_defaults = False
14491

14592
for arg, default in filters.TEST_FILTER_DEFAULTS.items():
14693
if hasattr(args, arg) and default != getattr(args, arg):
14794
has_filter_defaults = True
14895
break
14996

150-
# "all" takes priority over everything else
151-
if "all" in args.tests:
152-
args.tests = ["all"]
153-
elif "last" in args.tests:
154-
args.tests = ["last"]
155-
elif len(args.tests) == 0:
156-
if has_filter_defaults or args.filter is not None:
157-
args.tests = ["all"]
158-
else:
159-
args.tests = ["last"]
160-
161-
if "all" in args.tests and args.filter is not None and not has_filter_defaults:
97+
if SeriesID("all") in args.tests and args.filter is not None and not has_filter_defaults:
16298
output.fprint(verbose, "Using default search filters: The current system, user, and "
16399
"created less than 1 day ago.", color=output.CYAN)
164100
args.filter = make_filter_query()
@@ -173,7 +109,7 @@ def arg_filtered_tests(pav_cfg: "PavConfig", args: argparse.Namespace,
173109

174110
order_func, order_asc = filters.get_sort_opts(sort_by, "TEST")
175111

176-
if "all" in args.tests:
112+
if SeriesID("all") in args.tests:
177113
tests = dir_db.SelectItems([], [])
178114
working_dirs = set(map(lambda cfg: cfg['working_dir'],
179115
pav_cfg.configs.values()))
@@ -235,10 +171,7 @@ def arg_filtered_series(pav_cfg: config.PavConfig, args: argparse.Namespace,
235171
limit = getattr(args, 'limit', filters.SERIES_FILTER_DEFAULTS['limit'])
236172
verbose = verbose or io.StringIO()
237173

238-
if not args.series:
239-
args.series = ['last']
240-
241-
if 'all' in args.series:
174+
if SeriesID('all') in args.series:
242175
for arg, default in filters.SERIES_FILTER_DEFAULTS.items():
243176
if hasattr(args, arg) and default != getattr(args, arg):
244177
break
@@ -252,14 +185,14 @@ def arg_filtered_series(pav_cfg: config.PavConfig, args: argparse.Namespace,
252185
for sid in args.series:
253186
# Go through each provided sid (including last and all) and find all
254187
# matching series. Then only add them if we haven't seen them yet.
255-
if sid == 'last':
188+
if sid.last():
256189
last_series = load_last_series(pav_cfg, verbose)
257190
if last_series is None:
258191
return []
259192

260193
found_series.append(last_series.info())
261194

262-
elif sid == 'all':
195+
elif sid.all():
263196
sort_by = getattr(args, 'sort_by', filters.SERIES_FILTER_DEFAULTS['sort_by'])
264197
order_func, order_asc = filters.get_sort_opts(sort_by, 'SERIES')
265198

@@ -283,7 +216,7 @@ def arg_filtered_series(pav_cfg: config.PavConfig, args: argparse.Namespace,
283216
limit=limit,
284217
).data
285218
else:
286-
found_series.append(series.SeriesInfo.load(pav_cfg, sid))
219+
found_series.append(series.SeriesInfo.load(pav_cfg, sid.id_str))
287220

288221
matching_series = []
289222
for sinfo in found_series:
@@ -358,20 +291,16 @@ def test_list_to_paths(pav_cfg, req_tests, errfile=None) -> List[Path]:
358291
test_paths = []
359292
for raw_id in req_tests:
360293

361-
if raw_id == 'last':
294+
if isinstance(raw_id, SeriesID) and raw_id.last():
362295
raw_id = series.load_user_series_id(pav_cfg, errfile)
363296
if raw_id is None:
364297
output.fprint(errfile, "User has no 'last' series for this machine.",
365298
color=output.YELLOW)
366299
continue
367300

368-
if raw_id is None or not raw_id:
369-
continue
370-
371-
if '.' in raw_id or utils.is_int(raw_id):
372-
# This is a test id.
301+
if isinstance(raw_id, TestID):
373302
try:
374-
test_wd, _id = TestRun.parse_raw_id(pav_cfg, raw_id)
303+
test_wd, _id = TestRun.parse_raw_id(pav_cfg, raw_id.id_str)
375304
except TestRunError as err:
376305
output.fprint(errfile, err, color=output.YELLOW)
377306
continue
@@ -382,18 +311,17 @@ def test_list_to_paths(pav_cfg, req_tests, errfile=None) -> List[Path]:
382311
output.fprint(errfile,
383312
"Test run with id '{}' could not be found.".format(raw_id),
384313
color=output.YELLOW)
385-
elif raw_id[0] == 's' and utils.is_int(raw_id[1:]):
386-
# A series.
314+
elif isinstance(raw_id, SeriesID):
387315
try:
388316
test_paths.extend(
389-
series.list_series_tests(pav_cfg, raw_id))
317+
series.list_series_tests(pav_cfg, raw_id.id_str))
390318
except TestSeriesError:
391319
output.fprint(errfile, "Invalid series id '{}'".format(raw_id),
392320
color=output.YELLOW)
393321
else:
394322
# A group
395323
try:
396-
group = groups.TestGroup(pav_cfg, raw_id)
324+
group = groups.TestGroup(pav_cfg, raw_id.id_str)
397325
except TestGroupError as err:
398326
output.fprint(
399327
errfile,
@@ -419,30 +347,21 @@ def test_list_to_paths(pav_cfg, req_tests, errfile=None) -> List[Path]:
419347

420348

421349
def _filter_tests_by_raw_id(pav_cfg, id_pairs: List[ID_Pair],
422-
exclude_ids: List[str]) -> List[ID_Pair]:
350+
exclude_ids: List[TestID]) -> List[ID_Pair]:
423351
"""Filter the given tests by raw id."""
424352

425353
exclude_pairs = []
426354

427355
for raw_id in exclude_ids:
428-
if '.' in raw_id:
429-
label, ex_id = raw_id.split('.', 1)
430-
else:
431-
label = 'main'
432-
ex_id = raw_id
356+
label = raw_id.label
357+
ex_id = raw_id.test_num
433358

434359
ex_wd = pav_cfg['configs'].get(label, None)
435360
if ex_wd is None:
436361
# Invalid label.
437362
continue
438363

439364
ex_wd = Path(ex_wd)
440-
441-
try:
442-
ex_id = int(ex_id)
443-
except ValueError:
444-
continue
445-
446365
exclude_pairs.append((ex_wd, ex_id))
447366

448367
return [pair for pair in id_pairs if pair not in exclude_pairs]
@@ -483,8 +402,8 @@ def get_tests_by_paths(pav_cfg, test_paths: List[Path], errfile: TextIO,
483402
return load_tests(pav_cfg, test_pairs, errfile)
484403

485404

486-
def get_tests_by_id(pav_cfg, test_ids: List['str'], errfile: TextIO,
487-
exclude_ids: List[str] = None) -> List[TestRun]:
405+
def get_tests_by_id(pav_cfg, test_ids: List[Union[TestID, SeriesID]], errfile: TextIO,
406+
exclude_ids: List[TestID] = None) -> List[TestRun]:
488407
"""Convert a list of raw test id's and series id's into a list of
489408
test objects.
490409
@@ -495,23 +414,16 @@ def get_tests_by_id(pav_cfg, test_ids: List['str'], errfile: TextIO,
495414
:return: List of test objects
496415
"""
497416

498-
test_ids = [str(test) for test in test_ids.copy()]
499-
500-
if not test_ids:
501-
# Get the last series ran by this user
502-
series_id = series.load_user_series_id(pav_cfg)
503-
if series_id is not None:
504-
test_ids.append(series_id)
505-
else:
506-
raise CommandError("No tests specified and no last series was found.")
507-
508417
# Convert series and test ids into test paths.
509418
test_id_pairs = []
510419
for raw_id in test_ids:
511420
# Series start with 's' (like 'snake') and never have labels
512-
if '.' not in raw_id and raw_id.startswith('s'):
421+
if isinstance(raw_id, SeriesID):
513422
try:
514-
series_obj = series.TestSeries.load(pav_cfg, raw_id)
423+
if raw_id.last():
424+
series_obj = load_last_series(pav_cfg, errfile)
425+
else:
426+
series_obj = series.TestSeries.load(pav_cfg, raw_id.id_str)
515427
except TestSeriesError as err:
516428
output.fprint(errfile, "Suite {} could not be found.\n{}"
517429
.format(raw_id, err), color=output.RED)
@@ -521,7 +433,7 @@ def get_tests_by_id(pav_cfg, test_ids: List['str'], errfile: TextIO,
521433
# Just a plain test id.
522434
else:
523435
try:
524-
test_id_pairs.append(TestRun.parse_raw_id(pav_cfg, raw_id))
436+
test_id_pairs.append(TestRun.parse_raw_id(pav_cfg, raw_id.id_str))
525437

526438
except TestRunError as err:
527439
output.fprint(sys.stdout, "Error loading test '{}': {}"
@@ -600,3 +512,29 @@ def get_glob(test_suite_name, test_names):
600512

601513
testset_name = ','.join(globs).rstrip(',')
602514
return testset_name
515+
516+
517+
def get_last_test_id(pav_cfg: "PavConfig", errfile: TextIO) -> Optional[TestID]:
518+
"""Get the ID of the last run test, if it exists, and if there is a single
519+
unambigous last test. If there is not, return None."""
520+
521+
last_series = load_last_series(pav_cfg, errfile)
522+
523+
if last_series is None:
524+
return None
525+
526+
test_ids = list(last_series.tests.keys())
527+
528+
if len(test_ids) == 0:
529+
output.fprint(
530+
errfile,
531+
f"Most recent series contains no tests.")
532+
return None
533+
534+
if len(test_ids) > 1:
535+
output.fprint(
536+
errfile,
537+
f"Multiple tests exist in last series. Could not unambiguously identify last test.")
538+
return None
539+
540+
return TestID(test_ids[0])

lib/pavilion/commands/cancel.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from pavilion.errors import TestSeriesError
1313
from pavilion.test_run import TestRun
1414
from pavilion.config import PavConfig
15+
from pavilion.test_ids import resolve_mixed_ids, SeriesID
1516
from pavilion.micro import partition
1617
from .base_classes import Command
1718
from ..errors import TestRunError
@@ -45,15 +46,10 @@ def _setup_arguments(self, parser):
4546
def run(self, pav_cfg: PavConfig, args: Namespace) -> int:
4647
"""Cancel the given tests or series."""
4748

48-
if len(args.tests) == 0:
49-
# Get the last series ran by this user.
50-
series_id = series.load_user_series_id(pav_cfg)
51-
52-
if series_id is not None:
53-
args.tests.append(series_id)
49+
ids = resolve_mixed_ids(args.tests, auto_last=True)
5450

5551
# Separate out into tests and series
56-
series_ids, test_ids = partition(cmd_utils.is_series_id, args.tests)
52+
series_ids, test_ids = partition(lambda x: isinstance(x, SeriesID), ids)
5753

5854
args.tests = list(test_ids)
5955
args.series = list(series_ids)

lib/pavilion/commands/cat.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from pavilion import dir_db
88
from pavilion import output
99
from pavilion import cmd_utils
10+
from pavilion.test_ids import TestID
1011
from .base_classes import Command
1112

1213

@@ -23,6 +24,7 @@ def __init__(self):
2324
def _setup_arguments(self, parser):
2425
parser.add_argument(
2526
'test_id', help="test id",
27+
nargs='?', default=None,
2628
metavar='TEST_ID'
2729
)
2830
parser.add_argument(
@@ -35,9 +37,21 @@ def _setup_arguments(self, parser):
3537
def run(self, pav_cfg, args):
3638
"""Run this command."""
3739

38-
tests = cmd_utils.get_tests_by_id(pav_cfg, [args.test_id], self.errfile)
40+
if args.test_id is None:
41+
test_id = cmd_utils.get_last_test_id(pav_cfg, self.errfile)
42+
43+
if test_id is None:
44+
output.fprint(self.errfile, "No last test found.", color=output.RED)
45+
return 1
46+
elif TestID.is_valid_id(args.test_id):
47+
test_id = TestID(args.test_id)
48+
else:
49+
output.fprint(self.errfile, f"{args.test_id} is not a valid test ID.")
50+
return errno.EEXIST
51+
52+
tests = cmd_utils.get_tests_by_id(pav_cfg, [test_id], self.errfile)
3953
if not tests:
40-
output.fprint(self.errfile, "Could not find test '{}'".format(args.test_id))
54+
output.fprint(self.errfile, "Could not find test '{}'".format(test_id))
4155
return errno.EEXIST
4256
elif len(tests) > 1:
4357
output.fprint(

0 commit comments

Comments
 (0)