Skip to content

Commit d1b7213

Browse files
committed
Added excludes; in-doctest exception handling; mask warnings (for now?).
1 parent 44a161f commit d1b7213

File tree

2 files changed

+69
-16
lines changed

2 files changed

+69
-16
lines changed

tools/check_doctest.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@
99
# "verbose=1",
1010
# ]
1111

12-
tstargs = ["-mvr", "ncdata.iris"]
12+
# tstargs = ["-mvr", "iris.coords", "-o", "verbose=True"]
13+
14+
# tstargs = ["-mvr", "iris.tests.unit.fileformats.netcdf", "-e", "attribute_handlers"]
15+
16+
tstargs = ["-mvr", "iris._combine", "-o", "raise_on_error=True"]
1317

1418
args = _parser.parse_args(tstargs)
1519
kwargs = parserargs_as_kwargs(args)

tools/run_doctests.py

Lines changed: 64 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,48 @@
22
import argparse
33
import doctest
44
import importlib
5+
import traceback
56
from pathlib import Path
67
import pkgutil
78
import sys
9+
import warnings
810

911

1012
def list_modules_recursive(
11-
module_importname: str, include_private: bool = True
13+
module_importname: str, include_private: bool = True,
14+
exclude_matches: list[str] = []
1215
):
1316
module_names = [module_importname]
1417
# Identify module from its import path (no import -> fail back to caller)
15-
module = importlib.import_module(module_importname)
16-
# Get the filepath of the module base directory
17-
module_filepath = Path(module.__file__)
18-
if module_filepath.name == "__init__.py":
19-
search_filepath = str(module_filepath.parent)
20-
for _, name, ispkg in pkgutil.iter_modules([search_filepath]):
21-
if not name.startswith("_") or include_private:
18+
try:
19+
error = None
20+
module = importlib.import_module(module_importname)
21+
except Exception as exc:
22+
print(f"\n\nIMPORT FAILED: {module_importname}\n")
23+
error = exc
24+
25+
if error is None:
26+
# Add sub-modules to the list
27+
# Get the filepath of the module base directory
28+
module_filepath = Path(module.__file__)
29+
if module_filepath.name == "__init__.py":
30+
search_filepath = str(module_filepath.parent)
31+
for _, name, ispkg in pkgutil.iter_modules([search_filepath]):
32+
if name.startswith("_") and not include_private:
33+
continue
34+
2235
submodule_name = module_importname + "." + name
36+
if any(match in submodule_name for match in exclude_matches):
37+
continue
38+
2339
module_names.append(submodule_name)
2440
if ispkg:
2541
module_names.extend(
2642
list_modules_recursive(
2743
submodule_name, include_private=include_private
2844
)
2945
)
46+
3047
# I don't know why there are duplicates, but there can be.
3148
result = []
3249
for name in module_names:
@@ -69,6 +86,7 @@ def run_doctest_paths(
6986
paths_are_modules:bool = False,
7087
recurse_modules: bool = False,
7188
include_private_modules: bool = False,
89+
exclude_matches: list[str] = [],
7290
option_kwargs: dict = {},
7391
verbose: bool = False,
7492
dry_run: bool = False,
@@ -80,23 +98,28 @@ def run_doctest_paths(
8098
"RUNNING run_doctest("
8199
f"paths={paths!r}"
82100
f", paths_are_modules={paths_are_modules!r}"
101+
f", recurse_modules={recurse_modules!r}"
102+
f", include_private_modules={include_private_modules!r}"
103+
f", exclude_matches={exclude_matches!r}"
83104
f", option_kwargs={option_kwargs!r}"
84105
f", verbose={verbose!r}"
85106
f", dry_run={dry_run!r}"
86107
f", stop_on_failure={stop_on_failure!r}"
87-
f", include_private={include_private_modules!r}"
88108
")"
89109
)
90110
if dry_run:
91111
verbose = True
92112

113+
warnings.simplefilter("ignore")
114+
93115
if paths_are_modules:
94116
doctest_function = doctest.testmod
95117
if recurse_modules:
96118
module_paths = []
97119
for path in paths:
98120
module_paths += list_modules_recursive(
99-
path, include_private=include_private_modules
121+
path, include_private=include_private_modules,
122+
exclude_matches=exclude_matches
100123
)
101124
paths = module_paths
102125

@@ -113,14 +136,33 @@ def run_doctest_paths(
113136
if verbose:
114137
print(f"\n-----\ndoctest.{doctest_function.__name__}: {path!r}")
115138
if not dry_run:
139+
op_fail = None
116140
if paths_are_modules:
117-
arg = importlib.import_module(path)
141+
try:
142+
arg = importlib.import_module(path)
143+
except Exception as exc:
144+
op_fail = exc
118145
else:
119146
arg = path
120-
n_fails, n_tests = doctest_function(arg, **option_kwargs)
121-
n_total_fails += n_fails
122-
n_total_tests += n_tests
123-
n_paths_tested += 1
147+
148+
if op_fail is None:
149+
try:
150+
n_fails, n_tests = doctest_function(arg, **option_kwargs)
151+
n_total_fails += n_fails
152+
n_total_tests += n_tests
153+
n_paths_tested += 1
154+
except Exception as exc:
155+
op_fail = exc
156+
157+
if op_fail is not None:
158+
n_total_fails += 1
159+
print(f"\n\nERROR occurred at {path!r}: {op_fail}\n")
160+
if isinstance(op_fail, doctest.UnexpectedException):
161+
# This is what happens with "-o raise_on_error=True", which is the
162+
# Python call equivalent of "-o FAIL_FAST" in the doctest CLI.
163+
print(f"Doctest caught exception: {op_fail}")
164+
traceback.print_exception(*op_fail.exc_info)
165+
124166
if n_total_fails > 0 and stop_on_failure:
125167
break
126168

@@ -168,6 +210,12 @@ def run_doctest_paths(
168210
action="store_true",
169211
help="If set, exclude private modules (only applies with -m and -r)",
170212
)
213+
_parser.add_argument(
214+
"-e",
215+
"--exclude",
216+
action="append",
217+
help="Match fragments of paths to exclude.",
218+
)
171219
_parser.add_argument(
172220
"-o",
173221
"--options",
@@ -209,6 +257,7 @@ def parserargs_as_kwargs(args):
209257
paths_are_modules=args.module,
210258
recurse_modules=args.recursive,
211259
include_private_modules=not args.publiconly,
260+
exclude_matches=args.exclude or [],
212261
option_kwargs=process_options(args.options),
213262
verbose=args.verbose,
214263
dry_run=args.dryrun,

0 commit comments

Comments
 (0)