From fbafce07d24e0e1cddcb33021567f016e7c1f44c Mon Sep 17 00:00:00 2001 From: Hank Wikle Date: Wed, 3 Apr 2024 12:52:32 -0600 Subject: [PATCH 01/14] add CLI flag to show tracebacks, with corresponding pformat argument --- lib/pavilion/arguments.py | 4 ++++ lib/pavilion/clean.py | 4 ++-- lib/pavilion/cmd_utils.py | 4 ++-- lib/pavilion/commands/_series.py | 2 +- lib/pavilion/commands/group.py | 24 ++++++++++++------------ lib/pavilion/commands/result.py | 4 ++-- lib/pavilion/commands/run.py | 2 +- lib/pavilion/commands/series.py | 8 ++++---- lib/pavilion/commands/show.py | 2 +- lib/pavilion/errors.py | 6 +++++- lib/pavilion/main.py | 15 +++++++++++++-- lib/pavilion/series/series.py | 7 ++++--- lib/pavilion/series/test_set.py | 15 ++++++++------- 13 files changed, 59 insertions(+), 38 deletions(-) diff --git a/lib/pavilion/arguments.py b/lib/pavilion/arguments.py index 40890ebc5..072815324 100644 --- a/lib/pavilion/arguments.py +++ b/lib/pavilion/arguments.py @@ -79,6 +79,10 @@ def get_parser(): '--profile-count', default=PROFILE_COUNT_DEFAULT, action='store', type=int, help="Number of rows in the profile table.") + parser.add_argument( + '--show-tracebacks', dest='show_tracebacks', action='store_true', + help="Display full traceback when printing error messages.") + _PAV_PARSER = parser _PAV_SUB_PARSER = parser.add_subparsers(dest='command_name') diff --git a/lib/pavilion/clean.py b/lib/pavilion/clean.py index a23bd46d2..2c7d2ef0c 100644 --- a/lib/pavilion/clean.py +++ b/lib/pavilion/clean.py @@ -74,7 +74,7 @@ def delete_unused_builds(pav_cfg, builds_dir: Path, tests_dir: Path, verbose: bo return count, msgs -def clean_groups(pav_cfg) -> Tuple[int, List[str]]: +def clean_groups(pav_cfg: config.PavConfig, show_tracebacks: bool = False) -> Tuple[int, List[str]]: """Remove members that no longer exist from groups, and delete empty groups. Returns the number of groups deleted and a list of error messages.""" @@ -88,7 +88,7 @@ def clean_groups(pav_cfg) -> Tuple[int, List[str]]: for group_path in groups_dir.iterdir(): group = groups.TestGroup(pav_cfg, group_path.name) for error in group.clean(): - msgs.append(error.pformat()) + msgs.append(error.pformat(show_tracebacks)) if not group.exists(): deleted += 1 diff --git a/lib/pavilion/cmd_utils.py b/lib/pavilion/cmd_utils.py index 7376446f2..e162d495a 100644 --- a/lib/pavilion/cmd_utils.py +++ b/lib/pavilion/cmd_utils.py @@ -321,7 +321,7 @@ def get_collection_path(pav_cfg, collection) -> Union[Path, None]: return None -def test_list_to_paths(pav_cfg, req_tests, errfile=None) -> List[Path]: +def test_list_to_paths(pav_cfg: config.PavConfig, req_tests: List, errfile: Optional[Path] = None, show_tracebacks: bool = False) -> List[Path]: """Given a list of raw test id's and series id's, return a list of paths to those tests. The keyword 'last' may also be given to get the last series run by @@ -379,7 +379,7 @@ def test_list_to_paths(pav_cfg, req_tests, errfile=None) -> List[Path]: output.fprint( errfile, "Invalid test group id '{}'.\n{}" - .format(raw_id, err.pformat())) + .format(raw_id, err.pformat(show_tracebacks))) continue if not group.exists(): diff --git a/lib/pavilion/commands/_series.py b/lib/pavilion/commands/_series.py index 1dc6c6981..622128130 100644 --- a/lib/pavilion/commands/_series.py +++ b/lib/pavilion/commands/_series.py @@ -43,6 +43,6 @@ def run(self, pav_cfg, args): series_obj.run() except TestSeriesError as err: output.fprint(self.errfile, "Error while running series '{}'.".format(args.series_id)) - output.fprint(self.errfile, err.pformat()) + output.fprint(self.errfile, err.pformat(args.show_tracebacks)) return 0 diff --git a/lib/pavilion/commands/group.py b/lib/pavilion/commands/group.py index dc4b04934..29e55fa7d 100644 --- a/lib/pavilion/commands/group.py +++ b/lib/pavilion/commands/group.py @@ -122,14 +122,14 @@ def run(self, pav_cfg, args): return self._run_sub_command(pav_cfg, args) - def _get_group(self, pav_cfg, group_name: str) -> TestGroup: + def _get_group(self, pav_cfg: config.PavConfig, group_name: str, show_tracebacks: bool = False) -> TestGroup: """Get the requested group, and print a standard error message on failure.""" try: group = TestGroup(pav_cfg, group_name) except TestGroupError as err: fprint(self.errfile, "Error loading group '{}'", color=output.RED) - fprint(self.errfile, err.pformat()) + fprint(self.errfile, err.pformat(show_tracebacks)) return None if not group.exists(): @@ -151,7 +151,7 @@ def _add_cmd(self, pav_cfg, args): group.create() except TestGroupError as err: fprint(self.errfile, "Error adding tests.", color=output.RED) - fprint(self.errfile, err.pformat()) + fprint(self.errfile, err.pformat(args.show_tracebacks)) return 1 added, errors = group.add(args.items) @@ -159,7 +159,7 @@ def _add_cmd(self, pav_cfg, args): fprint(self.errfile, "There were one or more errors when adding tests.", color=output.RED) for error in errors: - fprint(self.errfile, error.pformat(), '\n') + fprint(self.errfile, error.pformat(args.show_tracebacks), '\n') existed = len(args.items) - len(added) - len(errors) fprint(self.outfile, @@ -175,7 +175,7 @@ def _add_cmd(self, pav_cfg, args): def _remove_cmd(self, pav_cfg, args): """Remove the given tests/series/groups""" - group = self._get_group(pav_cfg, args.group) + group = self._get_group(pav_cfg, args.group, args.show_tracebacks) if group is None: return 1 @@ -184,7 +184,7 @@ def _remove_cmd(self, pav_cfg, args): fprint(self.errfile, "There were one or more errors when removing tests.", color=output.RED) for error in errors: - output.fprint(self.errfile, error.pformat(), '\n') + output.fprint(self.errfile, error.pformat(args.show_tracebacks), '\n') fprint(self.outfile, "Removed {} item{}." @@ -196,7 +196,7 @@ def _remove_cmd(self, pav_cfg, args): def _delete_cmd(self, pav_cfg, args): """Delete the group entirely.""" - group = self._get_group(pav_cfg, args.group) + group = self._get_group(pav_cfg, args.group, args.show_tracebacks) if group is None: return 1 @@ -222,7 +222,7 @@ def _delete_cmd(self, pav_cfg, args): fprint(self.errfile, "Could not remove group '{}'" .format(group.display_name), color=output.RED) - fprint(self.errfile, err.pformat()) + fprint(self.errfile, err.pformat(args.show_tracebacks)) return 1 return 0 @@ -261,7 +261,7 @@ def _list_cmd(self, pav_cfg, args): def _members_cmd(self, pav_cfg, args): """List the members of a group.""" - group = self._get_group(pav_cfg, args.group) + group = self._get_group(pav_cfg, args.group, args.show_tracebacks) if group is None: return 1 @@ -276,7 +276,7 @@ def _members_cmd(self, pav_cfg, args): members = group.members(recursive=args.recursive) except TestGroupError as err: fprint(self.errfile, "Could not get members.", color=output.RED) - fprint(self.errfile, err.pformat()) + fprint(self.errfile, err.pformat(args.show_tracebacks)) return 1 filtered_members = [] @@ -309,7 +309,7 @@ def _members_cmd(self, pav_cfg, args): def _rename_cmd(self, pav_cfg, args): """Give a test group a new name.""" - group = self._get_group(pav_cfg, args.group) + group = self._get_group(pav_cfg, args.group, args.show_tracebacks) if group is None: return 1 @@ -317,6 +317,6 @@ def _rename_cmd(self, pav_cfg, args): group.rename(args.new_name, redirect_parents=not args.no_redirect) except TestGroupError as err: fprint(self.errfile, "Error renaming group.", color=output.RED) - fprint(self.errfile, err.pformat()) + fprint(self.errfile, err.pformat(args.show_tracebacks)) return 0 diff --git a/lib/pavilion/commands/result.py b/lib/pavilion/commands/result.py index 44e28e347..fa5accbb4 100644 --- a/lib/pavilion/commands/result.py +++ b/lib/pavilion/commands/result.py @@ -336,7 +336,7 @@ def key_fields(self, args): return fields def update_results(self, pav_cfg: dict, tests: List[TestRun], - log_file: IO[str], save: bool = False) -> bool: + log_file: IO[str], save: bool = False, show_tracebacks: bool = False) -> bool: """Update each of the given tests with the result section from the current version of their configs. Then rerun result processing and update the results in the test object (but change nothing on disk). @@ -364,7 +364,7 @@ def update_results(self, pav_cfg: dict, tests: List[TestRun], except TestConfigError as err: output.fprint(self.errfile, "Test '{}' could not be reloaded." .format(test.name), color=output.RED) - output.fprint(self.errfile, err.pformat()) + output.fprint(self.errfile, err.pformat(show_tracebacks)) return False ptest = ptests[0] diff --git a/lib/pavilion/commands/run.py b/lib/pavilion/commands/run.py index 10afa8714..f1cb94b80 100644 --- a/lib/pavilion/commands/run.py +++ b/lib/pavilion/commands/run.py @@ -175,7 +175,7 @@ def run(self, pav_cfg, args): output.fprint(self.errfile, "Could not add series to group '{}'".format(args.group), color=output.RED) - output.fprint(self.errfile, err.pformat()) + output.fprint(self.errfile, err.pformat(args.show_tracebacks)) return errno.EINVAL else: diff --git a/lib/pavilion/commands/series.py b/lib/pavilion/commands/series.py index 64e1bba5d..5577838f0 100644 --- a/lib/pavilion/commands/series.py +++ b/lib/pavilion/commands/series.py @@ -156,7 +156,7 @@ def _setup_arguments(self, parser): state_p.add_argument('series', default='last', nargs='?', help="The series to print status history for.") - def _find_series(self, pav_cfg, series_name): + def _find_series(self, pav_cfg: config.PavConfig, series_name: int, show_tracebacks: bool = False): """Grab the series based on the series name, if one was given.""" if series_name == 'last': @@ -168,7 +168,7 @@ def _find_series(self, pav_cfg, series_name): output.fprint(self.errfile, "Could not load given series '{}'" .format(series_name)) - output.fprint(self.errfile, err.pformat()) + output.fprint(self.errfile, err.pformat(show_tracebacks)) return None return ser @@ -195,7 +195,7 @@ def _run_cmd(self, pav_cfg, args): modes=args.modes, overrides=args.overrides) except series_config.SeriesConfigError as err: - output.fprint(self.errfile, err.pformat(), color=output.RED) + output.fprint(self.errfile, err.pformat(args.show_tracebacks), color=output.RED) return errno.EINVAL if args.re_name is not None: @@ -301,7 +301,7 @@ def _list_cmd(self, pav_cfg, args): def _sets_cmd(self, pav_cfg, args): """Display a series by test set.""" - ser = self._find_series(pav_cfg, args.series) + ser = self._find_series(pav_cfg, args.series, args.show_tracebacks) if ser is None: return errno.EINVAL diff --git a/lib/pavilion/commands/show.py b/lib/pavilion/commands/show.py index d228ec46e..11e04747c 100644 --- a/lib/pavilion/commands/show.py +++ b/lib/pavilion/commands/show.py @@ -701,7 +701,7 @@ def _nodes_cmd(self, pav_cfg, args): except errors.TestConfigError as err: output.fprint(self.errfile, "Could not load test {}\n{}" - .format(args.test, err.pformat())) + .format(args.test, err.pformat(args.show_tracebacks))) return errno.EINVAL test = None diff --git a/lib/pavilion/errors.py b/lib/pavilion/errors.py index 77442a9c2..d49200bba 100644 --- a/lib/pavilion/errors.py +++ b/lib/pavilion/errors.py @@ -5,6 +5,7 @@ import pprint import textwrap import shutil +import traceback import lark @@ -46,9 +47,12 @@ def __str__(self): else: return self.msg - def pformat(self) -> str: + def pformat(self, show_traceback: bool = False) -> str: """Specially format the exception for printing.""" + if show_traceback: + return traceback.format_exception(self) + lines = [] next_exc = self.prior_error width = shutil.get_terminal_size((80, 80)).columns diff --git a/lib/pavilion/main.py b/lib/pavilion/main.py index adb846899..349214cab 100644 --- a/lib/pavilion/main.py +++ b/lib/pavilion/main.py @@ -47,11 +47,18 @@ def main(): # This has to be done before we initialize plugins parser = arguments.get_parser() + # Just need the show_tracebacks flag here + partial_args, _ = parser.parse_known_args() + # Get the Pavilion config try: pav_cfg = config.find_pavilion_config() except Exception as err: - output.fprint(sys.stderr, "Error getting config, exiting.", err, color=output.RED) + if not partial_args.show_tracebacks: + output.fprint(sys.stderr, "Error getting config, exiting.", err, color=output.RED) + else: + print(traceback.format_exc()) + sys.exit(-1) # Setup all the loggers for Pavilion @@ -61,7 +68,11 @@ def main(): try: plugins.initialize_plugins(pav_cfg) except pavilion.errors.PluginError as err: - output.fprint(sys.stderr, "Error initializing plugins.", err, color=output.RED) + if not partial_args.show_tracebacks: + output.fprint(sys.stderr, "Error initializing plugins.", err, color=output.RED) + else: + print(traceback.format_exc()) + sys.exit(-1) # Partially parse the arguments. All we really care about is the subcommand. diff --git a/lib/pavilion/series/series.py b/lib/pavilion/series/series.py index dd62e5af6..92c7a4185 100644 --- a/lib/pavilion/series/series.py +++ b/lib/pavilion/series/series.py @@ -506,7 +506,8 @@ def run(self, build_only: bool = False, rebuild: bool = False, # Completion will be set when looked for. - def _run_set(self, test_set: TestSet, build_only: bool, rebuild: bool, local_builds_only: bool): + def _run_set(self, test_set: TestSet, build_only: bool, rebuild: bool, + local_builds_only: bool, show_tracebacks: bool = False): """Run all requested tests in the given test set.""" # Track which builds we've already marked as deprecated, when doing rebuilds. @@ -514,7 +515,7 @@ def _run_set(self, test_set: TestSet, build_only: bool, rebuild: bool, local_bui failed_builds = dict() tests_running = 0 - for test_batch in test_set.make_iter(build_only, rebuild, local_builds_only): + for test_batch in test_set.make_iter(build_only, rebuild, local_builds_only, show_tracebacks): # Add all the tests we created to this test set. self._add_tests(test_batch, test_set.iter_name) @@ -542,7 +543,7 @@ def _run_set(self, test_set: TestSet, build_only: bool, rebuild: bool, local_bui return try: - started_tests, new_jobs = test_set.kickoff() + started_tests, new_jobs = test_set.kickoff(show_tracebacks) tests_running += len(started_tests) except TestSetError as err: self.status.set(SERIES_STATES.KICKOFF_ERROR, diff --git a/lib/pavilion/series/test_set.py b/lib/pavilion/series/test_set.py index 533318de3..7bff049be 100644 --- a/lib/pavilion/series/test_set.py +++ b/lib/pavilion/series/test_set.py @@ -189,8 +189,8 @@ def __ordered_split(self) -> List['TestSet']: return test_sets - def make_iter(self, build_only=False, rebuild=False, local_builds_only=False) \ - -> Iterator[List[TestRun]]: + def make_iter(self, build_only: bool = False, rebuild: bool = False, + local_builds_only: bool = False, show_tracebacks: bool = False) -> Iterator[List[TestRun]]: """Resolve the given tests names and options into actual test run objects, and print the test creation status. This returns an iterator over batches tests, respecting the batch_size (half the simultanious limit). @@ -237,15 +237,16 @@ def make_iter(self, build_only=False, rebuild=False, local_builds_only=False) \ for error in cfg_resolver.errors: if error.request is not None: self.status.set(S_STATES.ERROR, - '{} - {}'.format(error.request.request, error.pformat())) + '{} - {}'.format(error.request.request, error.pformat(show_tracebacks))) + output.fprint( self.outfile, - "{} - {}".format(error.request.request, error.pformat())) + "{} - {}".format(error.request.request, error.pformat(show_tracebacks))) else: self.status.set(S_STATES.ERROR, error.pformat()) output.fprint( self.outfile, - "{}".format(error.pformat())) + "{}".format(error.pformat(show_tracebacks))) if not self.ignore_errors: raise TestSetError("Error creating tests for test set {}.".format(self.name), @@ -555,7 +556,7 @@ def build(self, deprecated_builds: Union[Set[str], None] = None, .format(len(built_tests), self.name)) - def kickoff(self) -> Tuple[List[TestRun], List[Job]]: + def kickoff(self, show_tracebacks: bool = False) -> Tuple[List[TestRun], List[Job]]: """Kickoff all the given tests under this test set. :return: The number of jobs kicked off. @@ -611,7 +612,7 @@ def kickoff(self) -> Tuple[List[TestRun], List[Job]]: output.fprint(self.outfile, "Errors:") for err in sched_errors: - output.fprint(self.outfile, err.pformat(), '\n') + output.fprint(self.outfile, err.pformat(show_tracebacks), '\n') jobs = dict() for test in new_started: From 78a3ee5c37ea2ae03bc8d94d57ca831f1bcef86e Mon Sep 17 00:00:00 2001 From: Hank Wikle Date: Wed, 10 Apr 2024 09:55:11 -0600 Subject: [PATCH 02/14] begin implementing PR feedback --- lib/pavilion/clean.py | 4 ++-- lib/pavilion/cmd_utils.py | 4 ++-- lib/pavilion/commands/_series.py | 2 +- lib/pavilion/commands/group.py | 27 ++++++++++++++------------- lib/pavilion/commands/result.py | 4 ++-- lib/pavilion/commands/run.py | 2 +- lib/pavilion/commands/series.py | 8 ++++---- lib/pavilion/commands/show.py | 2 +- lib/pavilion/errors.py | 5 +---- lib/pavilion/series/series.py | 6 +++--- lib/pavilion/series/test_set.py | 6 +++--- 11 files changed, 34 insertions(+), 36 deletions(-) diff --git a/lib/pavilion/clean.py b/lib/pavilion/clean.py index 2c7d2ef0c..f58406ebc 100644 --- a/lib/pavilion/clean.py +++ b/lib/pavilion/clean.py @@ -74,7 +74,7 @@ def delete_unused_builds(pav_cfg, builds_dir: Path, tests_dir: Path, verbose: bo return count, msgs -def clean_groups(pav_cfg: config.PavConfig, show_tracebacks: bool = False) -> Tuple[int, List[str]]: +def clean_groups(pav_cfg: config.PavConfig) -> Tuple[int, List[str]]: """Remove members that no longer exist from groups, and delete empty groups. Returns the number of groups deleted and a list of error messages.""" @@ -88,7 +88,7 @@ def clean_groups(pav_cfg: config.PavConfig, show_tracebacks: bool = False) -> Tu for group_path in groups_dir.iterdir(): group = groups.TestGroup(pav_cfg, group_path.name) for error in group.clean(): - msgs.append(error.pformat(show_tracebacks)) + msgs.append(error.pformat()) if not group.exists(): deleted += 1 diff --git a/lib/pavilion/cmd_utils.py b/lib/pavilion/cmd_utils.py index e162d495a..60cc227ce 100644 --- a/lib/pavilion/cmd_utils.py +++ b/lib/pavilion/cmd_utils.py @@ -321,7 +321,7 @@ def get_collection_path(pav_cfg, collection) -> Union[Path, None]: return None -def test_list_to_paths(pav_cfg: config.PavConfig, req_tests: List, errfile: Optional[Path] = None, show_tracebacks: bool = False) -> List[Path]: +def test_list_to_paths(pav_cfg: config.PavConfig, req_tests: List, errfile: Optional[Path] = None) -> List[Path]: """Given a list of raw test id's and series id's, return a list of paths to those tests. The keyword 'last' may also be given to get the last series run by @@ -379,7 +379,7 @@ def test_list_to_paths(pav_cfg: config.PavConfig, req_tests: List, errfile: Opti output.fprint( errfile, "Invalid test group id '{}'.\n{}" - .format(raw_id, err.pformat(show_tracebacks))) + .format(raw_id, err.pformat())) continue if not group.exists(): diff --git a/lib/pavilion/commands/_series.py b/lib/pavilion/commands/_series.py index 622128130..1dc6c6981 100644 --- a/lib/pavilion/commands/_series.py +++ b/lib/pavilion/commands/_series.py @@ -43,6 +43,6 @@ def run(self, pav_cfg, args): series_obj.run() except TestSeriesError as err: output.fprint(self.errfile, "Error while running series '{}'.".format(args.series_id)) - output.fprint(self.errfile, err.pformat(args.show_tracebacks)) + output.fprint(self.errfile, err.pformat()) return 0 diff --git a/lib/pavilion/commands/group.py b/lib/pavilion/commands/group.py index 29e55fa7d..88bf20146 100644 --- a/lib/pavilion/commands/group.py +++ b/lib/pavilion/commands/group.py @@ -122,15 +122,16 @@ def run(self, pav_cfg, args): return self._run_sub_command(pav_cfg, args) - def _get_group(self, pav_cfg: config.PavConfig, group_name: str, show_tracebacks: bool = False) -> TestGroup: + def _get_group(self, pav_cfg: config.PavConfig, group_name: str) -> TestGroup: """Get the requested group, and print a standard error message on failure.""" try: group = TestGroup(pav_cfg, group_name) except TestGroupError as err: fprint(self.errfile, "Error loading group '{}'", color=output.RED) - fprint(self.errfile, err.pformat(show_tracebacks)) - return None + fprint(self.errfile, err.pformat()) + + raise err if not group.exists(): fprint(self.errfile, @@ -151,7 +152,7 @@ def _add_cmd(self, pav_cfg, args): group.create() except TestGroupError as err: fprint(self.errfile, "Error adding tests.", color=output.RED) - fprint(self.errfile, err.pformat(args.show_tracebacks)) + fprint(self.errfile, err.pformat()) return 1 added, errors = group.add(args.items) @@ -159,7 +160,7 @@ def _add_cmd(self, pav_cfg, args): fprint(self.errfile, "There were one or more errors when adding tests.", color=output.RED) for error in errors: - fprint(self.errfile, error.pformat(args.show_tracebacks), '\n') + fprint(self.errfile, error.pformat(), '\n') existed = len(args.items) - len(added) - len(errors) fprint(self.outfile, @@ -175,7 +176,7 @@ def _add_cmd(self, pav_cfg, args): def _remove_cmd(self, pav_cfg, args): """Remove the given tests/series/groups""" - group = self._get_group(pav_cfg, args.group, args.show_tracebacks) + group = self._get_group(pav_cfg, args.group) if group is None: return 1 @@ -184,7 +185,7 @@ def _remove_cmd(self, pav_cfg, args): fprint(self.errfile, "There were one or more errors when removing tests.", color=output.RED) for error in errors: - output.fprint(self.errfile, error.pformat(args.show_tracebacks), '\n') + output.fprint(self.errfile, error.pformat(), '\n') fprint(self.outfile, "Removed {} item{}." @@ -196,7 +197,7 @@ def _remove_cmd(self, pav_cfg, args): def _delete_cmd(self, pav_cfg, args): """Delete the group entirely.""" - group = self._get_group(pav_cfg, args.group, args.show_tracebacks) + group = self._get_group(pav_cfg, args.group) if group is None: return 1 @@ -222,7 +223,7 @@ def _delete_cmd(self, pav_cfg, args): fprint(self.errfile, "Could not remove group '{}'" .format(group.display_name), color=output.RED) - fprint(self.errfile, err.pformat(args.show_tracebacks)) + fprint(self.errfile, err.pformat()) return 1 return 0 @@ -261,7 +262,7 @@ def _list_cmd(self, pav_cfg, args): def _members_cmd(self, pav_cfg, args): """List the members of a group.""" - group = self._get_group(pav_cfg, args.group, args.show_tracebacks) + group = self._get_group(pav_cfg, args.group) if group is None: return 1 @@ -276,7 +277,7 @@ def _members_cmd(self, pav_cfg, args): members = group.members(recursive=args.recursive) except TestGroupError as err: fprint(self.errfile, "Could not get members.", color=output.RED) - fprint(self.errfile, err.pformat(args.show_tracebacks)) + fprint(self.errfile, err.pformat()) return 1 filtered_members = [] @@ -309,7 +310,7 @@ def _members_cmd(self, pav_cfg, args): def _rename_cmd(self, pav_cfg, args): """Give a test group a new name.""" - group = self._get_group(pav_cfg, args.group, args.show_tracebacks) + group = self._get_group(pav_cfg, args.group) if group is None: return 1 @@ -317,6 +318,6 @@ def _rename_cmd(self, pav_cfg, args): group.rename(args.new_name, redirect_parents=not args.no_redirect) except TestGroupError as err: fprint(self.errfile, "Error renaming group.", color=output.RED) - fprint(self.errfile, err.pformat(args.show_tracebacks)) + fprint(self.errfile, err.pformat()) return 0 diff --git a/lib/pavilion/commands/result.py b/lib/pavilion/commands/result.py index fa5accbb4..44e28e347 100644 --- a/lib/pavilion/commands/result.py +++ b/lib/pavilion/commands/result.py @@ -336,7 +336,7 @@ def key_fields(self, args): return fields def update_results(self, pav_cfg: dict, tests: List[TestRun], - log_file: IO[str], save: bool = False, show_tracebacks: bool = False) -> bool: + log_file: IO[str], save: bool = False) -> bool: """Update each of the given tests with the result section from the current version of their configs. Then rerun result processing and update the results in the test object (but change nothing on disk). @@ -364,7 +364,7 @@ def update_results(self, pav_cfg: dict, tests: List[TestRun], except TestConfigError as err: output.fprint(self.errfile, "Test '{}' could not be reloaded." .format(test.name), color=output.RED) - output.fprint(self.errfile, err.pformat(show_tracebacks)) + output.fprint(self.errfile, err.pformat()) return False ptest = ptests[0] diff --git a/lib/pavilion/commands/run.py b/lib/pavilion/commands/run.py index f1cb94b80..10afa8714 100644 --- a/lib/pavilion/commands/run.py +++ b/lib/pavilion/commands/run.py @@ -175,7 +175,7 @@ def run(self, pav_cfg, args): output.fprint(self.errfile, "Could not add series to group '{}'".format(args.group), color=output.RED) - output.fprint(self.errfile, err.pformat(args.show_tracebacks)) + output.fprint(self.errfile, err.pformat()) return errno.EINVAL else: diff --git a/lib/pavilion/commands/series.py b/lib/pavilion/commands/series.py index 5577838f0..c7b4af45c 100644 --- a/lib/pavilion/commands/series.py +++ b/lib/pavilion/commands/series.py @@ -156,7 +156,7 @@ def _setup_arguments(self, parser): state_p.add_argument('series', default='last', nargs='?', help="The series to print status history for.") - def _find_series(self, pav_cfg: config.PavConfig, series_name: int, show_tracebacks: bool = False): + def _find_series(self, pav_cfg: config.PavConfig, series_name: int): """Grab the series based on the series name, if one was given.""" if series_name == 'last': @@ -168,7 +168,7 @@ def _find_series(self, pav_cfg: config.PavConfig, series_name: int, show_traceba output.fprint(self.errfile, "Could not load given series '{}'" .format(series_name)) - output.fprint(self.errfile, err.pformat(show_tracebacks)) + output.fprint(self.errfile, err.pformat()) return None return ser @@ -195,7 +195,7 @@ def _run_cmd(self, pav_cfg, args): modes=args.modes, overrides=args.overrides) except series_config.SeriesConfigError as err: - output.fprint(self.errfile, err.pformat(args.show_tracebacks), color=output.RED) + output.fprint(self.errfile, err.pformat(), color=output.RED) return errno.EINVAL if args.re_name is not None: @@ -301,7 +301,7 @@ def _list_cmd(self, pav_cfg, args): def _sets_cmd(self, pav_cfg, args): """Display a series by test set.""" - ser = self._find_series(pav_cfg, args.series, args.show_tracebacks) + ser = self._find_series(pav_cfg, args.series) if ser is None: return errno.EINVAL diff --git a/lib/pavilion/commands/show.py b/lib/pavilion/commands/show.py index 11e04747c..d228ec46e 100644 --- a/lib/pavilion/commands/show.py +++ b/lib/pavilion/commands/show.py @@ -701,7 +701,7 @@ def _nodes_cmd(self, pav_cfg, args): except errors.TestConfigError as err: output.fprint(self.errfile, "Could not load test {}\n{}" - .format(args.test, err.pformat(args.show_tracebacks))) + .format(args.test, err.pformat())) return errno.EINVAL test = None diff --git a/lib/pavilion/errors.py b/lib/pavilion/errors.py index d49200bba..c7281947f 100644 --- a/lib/pavilion/errors.py +++ b/lib/pavilion/errors.py @@ -47,12 +47,9 @@ def __str__(self): else: return self.msg - def pformat(self, show_traceback: bool = False) -> str: + def pformat(self) -> str: """Specially format the exception for printing.""" - if show_traceback: - return traceback.format_exception(self) - lines = [] next_exc = self.prior_error width = shutil.get_terminal_size((80, 80)).columns diff --git a/lib/pavilion/series/series.py b/lib/pavilion/series/series.py index 92c7a4185..0266e1a35 100644 --- a/lib/pavilion/series/series.py +++ b/lib/pavilion/series/series.py @@ -507,7 +507,7 @@ def run(self, build_only: bool = False, rebuild: bool = False, def _run_set(self, test_set: TestSet, build_only: bool, rebuild: bool, - local_builds_only: bool, show_tracebacks: bool = False): + local_builds_only: bool): """Run all requested tests in the given test set.""" # Track which builds we've already marked as deprecated, when doing rebuilds. @@ -515,7 +515,7 @@ def _run_set(self, test_set: TestSet, build_only: bool, rebuild: bool, failed_builds = dict() tests_running = 0 - for test_batch in test_set.make_iter(build_only, rebuild, local_builds_only, show_tracebacks): + for test_batch in test_set.make_iter(build_only, rebuild, local_builds_only): # Add all the tests we created to this test set. self._add_tests(test_batch, test_set.iter_name) @@ -543,7 +543,7 @@ def _run_set(self, test_set: TestSet, build_only: bool, rebuild: bool, return try: - started_tests, new_jobs = test_set.kickoff(show_tracebacks) + started_tests, new_jobs = test_set.kickoff() tests_running += len(started_tests) except TestSetError as err: self.status.set(SERIES_STATES.KICKOFF_ERROR, diff --git a/lib/pavilion/series/test_set.py b/lib/pavilion/series/test_set.py index 7bff049be..0d0e3792d 100644 --- a/lib/pavilion/series/test_set.py +++ b/lib/pavilion/series/test_set.py @@ -190,7 +190,7 @@ def __ordered_split(self) -> List['TestSet']: return test_sets def make_iter(self, build_only: bool = False, rebuild: bool = False, - local_builds_only: bool = False, show_tracebacks: bool = False) -> Iterator[List[TestRun]]: + local_builds_only: bool = False) -> Iterator[List[TestRun]]: """Resolve the given tests names and options into actual test run objects, and print the test creation status. This returns an iterator over batches tests, respecting the batch_size (half the simultanious limit). @@ -556,7 +556,7 @@ def build(self, deprecated_builds: Union[Set[str], None] = None, .format(len(built_tests), self.name)) - def kickoff(self, show_tracebacks: bool = False) -> Tuple[List[TestRun], List[Job]]: + def kickoff(self) -> Tuple[List[TestRun], List[Job]]: """Kickoff all the given tests under this test set. :return: The number of jobs kicked off. @@ -612,7 +612,7 @@ def kickoff(self, show_tracebacks: bool = False) -> Tuple[List[TestRun], List[Jo output.fprint(self.outfile, "Errors:") for err in sched_errors: - output.fprint(self.outfile, err.pformat(show_tracebacks), '\n') + output.fprint(self.outfile, err.pformat(), '\n') jobs = dict() for test in new_started: From ff0378ec5ff073d76aae9f5658358ab72ad66494 Mon Sep 17 00:00:00 2001 From: Hank Wikle Date: Wed, 10 Apr 2024 09:56:33 -0600 Subject: [PATCH 03/14] remove invalid verbose argument --- lib/pavilion/cmd_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pavilion/cmd_utils.py b/lib/pavilion/cmd_utils.py index 60cc227ce..bc017067a 100644 --- a/lib/pavilion/cmd_utils.py +++ b/lib/pavilion/cmd_utils.py @@ -180,7 +180,7 @@ def arg_filtered_tests(pav_cfg: "PavConfig", args: argparse.Namespace, if not args.tests: args.tests.append('last') - test_paths = test_list_to_paths(pav_cfg, args.tests, verbose) + test_paths = test_list_to_paths(pav_cfg, args.tests) return dir_db.select_from( pav_cfg, From d2d086abf4be41c626f914b789a7e72bf3b448f3 Mon Sep 17 00:00:00 2001 From: Hank Wikle Date: Wed, 29 May 2024 08:18:43 -0600 Subject: [PATCH 04/14] Fix missing import --- lib/pavilion/cmd_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pavilion/cmd_utils.py b/lib/pavilion/cmd_utils.py index bc017067a..1682a17c9 100644 --- a/lib/pavilion/cmd_utils.py +++ b/lib/pavilion/cmd_utils.py @@ -8,7 +8,7 @@ import sys import time from pathlib import Path -from typing import List, TextIO, Union, Iterator +from typing import List, TextIO, Union, Optional, Iterator from collections import defaultdict from pavilion import config From f29d799aee7b47aa9068f173db960ee4c711c570 Mon Sep 17 00:00:00 2001 From: Paul Ferrell Date: Mon, 19 Aug 2024 11:06:23 -0600 Subject: [PATCH 05/14] Probably fixed parse args problem. --- lib/pavilion/main.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/pavilion/main.py b/lib/pavilion/main.py index 349214cab..74e7b1aa8 100644 --- a/lib/pavilion/main.py +++ b/lib/pavilion/main.py @@ -47,14 +47,11 @@ def main(): # This has to be done before we initialize plugins parser = arguments.get_parser() - # Just need the show_tracebacks flag here - partial_args, _ = parser.parse_known_args() - # Get the Pavilion config try: pav_cfg = config.find_pavilion_config() except Exception as err: - if not partial_args.show_tracebacks: + if not '--show-tracebacks' in sys.argv: output.fprint(sys.stderr, "Error getting config, exiting.", err, color=output.RED) else: print(traceback.format_exc()) From 288cd8bb73fb039e6c01eca5902c50103ddd7c0b Mon Sep 17 00:00:00 2001 From: Hank Wikle Date: Thu, 5 Dec 2024 15:25:36 -0700 Subject: [PATCH 06/14] Progresss --- lib/pavilion/errors.py | 30 +++++++++++++++++++++++++++--- lib/pavilion/main.py | 4 ++-- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/lib/pavilion/errors.py b/lib/pavilion/errors.py index c7281947f..906c51eb1 100644 --- a/lib/pavilion/errors.py +++ b/lib/pavilion/errors.py @@ -7,10 +7,15 @@ import shutil import traceback +from traceback import format_exception +from typing import List + import lark import yc_yaml +from pavilion.micro import flatten + class PavilionError(RuntimeError): """Base class for all Pavilion errors.""" @@ -47,12 +52,31 @@ def __str__(self): else: return self.msg - def pformat(self) -> str: - """Specially format the exception for printing.""" + @staticmethod + def _wrap_lines(lines: List[str], width: int) -> List[str]: + """Given a list of lines, produce a new list of lines wrapped to the specified width.""" + + lines = map(lambda x: textwrap.wrap(x, width=width), lines) + + return list(flatten(lines)) + + + def pformat(self, traceback: bool = False) -> str: + """Specially format the exception for printing. If traceback is True, return the full + traceback associated with the error. Otherwise, return a summary of the error.""" + + width = shutil.get_terminal_size((80, 80)).columns + + if traceback: + lines = self._wrap_lines(format_exception(self)) + + # Remove newlines, for consistency with textwrap.wrap + map(lambda x: x.rstrip("\n"), lines) + + return "\n".join(lines) lines = [] next_exc = self.prior_error - width = shutil.get_terminal_size((80, 80)).columns tab_level = 0 for line in str(self.msg).split('\n'): lines.extend(textwrap.wrap(line, width=width)) diff --git a/lib/pavilion/main.py b/lib/pavilion/main.py index 74e7b1aa8..204d8dcaf 100644 --- a/lib/pavilion/main.py +++ b/lib/pavilion/main.py @@ -54,7 +54,7 @@ def main(): if not '--show-tracebacks' in sys.argv: output.fprint(sys.stderr, "Error getting config, exiting.", err, color=output.RED) else: - print(traceback.format_exc()) + PavilionError(err).pformat(traceback=True) sys.exit(-1) @@ -68,7 +68,7 @@ def main(): if not partial_args.show_tracebacks: output.fprint(sys.stderr, "Error initializing plugins.", err, color=output.RED) else: - print(traceback.format_exc()) + PavilionError(err).pformat(traceback=True) sys.exit(-1) From 3b226b35b05740df9f591dddd45688d509a2d945 Mon Sep 17 00:00:00 2001 From: Hank Wikle Date: Thu, 5 Dec 2024 15:44:35 -0700 Subject: [PATCH 07/14] Fix some things --- lib/pavilion/clean.py | 1 + lib/pavilion/series/test_set.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/pavilion/clean.py b/lib/pavilion/clean.py index f58406ebc..c27a575b7 100644 --- a/lib/pavilion/clean.py +++ b/lib/pavilion/clean.py @@ -8,6 +8,7 @@ from pavilion import groups from pavilion import lockfile from pavilion import utils +from pavilion import config from pavilion.builder import TestBuilder from pavilion.test_run import test_run_attr_transform, TestAttributes diff --git a/lib/pavilion/series/test_set.py b/lib/pavilion/series/test_set.py index 0d0e3792d..b271de641 100644 --- a/lib/pavilion/series/test_set.py +++ b/lib/pavilion/series/test_set.py @@ -190,7 +190,8 @@ def __ordered_split(self) -> List['TestSet']: return test_sets def make_iter(self, build_only: bool = False, rebuild: bool = False, - local_builds_only: bool = False) -> Iterator[List[TestRun]]: + local_builds_only: bool = False, + show_tracebacks: bool = False) -> Iterator[List[TestRun]]: """Resolve the given tests names and options into actual test run objects, and print the test creation status. This returns an iterator over batches tests, respecting the batch_size (half the simultanious limit). From 768b83be7f7204ed52df71d4210652a0f9b64b69 Mon Sep 17 00:00:00 2001 From: Hank Wikle Date: Thu, 5 Dec 2024 15:54:41 -0700 Subject: [PATCH 08/14] Fix style issues --- lib/pavilion/cmd_utils.py | 3 ++- lib/pavilion/errors.py | 2 +- lib/pavilion/series/test_set.py | 8 ++++++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/pavilion/cmd_utils.py b/lib/pavilion/cmd_utils.py index 1682a17c9..876f6509b 100644 --- a/lib/pavilion/cmd_utils.py +++ b/lib/pavilion/cmd_utils.py @@ -321,7 +321,8 @@ def get_collection_path(pav_cfg, collection) -> Union[Path, None]: return None -def test_list_to_paths(pav_cfg: config.PavConfig, req_tests: List, errfile: Optional[Path] = None) -> List[Path]: +def test_list_to_paths(pav_cfg: config.PavConfig, req_tests: List, + errfile: Optional[Path] = None) -> List[Path]: """Given a list of raw test id's and series id's, return a list of paths to those tests. The keyword 'last' may also be given to get the last series run by diff --git a/lib/pavilion/errors.py b/lib/pavilion/errors.py index 906c51eb1..3b90f6450 100644 --- a/lib/pavilion/errors.py +++ b/lib/pavilion/errors.py @@ -56,7 +56,7 @@ def __str__(self): def _wrap_lines(lines: List[str], width: int) -> List[str]: """Given a list of lines, produce a new list of lines wrapped to the specified width.""" - lines = map(lambda x: textwrap.wrap(x, width=width), lines) + lines = map(lambda x: textwrap.wrap(x, width=width), lines) return list(flatten(lines)) diff --git a/lib/pavilion/series/test_set.py b/lib/pavilion/series/test_set.py index b271de641..38b0e10eb 100644 --- a/lib/pavilion/series/test_set.py +++ b/lib/pavilion/series/test_set.py @@ -238,11 +238,15 @@ def make_iter(self, build_only: bool = False, rebuild: bool = False, for error in cfg_resolver.errors: if error.request is not None: self.status.set(S_STATES.ERROR, - '{} - {}'.format(error.request.request, error.pformat(show_tracebacks))) + '{} - {}'.format( + error.request.request, + error.pformat(show_tracebacks))) output.fprint( self.outfile, - "{} - {}".format(error.request.request, error.pformat(show_tracebacks))) + "{} - {}".format( + error.request.request, + error.pformat(show_tracebacks))) else: self.status.set(S_STATES.ERROR, error.pformat()) output.fprint( From efd1d32c5589673c80525023112043194cffd0c4 Mon Sep 17 00:00:00 2001 From: Hank Wikle Date: Fri, 6 Dec 2024 12:12:44 -0700 Subject: [PATCH 09/14] Fix broken unit test --- lib/pavilion/cmd_utils.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/pavilion/cmd_utils.py b/lib/pavilion/cmd_utils.py index 876f6509b..a1a943109 100644 --- a/lib/pavilion/cmd_utils.py +++ b/lib/pavilion/cmd_utils.py @@ -180,7 +180,7 @@ def arg_filtered_tests(pav_cfg: "PavConfig", args: argparse.Namespace, if not args.tests: args.tests.append('last') - test_paths = test_list_to_paths(pav_cfg, args.tests) + test_paths = test_list_to_paths(pav_cfg, args.tests, verbose) return dir_db.select_from( pav_cfg, @@ -339,7 +339,7 @@ def test_list_to_paths(pav_cfg: config.PavConfig, req_tests: List, test_paths = [] for raw_id in req_tests: - + if raw_id == 'last': raw_id = series.load_user_series_id(pav_cfg, errfile) if raw_id is None: @@ -360,10 +360,12 @@ def test_list_to_paths(pav_cfg: config.PavConfig, req_tests: List, test_path = test_wd/TestRun.RUN_DIR/str(_id) test_paths.append(test_path) + if not test_path.exists(): output.fprint(errfile, "Test run with id '{}' could not be found.".format(raw_id), color=output.YELLOW) + elif raw_id[0] == 's' and utils.is_int(raw_id[1:]): # A series. try: From ef6e673e8208e2e72f49c323f140eba2f13c6e16 Mon Sep 17 00:00:00 2001 From: Hank Wikle Date: Fri, 6 Dec 2024 12:21:01 -0700 Subject: [PATCH 10/14] Fix style issues --- lib/pavilion/cmd_utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/pavilion/cmd_utils.py b/lib/pavilion/cmd_utils.py index a1a943109..e1ce127c0 100644 --- a/lib/pavilion/cmd_utils.py +++ b/lib/pavilion/cmd_utils.py @@ -339,7 +339,6 @@ def test_list_to_paths(pav_cfg: config.PavConfig, req_tests: List, test_paths = [] for raw_id in req_tests: - if raw_id == 'last': raw_id = series.load_user_series_id(pav_cfg, errfile) if raw_id is None: From 2eec46c18436402b432457da6277d0f837f1d8df Mon Sep 17 00:00:00 2001 From: Hank Wikle Date: Fri, 6 Dec 2024 13:40:25 -0700 Subject: [PATCH 11/14] Add option to print full traceback to error class --- lib/pavilion/cmd_utils.py | 2 +- lib/pavilion/commands/group.py | 6 ++++-- lib/pavilion/errors.py | 7 +++++-- lib/pavilion/series/test_set.py | 16 +++++----------- 4 files changed, 15 insertions(+), 16 deletions(-) diff --git a/lib/pavilion/cmd_utils.py b/lib/pavilion/cmd_utils.py index e1ce127c0..b6dcdfed7 100644 --- a/lib/pavilion/cmd_utils.py +++ b/lib/pavilion/cmd_utils.py @@ -321,7 +321,7 @@ def get_collection_path(pav_cfg, collection) -> Union[Path, None]: return None -def test_list_to_paths(pav_cfg: config.PavConfig, req_tests: List, +def test_list_to_paths(pav_cfg: config.PavConfig, req_tests: List[str], errfile: Optional[Path] = None) -> List[Path]: """Given a list of raw test id's and series id's, return a list of paths to those tests. diff --git a/lib/pavilion/commands/group.py b/lib/pavilion/commands/group.py index 88bf20146..2f6581e63 100644 --- a/lib/pavilion/commands/group.py +++ b/lib/pavilion/commands/group.py @@ -2,6 +2,7 @@ import errno import fnmatch +from typing import Optional from pavilion import groups from pavilion import config @@ -122,7 +123,7 @@ def run(self, pav_cfg, args): return self._run_sub_command(pav_cfg, args) - def _get_group(self, pav_cfg: config.PavConfig, group_name: str) -> TestGroup: + def _get_group(self, pav_cfg: config.PavConfig, group_name: str) -> Optional[TestGroup]: """Get the requested group, and print a standard error message on failure.""" try: @@ -131,13 +132,14 @@ def _get_group(self, pav_cfg: config.PavConfig, group_name: str) -> TestGroup: fprint(self.errfile, "Error loading group '{}'", color=output.RED) fprint(self.errfile, err.pformat()) - raise err + return None if not group.exists(): fprint(self.errfile, "Group '{}' does not exist.\n Looked here:" .format(group_name), color=output.RED) fprint(self.errfile, " " + group.path.as_posix()) + return None return group diff --git a/lib/pavilion/errors.py b/lib/pavilion/errors.py index 3b90f6450..c1ed2a80d 100644 --- a/lib/pavilion/errors.py +++ b/lib/pavilion/errors.py @@ -23,6 +23,9 @@ class PavilionError(RuntimeError): SPLIT_RE = re.compile(': *\n? *') TAB_LEVEL = ' ' + # Set traceback behavior for all instances + show_tracebacks = False + def __init__(self, msg, prior_error=None, data=None): """These take a new message and whatever prior error caused the problem. @@ -61,13 +64,13 @@ def _wrap_lines(lines: List[str], width: int) -> List[str]: return list(flatten(lines)) - def pformat(self, traceback: bool = False) -> str: + def pformat(self) -> str: """Specially format the exception for printing. If traceback is True, return the full traceback associated with the error. Otherwise, return a summary of the error.""" width = shutil.get_terminal_size((80, 80)).columns - if traceback: + if PavilionError.show_tracebacks: lines = self._wrap_lines(format_exception(self)) # Remove newlines, for consistency with textwrap.wrap diff --git a/lib/pavilion/series/test_set.py b/lib/pavilion/series/test_set.py index 38b0e10eb..533318de3 100644 --- a/lib/pavilion/series/test_set.py +++ b/lib/pavilion/series/test_set.py @@ -189,9 +189,8 @@ def __ordered_split(self) -> List['TestSet']: return test_sets - def make_iter(self, build_only: bool = False, rebuild: bool = False, - local_builds_only: bool = False, - show_tracebacks: bool = False) -> Iterator[List[TestRun]]: + def make_iter(self, build_only=False, rebuild=False, local_builds_only=False) \ + -> Iterator[List[TestRun]]: """Resolve the given tests names and options into actual test run objects, and print the test creation status. This returns an iterator over batches tests, respecting the batch_size (half the simultanious limit). @@ -238,20 +237,15 @@ def make_iter(self, build_only: bool = False, rebuild: bool = False, for error in cfg_resolver.errors: if error.request is not None: self.status.set(S_STATES.ERROR, - '{} - {}'.format( - error.request.request, - error.pformat(show_tracebacks))) - + '{} - {}'.format(error.request.request, error.pformat())) output.fprint( self.outfile, - "{} - {}".format( - error.request.request, - error.pformat(show_tracebacks))) + "{} - {}".format(error.request.request, error.pformat())) else: self.status.set(S_STATES.ERROR, error.pformat()) output.fprint( self.outfile, - "{}".format(error.pformat(show_tracebacks))) + "{}".format(error.pformat())) if not self.ignore_errors: raise TestSetError("Error creating tests for test set {}.".format(self.name), From 6635779e6d60c8e09785c676e60e81d718d41f8b Mon Sep 17 00:00:00 2001 From: Hank Wikle Date: Tue, 10 Dec 2024 09:33:23 -0700 Subject: [PATCH 12/14] Misc --- lib/pavilion/errors.py | 1 + lib/pavilion/main.py | 34 ++++++++++++++++------------------ 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/lib/pavilion/errors.py b/lib/pavilion/errors.py index c1ed2a80d..956275012 100644 --- a/lib/pavilion/errors.py +++ b/lib/pavilion/errors.py @@ -39,6 +39,7 @@ def __init__(self, msg, prior_error=None, data=None): self.data = data super().__init__(msg) + @property def msg(self): """Just return msg. This exists to be overridden in order to allow for diff --git a/lib/pavilion/main.py b/lib/pavilion/main.py index 204d8dcaf..feb0b7c73 100644 --- a/lib/pavilion/main.py +++ b/lib/pavilion/main.py @@ -7,6 +7,8 @@ import pavilion.commands import pavilion.errors + +from pavilion.errors import PavilionError from . import arguments from . import commands from . import config @@ -41,8 +43,7 @@ def main(): # Pavilion is compatible with python >= 3.4 if (sys.version_info[0] != SUPPORTED_MAJOR_VERSION or sys.version_info[1] < MIN_SUPPORTED_MINOR_VERSION): - output.fprint(sys.stderr, "Pavilion requires python 3.6 or higher.", color=output.RED) - sys.exit(-1) + raise PavilionError("Pavilion requires python 3.6 or higher.") # This has to be done before we initialize plugins parser = arguments.get_parser() @@ -51,12 +52,7 @@ def main(): try: pav_cfg = config.find_pavilion_config() except Exception as err: - if not '--show-tracebacks' in sys.argv: - output.fprint(sys.stderr, "Error getting config, exiting.", err, color=output.RED) - else: - PavilionError(err).pformat(traceback=True) - - sys.exit(-1) + raise PavilionError("Error getting config, exiting.") from err # Setup all the loggers for Pavilion log_output = log_setup.setup_loggers(pav_cfg) @@ -65,12 +61,7 @@ def main(): try: plugins.initialize_plugins(pav_cfg) except pavilion.errors.PluginError as err: - if not partial_args.show_tracebacks: - output.fprint(sys.stderr, "Error initializing plugins.", err, color=output.RED) - else: - PavilionError(err).pformat(traceback=True) - - sys.exit(-1) + raise PavilionError("Error initializing plugins.") from err # Partially parse the arguments. All we really care about is the subcommand. partial_args, _ = parser.parse_known_args() @@ -186,7 +177,14 @@ def profile_main(): if __name__ == '__main__': - if '--profile' in sys.argv: - profile_main() - else: - main() + if '--show-tracebacks' in sys.argv: + PavilionError.show_tracebacks = True + + try: + if '--profile' in sys.argv: + profile_main() + else: + main() + except PavilionError as err: + err.pformat() + exit(-1) From 3f55c31999560755fe50121607a21ef5f4091896 Mon Sep 17 00:00:00 2001 From: Hank Wikle Date: Tue, 10 Dec 2024 14:02:12 -0700 Subject: [PATCH 13/14] Add test for pformat --- lib/pavilion/errors.py | 2 +- test/tests/errors_tests.py | 83 +++++++++++++++++++++++--------------- 2 files changed, 52 insertions(+), 33 deletions(-) diff --git a/lib/pavilion/errors.py b/lib/pavilion/errors.py index 956275012..7650bbcc1 100644 --- a/lib/pavilion/errors.py +++ b/lib/pavilion/errors.py @@ -72,7 +72,7 @@ def pformat(self) -> str: width = shutil.get_terminal_size((80, 80)).columns if PavilionError.show_tracebacks: - lines = self._wrap_lines(format_exception(self)) + lines = self._wrap_lines(format_exception(PavilionError, self, tb=None), width) # Remove newlines, for consistency with textwrap.wrap map(lambda x: x.rstrip("\n"), lines) diff --git a/test/tests/errors_tests.py b/test/tests/errors_tests.py index a44dea761..6444d61a2 100644 --- a/test/tests/errors_tests.py +++ b/test/tests/errors_tests.py @@ -7,48 +7,67 @@ class ErrorTests(unittest.PavTestCase): - """Test functionaility of Pavilion specific errors.""" + """Test functionaility of Pavilion specific errors.""" - def test_error_pickling(self): - """Check that all of the Pavilon errors pickle and unpickle correctly.""" + def test_error_pickling(self): + """Check that all of the Pavilon errors pickle and unpickle correctly.""" - prior_error = ValueError("hiya") + prior_error = ValueError("hiya") - base_args = (["foo"], ) - base_kwargs = {'prior_error':prior_error, 'data': {"foo": "bar"}} + base_args = (["foo"], ) + base_kwargs = {'prior_error':prior_error, 'data': {"foo": "bar"}} - spec_args = { - 'VariableError': (('hello',), - {'var_set': 'var', 'var': 'foo', - 'index': 3, 'sub_var': 'ok', 'prior_error': prior_error}), - 'DeferredError': (('hello',), - {'var_set': 'var', 'var': 'foo', - 'index': 3, 'sub_var': 'ok', 'prior_error': prior_error}), - 'ParserValueError': ((Token('oh_no', 'terrible_things'), 'hello'), {}) - } + spec_args = { + 'VariableError': (('hello',), + {'var_set': 'var', 'var': 'foo', + 'index': 3, 'sub_var': 'ok', 'prior_error': prior_error}), + 'DeferredError': (('hello',), + {'var_set': 'var', 'var': 'foo', + 'index': 3, 'sub_var': 'ok', 'prior_error': prior_error}), + 'ParserValueError': ((Token('oh_no', 'terrible_things'), 'hello'), {}) + } - base_attrs = dir(errors.PavilionError("foo")) + base_attrs = dir(errors.PavilionError("foo")) - exc_classes = [] - for name in dir(errors): - obj = getattr(errors, name) - if (type(obj) == type(errors.PavilionError) - and issubclass(obj, errors.PavilionError)): - exc_classes.append(obj) + exc_classes = [] + for name in dir(errors): + obj = getattr(errors, name) + if (type(obj) == type(errors.PavilionError) + and issubclass(obj, errors.PavilionError)): + exc_classes.append(obj) - for exc_class in exc_classes: - exc_name = exc_class.__name__ + for exc_class in exc_classes: + exc_name = exc_class.__name__ - args, kwargs = spec_args.get(exc_name, (base_args, base_kwargs)) + args, kwargs = spec_args.get(exc_name, (base_args, base_kwargs)) - inst = exc_class(*args, **kwargs) + inst = exc_class(*args, **kwargs) - p_str = pickle.dumps(inst) + p_str = pickle.dumps(inst) - try: - new_inst = pickle.loads(p_str) - except TypeError: - self.fail("Failed to reconstitute exception '{}'".format(exc_name)) + try: + new_inst = pickle.loads(p_str) + except TypeError: + self.fail("Failed to reconstitute exception '{}'".format(exc_name)) - self.assertEqual(inst, new_inst) + self.assertEqual(inst, new_inst) + + def test_pformat(self): + try: + try: + raise RuntimeError("Raised a RuntimeError as a test") + except RuntimeError as err: + raise errors.PavilionError("Match this") from err + except errors.PavilionError as err: + self.assertTrue(err.pformat() == "Match this") + + errors.PavilionError.show_tracebacks = True + + try: + try: + raise RuntimeError("Raised a RuntimeError as a test") + except RuntimeError as err: + raise errors.PavilionError("Match this") from err + except errors.PavilionError as err: + self.assertTrue(err.pformat().startswith("Traceback (most recent call last)")) From 504d00cb61822831c4492e9e56ad0646cb942693 Mon Sep 17 00:00:00 2001 From: Hank Wikle Date: Tue, 10 Dec 2024 14:29:22 -0700 Subject: [PATCH 14/14] Finish traceback stuff --- lib/pavilion/errors.py | 7 ++----- test/tests/errors_tests.py | 7 +++++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/pavilion/errors.py b/lib/pavilion/errors.py index 7650bbcc1..286196cb1 100644 --- a/lib/pavilion/errors.py +++ b/lib/pavilion/errors.py @@ -72,12 +72,9 @@ def pformat(self) -> str: width = shutil.get_terminal_size((80, 80)).columns if PavilionError.show_tracebacks: - lines = self._wrap_lines(format_exception(PavilionError, self, tb=None), width) + lines = format_exception(PavilionError, self, self.__traceback__) - # Remove newlines, for consistency with textwrap.wrap - map(lambda x: x.rstrip("\n"), lines) - - return "\n".join(lines) + return "".join(lines) lines = [] next_exc = self.prior_error diff --git a/test/tests/errors_tests.py b/test/tests/errors_tests.py index 6444d61a2..e5290ea5d 100644 --- a/test/tests/errors_tests.py +++ b/test/tests/errors_tests.py @@ -54,13 +54,16 @@ def test_error_pickling(self): self.assertEqual(inst, new_inst) def test_pformat(self): + """Test that pformat formats errors as expected, including when Pavilion + is set to show full tracebacks for errors.""" + try: try: raise RuntimeError("Raised a RuntimeError as a test") except RuntimeError as err: raise errors.PavilionError("Match this") from err except errors.PavilionError as err: - self.assertTrue(err.pformat() == "Match this") + self.assertEqual(err.pformat(), "Match this") errors.PavilionError.show_tracebacks = True @@ -70,4 +73,4 @@ def test_pformat(self): except RuntimeError as err: raise errors.PavilionError("Match this") from err except errors.PavilionError as err: - self.assertTrue(err.pformat().startswith("Traceback (most recent call last)")) + self.assertTrue(err.pformat().startswith("Traceback"))