-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Includes new command `benchpark unit-test` This command makes use of pytest, which can accept more arguments that are specified as command-line option: add a "pass-through" mechanism for arguments that are unknown to the command (but e.g. in this case are known to pytest) * Add a `spack.yaml` environment file which makes it easier to install the resources needed to run unit tests (e.g. pytest); these are not included in Benchpark's `requirements.txt` * Tests are added for Spec syntax and for the Experiment class. Coverage is currently low but that is a matter for future PRs. * This includes a minor refactor of the logic used in the library code to locate Benchpark's root directory (previously `source_location`, now accessable as `paths.benchpark_root`) --------- Co-authored-by: Alec Scott <[email protected]> Co-authored-by: pearce8 <[email protected]> Co-authored-by: Peter Josef Scheibel <[email protected]>
- Loading branch information
1 parent
f1efb3b
commit e2d432d
Showing
18 changed files
with
772 additions
and
56 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
#------------------------------------------------------------------------ | ||
# Load Development Spack Environment (If Spack is installed.) | ||
# | ||
# Run 'direnv allow' from within the cloned repository to automatically | ||
# load the spack environment when you enter the directory. | ||
#------------------------------------------------------------------------ | ||
if type spack &>/dev/null; then | ||
. $SPACK_ROOT/share/spack/setup-env.sh | ||
spack env activate -d . | ||
fi |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,232 @@ | ||
# Copyright 2023 Lawrence Livermore National Security, LLC and other | ||
# Benchpark Project Developers. See the top-level COPYRIGHT file for details. | ||
# | ||
# SPDX-License-Identifier: Apache-2.0 | ||
import argparse | ||
import collections | ||
import io | ||
import pytest | ||
import os | ||
import re | ||
import sys | ||
|
||
import llnl.util.filesystem | ||
import llnl.util.tty.color as color | ||
import llnl.util.tty.colify as colify | ||
|
||
import benchpark.paths | ||
|
||
|
||
def setup_parser(subparser): | ||
subparser.add_argument( | ||
"-H", | ||
"--pytest-help", | ||
action="store_true", | ||
default=False, | ||
help="show full pytest help, with advanced options", | ||
) | ||
|
||
subparser.add_argument( | ||
"-n", | ||
"--numprocesses", | ||
type=int, | ||
default=1, | ||
help="run tests in parallel up to this wide, default 1 for sequential", | ||
) | ||
|
||
# extra arguments to list tests | ||
list_group = subparser.add_argument_group("listing tests") | ||
list_mutex = list_group.add_mutually_exclusive_group() | ||
list_mutex.add_argument( | ||
"-l", | ||
"--list", | ||
action="store_const", | ||
default=None, | ||
dest="list", | ||
const="list", | ||
help="list test filenames", | ||
) | ||
list_mutex.add_argument( | ||
"-L", | ||
"--list-long", | ||
action="store_const", | ||
default=None, | ||
dest="list", | ||
const="long", | ||
help="list all test functions", | ||
) | ||
list_mutex.add_argument( | ||
"-N", | ||
"--list-names", | ||
action="store_const", | ||
default=None, | ||
dest="list", | ||
const="names", | ||
help="list full names of all tests", | ||
) | ||
|
||
# spell out some common pytest arguments, so they'll show up in help | ||
pytest_group = subparser.add_argument_group( | ||
"common pytest arguments (benchpark unit-test --pytest-help for more)" | ||
) | ||
pytest_group.add_argument( | ||
"-s", | ||
action="append_const", | ||
dest="parsed_args", | ||
const="-s", | ||
help="print output while tests run (disable capture)", | ||
) | ||
pytest_group.add_argument( | ||
"-k", | ||
action="store", | ||
metavar="EXPRESSION", | ||
dest="expression", | ||
help="filter tests by keyword (can also use w/list options)", | ||
) | ||
pytest_group.add_argument( | ||
"--showlocals", | ||
action="append_const", | ||
dest="parsed_args", | ||
const="--showlocals", | ||
help="show local variable values in tracebacks", | ||
) | ||
|
||
# remainder is just passed to pytest | ||
subparser.add_argument( | ||
"pytest_args", nargs=argparse.REMAINDER, help="arguments for pytest" | ||
) | ||
|
||
|
||
def do_list(args, extra_args): | ||
"""Print a lists of tests than what pytest offers.""" | ||
|
||
def colorize(c, prefix): | ||
if isinstance(prefix, tuple): | ||
return "::".join( | ||
color.colorize("@%s{%s}" % (c, p)) for p in prefix if p != "()" | ||
) | ||
return color.colorize("@%s{%s}" % (c, prefix)) | ||
|
||
# To list the files we just need to inspect the filesystem, | ||
# which doesn't need to wait for pytest collection and doesn't | ||
# require parsing pytest output | ||
files = llnl.util.filesystem.find( | ||
root=benchpark.paths.test_path, files="*.py", recursive=True | ||
) | ||
files = [ | ||
os.path.relpath(f, start=benchpark.paths.benchpark_root) | ||
for f in files | ||
if not f.endswith(("conftest.py", "__init__.py")) | ||
] | ||
|
||
old_output = sys.stdout | ||
try: | ||
sys.stdout = output = io.StringIO() | ||
pytest.main(["--collect-only"] + extra_args) | ||
finally: | ||
sys.stdout = old_output | ||
|
||
lines = output.getvalue().split("\n") | ||
tests = collections.defaultdict(set) | ||
|
||
# collect tests into sections | ||
node_regexp = re.compile(r"(\s*)<([^ ]*) ['\"]?([^']*)['\"]?>") | ||
key_parts, name_parts = [], [] | ||
for line in lines: | ||
match = node_regexp.match(line) | ||
if not match: | ||
continue | ||
indent, nodetype, name = match.groups() | ||
|
||
# strip parametrized tests | ||
if "[" in name: | ||
name = name[: name.index("[")] | ||
|
||
len_indent = len(indent) | ||
if os.path.isabs(name): | ||
name = os.path.relpath(name, start=benchpark.paths.benchpark_root) | ||
|
||
item = (len_indent, name, nodetype) | ||
|
||
# Reduce the parts to the scopes that are of interest | ||
name_parts = [x for x in name_parts if x[0] < len_indent] | ||
key_parts = [x for x in key_parts if x[0] < len_indent] | ||
|
||
# From version 3.X to version 6.X the output format | ||
# changed a lot in pytest, and probably will change | ||
# in the future - so this manipulation might be fragile | ||
if nodetype.lower() == "function": | ||
name_parts.append(item) | ||
key_end = os.path.join(*key_parts[-1][1].split("/")) | ||
key = next(f for f in files if f.endswith(key_end)) | ||
tests[key].add(tuple(x[1] for x in name_parts)) | ||
elif nodetype.lower() == "class": | ||
name_parts.append(item) | ||
elif nodetype.lower() in ("package", "module"): | ||
key_parts.append(item) | ||
|
||
if args.list == "list": | ||
files = set(tests.keys()) | ||
color_files = [colorize("B", file) for file in sorted(files)] | ||
colify.colify(color_files) | ||
|
||
elif args.list == "long": | ||
for prefix, functions in sorted(tests.items()): | ||
path = colorize("*B", prefix) + "::" | ||
functions = [colorize("c", f) for f in sorted(functions)] | ||
color.cprint(path) | ||
colify.colify(functions, indent=4) | ||
print() | ||
|
||
else: # args.list == "names" | ||
all_functions = [ | ||
colorize("*B", prefix) + "::" + colorize("c", f) | ||
for prefix, functions in sorted(tests.items()) | ||
for f in sorted(functions) | ||
] | ||
colify.colify(all_functions) | ||
|
||
|
||
def add_back_pytest_args(args, unknown_args): | ||
"""Add parsed pytest args, unknown args, and remainder together. | ||
We add some basic pytest arguments to the Spack parser to ensure that | ||
they show up in the short help, so we have to reassemble things here. | ||
""" | ||
result = args.parsed_args or [] | ||
result += unknown_args or [] | ||
result += args.pytest_args or [] | ||
if args.expression: | ||
result += ["-k", args.expression] | ||
return result | ||
|
||
|
||
def command(args, unknown_args): | ||
global pytest | ||
|
||
if args.pytest_help: | ||
# make the pytest.main help output more accurate | ||
sys.argv[0] = "spack unit-test" | ||
return pytest.main(["-h"]) | ||
|
||
# add back any parsed pytest args we need to pass to pytest | ||
pytest_args = add_back_pytest_args(args, unknown_args) | ||
pytest_root = benchpark.paths.benchpark_root | ||
|
||
if args.numprocesses is not None and args.numprocesses > 1: | ||
pytest_args.extend( | ||
[ | ||
"--dist", | ||
"loadfile", | ||
"--tx", | ||
f"{args.numprocesses}*popen//python=benchpark-tmpconfig benchpark-python", | ||
] | ||
) | ||
|
||
# pytest.ini lives in the root of the spack repository. | ||
with llnl.util.filesystem.working_dir(pytest_root): | ||
if args.list: | ||
do_list(args, pytest_args) | ||
return | ||
|
||
return pytest.main(pytest_args) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.