Skip to content

Commit 466a054

Browse files
authored
Merge pull request #41 from nicholasjng/staging
Add generalized benchmark IO, refactor console reporting, harmonize `pybm env` interface
2 parents 4a95e60 + d46147d commit 466a054

29 files changed

+527
-453
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ repos:
1111
files: pybm/
1212

1313
- repo: https://github.com/psf/black
14-
rev: 21.12b0
14+
rev: 22.1.0
1515
hooks:
1616
- id: black
1717
language_version: python3

pybm/builders/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ def list(self, executable: Union[str, Path], verbose: bool = False) -> List[str]
5252
def uninstall(
5353
self,
5454
spec: PythonSpec,
55-
packages: List[str],
55+
packages: Optional[List[str]] = None,
5656
requirements_file: Optional[str] = None,
5757
options: Optional[List[str]] = None,
5858
verbose: bool = False,

pybm/builders/stdlib.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ class VenvBuilder(BaseBuilder):
7474

7575
def __init__(self, config: PybmConfig):
7676
super().__init__(config=config)
77-
self.venv_home: str = config.get_value("builder.homedir")
77+
self.venv_home: Path = Path(config.get_value("builder.homedir"))
7878

7979
# persistent venv options
8080
self.venv_options: List[str] = []
@@ -254,11 +254,12 @@ def install(
254254
spec.update_packages(packages=new_packages)
255255

256256
def link(self, env_dir: Union[str, Path], verbose: bool = False):
257-
# TODO: This discovery stuff should go into caller routine
258-
if (Path(self.venv_home) / env_dir).exists():
259-
path = Path(self.venv_home) / env_dir
257+
home_path = self.venv_home / env_dir
258+
if home_path.exists():
259+
path = home_path
260260
else:
261261
path = Path(env_dir)
262+
262263
if not builder_util.is_valid_venv(path, verbose=verbose):
263264
msg = (
264265
f"The specified path {str(env_dir)} was not recognized as a valid "
@@ -293,7 +294,7 @@ def list(self, executable: Union[str, Path], verbose: bool = False) -> List[str]
293294
def uninstall(
294295
self,
295296
spec: PythonSpec,
296-
packages: List[str],
297+
packages: Optional[List[str]] = None,
297298
requirements_file: Optional[str] = None,
298299
pip_options: Optional[List[str]] = None,
299300
verbose: bool = False,
@@ -304,7 +305,12 @@ def uninstall(
304305
executable = spec.executable
305306

306307
# do not ask for confirmation
307-
command = [executable, "-m", "pip", "uninstall", "-y", *packages]
308+
command = [executable, "-m", "pip", "uninstall", "-y"]
309+
if packages is not None:
310+
command += packages
311+
elif requirements_file is not None:
312+
command += ["-r", requirements_file]
313+
308314
command += list(set(options))
309315

310316
with pip_context("uninstall", spec.root, packages, None):

pybm/builders/util.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from pybm.exceptions import BuilderError
66
from pybm.specs import PythonSpec
77
from pybm.util.common import version_tuple
8-
from pybm.util.path import get_subdirs, list_contents
8+
from pybm.util.path import get_subdirs, get_filenames
99
from pybm.util.subprocess import run_subprocess
1010

1111

@@ -31,6 +31,7 @@ def get_executable(root: Union[str, Path]) -> str:
3131

3232
def is_valid_venv(path: Union[str, Path], verbose: bool = False) -> bool:
3333
"""Check if a directory is a valid virtual environment."""
34+
3435
subdir_set = set(get_subdirs(path=path))
3536
if os.name != "nt":
3637
exec_set, bin_folder = {"pip", "python"}, "bin"
@@ -69,7 +70,7 @@ def is_valid_venv(path: Union[str, Path], verbose: bool = False) -> bool:
6970
end="",
7071
)
7172

72-
actual_set = set(list_contents(bin_dir, names_only=True))
73+
actual_set = set(get_filenames(bin_dir))
7374

7475
if not exec_set <= actual_set:
7576
if verbose:

pybm/commands/compare.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
from pybm.config import get_component_class
66
from pybm.reporters import BaseReporter
77
from pybm.status_codes import ERROR, SUCCESS
8-
from pybm.util.path import get_subdirs
98

109

1110
class CompareCommand(CLICommand):
@@ -73,16 +72,13 @@ def run(self, args: List[str]) -> int:
7372
reporter: BaseReporter = get_component_class("reporter", config=self.config)
7473

7574
refs: List[str] = options.refs
76-
n_previous: int = options.include_previous
77-
report_absolutes: bool = options.absolute
78-
79-
result_dir = reporter.result_dir
80-
results = sorted(get_subdirs(result_dir), key=int)[-n_previous:]
75+
previous: int = options.include_previous
76+
absolute: bool = options.absolute
8177

8278
reporter.compare(
8379
*refs,
84-
results=results,
85-
report_absolutes=report_absolutes,
80+
absolute=absolute,
81+
previous=previous,
8682
target_filter=options.target_filter,
8783
benchmark_filter=options.benchmark_filter,
8884
context_filter=options.context_filter,

pybm/commands/env.py

Lines changed: 49 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
import argparse
2-
from contextlib import ExitStack
32
from typing import List, Callable, Mapping, Optional
43

54
from pybm.builders import BaseBuilder
65
from pybm.command import CLICommand
76
from pybm.config import PybmConfig, get_component_class
87
from pybm.env_store import EnvironmentStore
9-
from pybm.exceptions import PybmError
108
from pybm.logging import get_logger
119
from pybm.status_codes import ERROR, SUCCESS
1210

@@ -27,7 +25,7 @@ class EnvCommand(CLICommand):
2725
" or: pybm env install <identifier> <packages> [<options>]\n"
2826
" or: pybm env uninstall <identifier> <packages> [<options>]\n"
2927
" or: pybm env list\n"
30-
" or: pybm env update <env> <attr> <value>\n"
28+
" or: pybm env switch <env> <ref>\n"
3129
)
3230

3331
def __init__(self):
@@ -93,12 +91,12 @@ def add_arguments(self, subcommand: str = None):
9391
"environment. Raises an error if the path does not exist or is not "
9492
"recognized as a valid Python virtual environment.",
9593
)
96-
elif subcommand in ["delete", "install", "uninstall"]:
94+
elif subcommand == "delete":
9795
self.parser.add_argument(
9896
"identifier",
9997
metavar="<id>",
100-
help="Information that uniquely identifies the environment. "
101-
"Can be name, checked out commit/branch/tag, or worktree directory.",
98+
help="Information that uniquely identifies the environment. Can be "
99+
"name, checked out commit/branch/tag, or worktree directory.",
102100
)
103101
if subcommand == "delete":
104102
self.parser.add_argument(
@@ -107,19 +105,35 @@ def add_arguments(self, subcommand: str = None):
107105
action="store_true",
108106
help="Force worktree removal, including untracked files.",
109107
)
110-
else:
111-
self.parser.add_argument(
112-
"packages",
113-
nargs="*",
114-
default=None,
115-
metavar="<packages>",
116-
help="Package dependencies to install into the new virtual "
117-
"environment.",
118-
)
108+
elif subcommand in ["install", "uninstall"]:
109+
self.parser.add_argument(
110+
"name",
111+
metavar="<name>",
112+
help="Information that uniquely identifies the environment. Can be "
113+
"name, checked out (partial) commit/branch/tag, or worktree directory.",
114+
)
115+
self.parser.add_argument(
116+
"packages",
117+
nargs="*",
118+
default=None,
119+
metavar="<packages>",
120+
help="Package dependencies to install into / uninstall from the new "
121+
"virtual environment.",
122+
)
119123
elif subcommand == "list":
120124
pass
121-
elif subcommand == "update":
122-
pass
125+
elif subcommand == "switch":
126+
self.parser.add_argument(
127+
"name",
128+
metavar="<name>",
129+
help="Name of the benchmark environment to switch checkout for.",
130+
)
131+
self.parser.add_argument(
132+
"ref",
133+
metavar="<ref>",
134+
help="New git reference to check out in the chosen environment. Can "
135+
"be a branch name, tag, or (partial) commit SHA.",
136+
)
123137

124138
assert subcommand is not None, "no valid subcommand specified"
125139

@@ -134,7 +148,7 @@ def add_arguments(self, subcommand: str = None):
134148
)
135149
builder_group = self.parser.add_argument_group(builder_group_desc)
136150

137-
# add builder-specific options into the group
151+
# add builder-specific options
138152
for arg in builder_args:
139153
builder_group.add_argument(arg.pop("flags"), **arg)
140154

@@ -191,53 +205,33 @@ def install(self, options: argparse.Namespace):
191205
verbose: bool = option_dict.pop("verbose")
192206

193207
# env name / git worktree info
194-
identifier: str = option_dict.pop("identifier")
208+
info: str = option_dict.pop("identifier")
195209

196-
# builder arguments
197210
packages: Optional[List[str]] = option_dict.pop("packages")
198211

199-
builder: BaseBuilder = get_component_class("builder", config=self.config)
200-
201212
env_store = EnvironmentStore(config=self.config, verbose=verbose)
202213

203-
target_env = env_store.get(identifier)
204-
205-
with ExitStack() as ctx:
206-
ctx.callback(env_store.save)
207-
builder.install(
208-
spec=target_env.python,
209-
packages=packages,
210-
verbose=verbose,
211-
**option_dict,
212-
)
214+
env_store.install(info=info, packages=packages, verbose=verbose, **option_dict)
213215

214216
return SUCCESS
215217

216218
def uninstall(self, options: argparse.Namespace):
217219
option_dict = vars(options)
218220

219-
# verbosity
220221
verbose: bool = option_dict.pop("verbose")
221222

222-
identifier: str = option_dict.pop("identifier")
223+
info: str = option_dict.pop("identifier")
223224

224-
# builder arguments
225225
packages: List[str] = option_dict.pop("packages")
226226

227-
builder: BaseBuilder = get_component_class("builder", config=self.config)
228-
229227
env_store = EnvironmentStore(config=self.config, verbose=verbose)
230228

231-
target_env = env_store.get(identifier)
232-
233-
with ExitStack() as ctx:
234-
ctx.callback(env_store.save)
235-
builder.uninstall(
236-
spec=target_env.python,
237-
packages=packages,
238-
verbose=verbose,
239-
**option_dict,
240-
)
229+
env_store.uninstall(
230+
info=info,
231+
packages=packages,
232+
verbose=verbose,
233+
**option_dict,
234+
)
241235

242236
return SUCCESS
243237

@@ -249,8 +243,13 @@ def list(self, options: argparse.Namespace):
249243

250244
return SUCCESS
251245

252-
def update(self, options: argparse.Namespace):
253-
raise PybmError("env updating is not implemented yet.")
246+
def switch(self, options: argparse.Namespace):
247+
name: str = options.name
248+
ref: str = options.ref
249+
verbose: bool = options.verbose
250+
251+
env_store = EnvironmentStore(config=self.config, verbose=verbose)
252+
env_store.switch(name=name, ref=ref)
254253

255254
def run(self, args: List[str]):
256255
logger.debug(f"Running command: `{self.format_call(args)}`")
@@ -261,7 +260,7 @@ def run(self, args: List[str]):
261260
"install": self.install,
262261
"uninstall": self.uninstall,
263262
"list": self.list,
264-
"update": self.update,
263+
"switch": self.switch,
265264
}
266265

267266
if not args or args[0] not in subcommand_handlers:

pybm/commands/init.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from pybm.exceptions import PybmError
88
from pybm.status_codes import SUCCESS
99
from pybm.util.git import is_main_worktree
10-
from pybm.util.path import list_contents
10+
from pybm.util.path import get_filenames
1111

1212

1313
class InitCommand(CLICommand):
@@ -93,7 +93,7 @@ def run(self, args: List[str]) -> int:
9393
config_dir.mkdir(parents=False, exist_ok=True)
9494

9595
if options.remove_existing:
96-
for p in list_contents(config_dir, file_suffix=".toml", names_only=True):
96+
for p in get_filenames(config_dir, file_ext=".toml"):
9797
(config_dir / p).unlink()
9898

9999
if Path(LOCAL_CONFIG).exists():

pybm/commands/run.py

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import json
21
import warnings
32
from pathlib import Path
43
from typing import List, Optional
@@ -7,8 +6,9 @@
76
from pybm.config import PybmConfig, get_component_class
87
from pybm.env_store import EnvironmentStore
98
from pybm.exceptions import PybmError
9+
from pybm.reporters import BaseReporter
1010
from pybm.runners import BaseRunner
11-
from pybm.runners.util import create_subdir, create_rundir, discover_targets
11+
from pybm.runners.util import discover_targets
1212
from pybm.status_codes import SUCCESS, ERROR
1313

1414

@@ -126,6 +126,7 @@ def run(self, args: List[str]) -> int:
126126
runner_options = vars(options)
127127

128128
runner: BaseRunner = get_component_class("runner", config=self.config)
129+
reporter: BaseReporter = get_component_class("reporter", config=self.config)
129130

130131
verbose: bool = runner_options.pop("verbose")
131132

@@ -142,8 +143,6 @@ def run(self, args: List[str]) -> int:
142143
benchmark_context = runner_options.pop("benchmark_context")
143144
# at this point, runner_options only include the additional runner kwargs
144145

145-
result_dir = create_rundir(runner.result_dir)
146-
147146
if source_path.is_absolute():
148147
raise PybmError(
149148
f"Benchmark path {source_path!r} was given in absolute form. Please "
@@ -193,8 +192,6 @@ def run(self, args: List[str]) -> int:
193192
worktree = environment.worktree
194193
ref, ref_type = worktree.get_ref_and_type()
195194

196-
subdir = create_subdir(result_dir=result_dir, worktree=worktree)
197-
198195
runner.check_required_packages(environment=environment)
199196

200197
with discover_targets(
@@ -241,23 +238,17 @@ def run(self, args: List[str]) -> int:
241238

242239
if rc != 0:
243240
raise PybmError(
244-
"Something went wrong during the benchmark. stderr output "
241+
"Something went wrong during the benchmark. Stderr output "
245242
f"of the dispatched subprocess:\n{data}"
246243
)
247244
elif not data:
248245
raise PybmError(
249246
"No result data was obtained from the dispatched "
250247
"benchmark subprocess. Please check that the configured "
251-
"benchmark runner actually writes the results to stdout. "
252-
"If you are using the Google Benchmark runner, please "
253-
"adapt your benchmark files to use Google Benchmark."
248+
"benchmark runner actually writes the results to stdout."
254249
)
255250
else:
256-
# TODO: Switch this to a general IO Connector
257-
result_name = Path(benchmark).stem + "_results.json"
258-
result_file = subdir / result_name
259-
with open(result_file, "w") as res:
260-
json.dump(json.loads(data), res)
251+
reporter.write(ref, benchmark, data)
261252

262253
if checkout_mode:
263254
root_ref, root_type = root_checkout

pybm/config.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,5 +205,7 @@ def get_runner_requirements(config: PybmConfig) -> List[str]:
205205
"ms/msec, us/usec, and ns/nsec, where either spelling is admissible.",
206206
"significantdigits": "Number of significant digits to round floating point "
207207
"results to in console display.",
208+
"shalength": "Length of git SHA fragments to display in console output. "
209+
"Default value is 8, meaning the first eight hex digits are displayed.",
208210
},
209211
}

0 commit comments

Comments
 (0)