Skip to content

Commit 01b1d81

Browse files
committed
add cryo_test testing framework
1 parent cbb214e commit 01b1d81

File tree

22 files changed

+1728
-0
lines changed

22 files changed

+1728
-0
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
2+
# cryo test
3+
4+
utility for performing comparisons between cryo environments
5+
6+
```bash
7+
cryo_test is a cli tool for comparing cryo outputs across different conditions
8+
9+
Usage: cryo_test QUERY [OPTIONS]
10+
11+
This will 1. setup comparison dir, 2. collect data, and 3. compare outputs
12+
13+
Examples:
14+
cryo_test --rpc source1=https://h1.com source2=https://h2.com (compare rpc's)
15+
cryo_test --executable old=/path/to/cryo1 new=/path/to/cryo2 (compare executables)
16+
cryo_test --python ... (use python)
17+
cryo_test --cli-vs-python ... (compare cli vs python)
18+
19+
Options:
20+
-h, --help show this help message and exit
21+
-e, --executable, --executables EXECUTABLE [...]
22+
executable(s) to use
23+
--rpc RPC [...] rpc endpoint(s) to use
24+
-d, --datatype, --datatypes DATATYPE [...]
25+
datatype(s) to collect
26+
-p, --python use python for all batches
27+
--cli-vs-python compare cli to python
28+
-r, --rerun rerun previous comparison
29+
--label LABEL name of comparison
30+
-s, --steps {setup,collect,compare} [...] steps to perform {setup, collect, compare}
31+
--dir DIR directory for storing comparison data
32+
-i, --interactive load data in interactive python session
33+
--scan-interactive scan data in interactive python session
34+
--debug, --pdb enter debug mode upon error
35+
```
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
"""library for performing comparisons between cryo jobs"""
2+
3+
from .comparison import perform_comparison
4+
5+
6+
__version__ = '0.1.0'
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from __future__ import annotations
2+
3+
from . import cli
4+
5+
6+
if __name__ == '__main__':
7+
cli.run_cli()
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from __future__ import annotations
2+
3+
from .cli_classes import *
4+
from .cli_run import *
5+
from .cli_summary import *
6+
from .cli_utils import *
7+
8+
9+
if __name__ == '__main__':
10+
run_cli()
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
from __future__ import annotations
2+
3+
import argparse
4+
import typing
5+
6+
import rich_argparse
7+
8+
if typing.TYPE_CHECKING:
9+
import typing_extensions
10+
11+
12+
usage_template = """%(prog)s [{prog}]QUERY [OPTIONS][/{prog}]\n\n
13+
14+
[{help} not bold]This will 1. setup comparison dir, 2. collect data, and 3. compare outputs[/{help} not bold]
15+
16+
[{groups}]Examples:[/{groups}]
17+
[{prog}]cryo_test --rpc source1=https://h1.com source2=https://h2.com[/{prog}] (compare rpc's)
18+
[{prog}]cryo_test --executable old=/path/to/cryo1 new=/path/to/cryo2[/{prog}] (compare executables)
19+
[{prog}]cryo_test --python ...[/{prog}] (use python)
20+
[{prog}]cryo_test --cli-vs-python ...[/{prog}] (compare cli vs python)"""
21+
22+
23+
class CryoTestHelpFormatter(rich_argparse.RichHelpFormatter):
24+
usage_markup = True
25+
26+
styles = {
27+
'argparse.prog': 'bold white',
28+
'argparse.groups': 'bold rgb(0,255,0)',
29+
'argparse.args': 'bold white',
30+
'argparse.metavar': 'grey62',
31+
'argparse.help': 'grey62',
32+
'argparse.text': 'blue',
33+
'argparse.syntax': 'blue',
34+
'argparse.default': 'blue',
35+
}
36+
37+
def __init__(self, prog: str) -> None:
38+
super().__init__('cryo_test', max_help_position=45)
39+
40+
def _format_args(self, action, default_metavar): # type: ignore
41+
get_metavar = self._metavar_formatter(action, default_metavar)
42+
if action.nargs == argparse.ZERO_OR_MORE:
43+
return '[%s [%s ...]]' % get_metavar(2)
44+
elif action.nargs == argparse.ONE_OR_MORE:
45+
return '%s [...]' % get_metavar(1)
46+
return super()._format_args(action, default_metavar)
47+
48+
def format_help(self) -> str:
49+
import rich
50+
51+
line = '[{prog}]cryo_test[/{prog}] [{help}]is a cli tool for comparing [{prog}]cryo[/{prog}] outputs across different conditions\n'
52+
rich.print(self.format_styles(line))
53+
54+
# indent certain arguments for full alignment
55+
raw = super().format_help()
56+
lines = raw.split('\n')
57+
for i, line in enumerate(lines):
58+
if line.startswith(' \x1b[38;2;197;149;242m--'):
59+
lines[i] = ' ' + lines[i].replace(' ', '', 1)
60+
61+
# indices = [i for i, line in enumerate(lines) if line.startswith(' \x1b[1;37m--')]
62+
# if lines[indices[0]] == '':
63+
# lines = lines[:indices[0]] + ['FUCK'] + lines[indices[0]:]
64+
65+
# lines = [
66+
# line
67+
# for line in lines
68+
# if 'Options:' not in line and '--help' not in line
69+
# ]
70+
return '\n'.join(lines).replace('\n\n\n', '\n\n')
71+
72+
@classmethod
73+
def format_styles(cls, s: str) -> str:
74+
styles = {k.split('.')[1]: v for k, v in cls.styles.items()}
75+
return s.format(**styles)
76+
77+
78+
class CryoTestArgParser(argparse.ArgumentParser):
79+
def __init__(self, *args: typing.Any, **kwargs: typing.Any) -> None:
80+
return super().__init__(
81+
*args,
82+
formatter_class=CryoTestHelpFormatter, # type: ignore
83+
usage=CryoTestHelpFormatter.format_styles(usage_template),
84+
**kwargs,
85+
)
86+
87+
def error(self, message: str) -> typing_extensions.NoReturn:
88+
import sys
89+
import rich
90+
91+
sys.stderr.write(f'Error: {message}\n')
92+
print()
93+
self.print_usage()
94+
print()
95+
line = '[{help}]show all options with[/{help}] [bold white]cryo_test -h[/bold white]'
96+
rich.print(CryoTestHelpFormatter.format_styles(line))
97+
sys.exit(0)
Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
from __future__ import annotations
2+
3+
import typing
4+
5+
from .. import commands
6+
from .. import comparison
7+
from .. import files
8+
from . import cli_classes
9+
from . import cli_summary
10+
from . import cli_utils
11+
12+
if typing.TYPE_CHECKING:
13+
import argparse
14+
15+
16+
def run_cli() -> None:
17+
try:
18+
# parse inputs
19+
args = parse_args()
20+
21+
if set(args.steps) in [
22+
{'compare'},
23+
{'collect'},
24+
{'collect', 'compare'},
25+
]:
26+
rerun = True
27+
else:
28+
rerun = args.rerun
29+
30+
# load commands
31+
if rerun:
32+
if args.dir is not None:
33+
comparison_dir = args.dir
34+
else:
35+
comparison_dir = files.get_most_recent_comparison_dir()
36+
batches = files.load_commands(comparison_dir)
37+
else:
38+
comparison_dir, batches = create_command_batches(args)
39+
files.save_commands(comparison_dir=comparison_dir, batches=batches)
40+
41+
# summarize
42+
cli_summary.summarize_args(args, comparison_dir, batches)
43+
44+
# perform comparison
45+
comparison.perform_comparison(
46+
batches=batches,
47+
comparison_dir=comparison_dir,
48+
steps=args.steps,
49+
)
50+
51+
# enter interactive session
52+
if args.interactive or args.scan_interactive:
53+
print('entering interactive session')
54+
if args.scan_interactive:
55+
all_data: typing.Any = files.scan_all_data(comparison_dir)
56+
else:
57+
all_data = files.load_all_data(comparison_dir)
58+
cli_utils.open_interactive_session({'data': all_data})
59+
except Exception as e:
60+
if args.debug:
61+
cli_utils._enter_debugger()
62+
else:
63+
raise e
64+
65+
66+
def parse_args() -> argparse.Namespace:
67+
parser = cli_classes.CryoTestArgParser()
68+
#
69+
# specifying batch args
70+
parser.add_argument(
71+
'-e',
72+
'--executable',
73+
'--executables',
74+
nargs='+',
75+
help='executable(s) to use',
76+
)
77+
parser.add_argument('--rpc', nargs='+', help='rpc endpoint(s) to use')
78+
parser.add_argument(
79+
'-d',
80+
'--datatype',
81+
'--datatypes',
82+
nargs='+',
83+
help='datatype(s) to collect',
84+
)
85+
parser.add_argument(
86+
'-p', '--python', action='store_true', help='use python for all batches'
87+
)
88+
parser.add_argument(
89+
'--cli-vs-python', action='store_true', help='compare cli to python'
90+
)
91+
#
92+
# specifying how to run
93+
parser.add_argument(
94+
'-r', '--rerun', action='store_true', help='rerun previous comparison'
95+
)
96+
parser.add_argument('--label', help='name of comparison')
97+
parser.add_argument(
98+
'-s',
99+
'--steps',
100+
help='steps to perform {setup, collect, compare}',
101+
nargs='+',
102+
choices=['setup', 'collect', 'compare'],
103+
default=['setup', 'collect', 'compare'],
104+
)
105+
parser.add_argument('--dir', help='directory for storing comparison data')
106+
parser.add_argument(
107+
'-i',
108+
'--interactive',
109+
action='store_true',
110+
help='load data in interactive python session',
111+
)
112+
parser.add_argument(
113+
'--scan-interactive',
114+
action='store_true',
115+
help='scan data in interactive python session',
116+
)
117+
parser.add_argument(
118+
'--debug',
119+
'--pdb',
120+
action='store_true',
121+
help='enter debug mode upon error',
122+
)
123+
return parser.parse_args()
124+
125+
126+
def create_command_batches(
127+
args: argparse.Namespace,
128+
) -> tuple[str, dict[str, dict[str, str]]]:
129+
command_arg_combos: dict[str, list[str]] = {}
130+
common_command_args: commands.PartialCommandArgs = {}
131+
batch_specific_args: dict[str, commands.PartialCommandArgs] = {}
132+
133+
# determine batch mode (the variables that change across batches)
134+
if args.executable is not None and len(args.executable) > 1:
135+
batch_mode = 'executable'
136+
elif args.rpc is not None and len(args.rpc) > 1:
137+
batch_mode = 'rpc'
138+
else:
139+
batch_mode = 'default'
140+
141+
# determine comparison dir
142+
comparison_dir = files.create_comparison_dir(args.dir, args.label)
143+
144+
# determine interface
145+
if args.python:
146+
common_command_args['interface'] = 'python'
147+
148+
#
149+
if batch_mode == 'default':
150+
batch_specific_args = {'default': {}}
151+
152+
# arg: executable
153+
if batch_mode == 'executable':
154+
for raw_executable in args.executable:
155+
batch_name, executable = raw_executable.split('=', maxsplit=1)
156+
batch_specific_args[batch_name] = {'executable': executable}
157+
else:
158+
if args.executable is None:
159+
common_command_args['executable'] = 'cryo'
160+
elif len(args.executable) == 1:
161+
common_command_args['executable'] = args.executable[0]
162+
else:
163+
raise Exception()
164+
165+
# arg: rpc
166+
if batch_mode == 'rpc':
167+
for raw_rpc in args.rpcs:
168+
batch_name, rpc = raw_rpc.split('=', maxsplits=1)
169+
batch_specific_args[batch_name] = {'rpc': rpc}
170+
else:
171+
if args.rpc is None:
172+
pass
173+
elif len(args.rpc) == 1:
174+
common_command_args['rpc'] = args.rpc[0]
175+
else:
176+
raise Exception()
177+
178+
# arg: datatypes
179+
if args.datatype is not None:
180+
command_arg_combos['datatype'] = args.datatype
181+
182+
# if cli-vs-python, create cli and python version of each batch
183+
if args.cli_vs_python:
184+
new_batch_specific_args = {}
185+
for batch_name, batch_kwargs in batch_specific_args.items():
186+
new_batch_specific_args[batch_name + '_cli'] = dict(
187+
batch_kwargs, interface='cli'
188+
)
189+
new_batch_specific_args[batch_name + '_python'] = dict(
190+
batch_kwargs, interface='python'
191+
)
192+
193+
# arg: output_dir
194+
for batch_name in batch_specific_args.keys():
195+
batch_specific_args[batch_name]['data_root'] = files.get_batch_data_dir(
196+
comparison_dir=comparison_dir,
197+
batch_name=batch_name,
198+
)
199+
200+
# generate commands
201+
batches = {}
202+
for batch_name in batch_specific_args.keys():
203+
kwargs: commands.PartialCommandArgs = dict(
204+
**common_command_args, **batch_specific_args[batch_name]
205+
)
206+
batches[batch_name] = commands.generate_commands(
207+
common_command_args=kwargs,
208+
command_arg_combos=command_arg_combos,
209+
)
210+
211+
return comparison_dir, batches
212+

0 commit comments

Comments
 (0)