-
Notifications
You must be signed in to change notification settings - Fork 3
Add tag-based test filtering #8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
1663f50
3bdb7ed
99999e8
2d3c4b4
eea288b
63fa6bb
6f820f0
df78970
e038c8b
bd21fdc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| #! /usr/bin/sh | ||
|
|
||
| export PYTHONPATH=$PYTHONPATH:./src | ||
| python3 -m dmtest $@ | ||
| python3 -m dmtest "$@" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,7 +10,9 @@ | |
| import dmtest.thin_migrate.register as thin_migrate_register | ||
| import dmtest.vdo.register as vdo_register | ||
| import dmtest.dependency_tracker as dep | ||
| import dmtest.config as config | ||
| import dmtest.test_filter as filter | ||
| import dmtest.tag_expression as tag_expr | ||
| from dmtest.utils import get_dmesg_log | ||
| import io | ||
| import itertools | ||
|
|
@@ -168,6 +170,10 @@ def cmd_list(tests: test_register.TestRegister, args, results: db.TestResults): | |
|
|
||
| for p in paths: | ||
| print(f"{formatter.tree_line(p)}", end=" ") | ||
| if args.show_tags: | ||
| tags = tests.get_tags(p) | ||
| tag_str = f"[{', '.join(sorted(tags))}]" if tags else "" | ||
| print(f"{tag_str.ljust(30)}", end=" ") | ||
| result = average_results(results.get_test_results(p, result_set, args.run_nr)) | ||
| if result is None: | ||
| print("-") | ||
|
|
@@ -430,6 +436,19 @@ def cmd_health(tests: test_register.TestRegister, args, results): | |
| print(f"{t.ljust(40, '.')} {found}") | ||
|
|
||
|
|
||
| # ----------------------------------------- | ||
| # 'list-tags' command | ||
|
|
||
|
|
||
| def cmd_list_tags(tests: test_register.TestRegister, args, results: db.TestResults): | ||
| tags = tests.count_tags() | ||
| if not tags: | ||
| print("No tags defined.") | ||
| return | ||
| for tag, count in sorted(tags.items()): | ||
|
lorelei-sakai marked this conversation as resolved.
|
||
| print(f" {tag.ljust(20)} {count} tests") | ||
|
|
||
|
|
||
| # ----------------------------------------- | ||
| # Command line parser | ||
|
|
||
|
|
@@ -460,6 +479,12 @@ def arg_filter(p): | |
| help="Select tests that match _all_ filters", | ||
| action="store_true", | ||
| ) | ||
| p.add_argument( | ||
| "--tags", | ||
| metavar="EXPRESSION", | ||
| type=str, | ||
| help="select tests matching the tag expression (e.g. '!experimental')", | ||
| ) | ||
|
|
||
|
|
||
| def build_filter(args): | ||
|
|
@@ -480,6 +505,22 @@ def build_filter(args): | |
| else: | ||
| top_filter.add_sub_filter(filter.StateFilter(s)) | ||
|
|
||
| # CLI --tags overrides config [filter].tags | ||
| tag_expression = getattr(args, "tags", None) | ||
| if tag_expression is None: | ||
| try: | ||
| cfg = config.read_config() | ||
| tag_expression = cfg.get("tags") | ||
| except (FileNotFoundError, ValueError): | ||
| pass | ||
|
|
||
| if tag_expression: | ||
| matcher = tag_expr.parse_tag_expression(tag_expression) | ||
| combined = filter.AndFilter() | ||
| combined.add_sub_filter(top_filter) | ||
| combined.add_sub_filter(filter.TagFilter(matcher)) | ||
| return combined | ||
|
|
||
| return top_filter | ||
|
|
||
|
|
||
|
|
@@ -537,6 +578,12 @@ def command_line_parser(): | |
| arg_filter(list_p) | ||
| arg_result_set(list_p) | ||
| arg_run_nr(list_p) | ||
| list_p.add_argument( | ||
| "-T", | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it worth adding a long form of this argument too, like
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I initially chose
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The verb is kind of redundant, yeah. The longer tag could be just
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
pytest's |
||
| help="Show tags alongside test names", | ||
| action="store_true", | ||
| dest="show_tags", | ||
| ) | ||
|
|
||
| log_p = subparsers.add_parser("log", help="list test logs") | ||
| log_p.set_defaults(func=cmd_log) | ||
|
|
@@ -588,6 +635,9 @@ def command_line_parser(): | |
| help="only show runs whose result matches the given state", | ||
| ) | ||
|
|
||
| list_tags_p = subparsers.add_parser("list-tags", help="list all tags with test counts") | ||
| list_tags_p.set_defaults(func=cmd_list_tags) | ||
|
|
||
| health_p = subparsers.add_parser( | ||
| "health", help="check required tools are installed" | ||
| ) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,27 +1,55 @@ | ||
| import toml | ||
|
|
||
|
|
||
| _CONFIG_KEYS = { | ||
| "metadata_dev": ("devices", None), | ||
| "data_dev": ("devices", None), | ||
| "cache_dev": ("devices", None), | ||
| "disable_by_id_check": ("devices", False), | ||
| "tags": ("filter", None), | ||
| "cache_policy": ("run", "smq"), | ||
| } | ||
|
|
||
|
|
||
| class Config: | ||
| def __init__(self, raw): | ||
| self._raw = raw | ||
|
|
||
| def get(self, key): | ||
| entry = _CONFIG_KEYS.get(key) | ||
| if entry is None: | ||
| raise KeyError(f"unknown config key: {key}") | ||
| section, default = entry | ||
| return self._raw.get(section, {}).get(key, default) | ||
|
|
||
|
|
||
| # Linux reordered my nvme drives once and I ran tests across | ||
| # /boot. This check tries to avoid that. The exception is | ||
| # virt devices, which don't seem to have an id. | ||
| def check_dev(cfg, name): | ||
| if cfg.get("disable_by_id_check", False): | ||
| return | ||
|
|
||
| value = cfg[name] | ||
| def check_dev(value, name): | ||
| if not ( | ||
| value.startswith("/dev/vd") or value.startswith("/dev/mapper/") | ||
| ) and not value.startswith("/dev/disk/by-id/"): | ||
| raise ValueError(f"config value '{name}' does not begin with /dev/disk/by-id") | ||
|
|
||
|
|
||
| def validate(cfg): | ||
| check_dev(cfg, "metadata_dev") | ||
| check_dev(cfg, "data_dev") | ||
| if "devices" not in cfg._raw: | ||
| raise ValueError( | ||
| "config.toml must have a [devices] section; " | ||
| "see config.toml.example for the expected format" | ||
| ) | ||
| for key in ("metadata_dev", "data_dev"): | ||
| if cfg.get(key) is None: | ||
| raise ValueError(f"config.toml: missing required key '{key}' in [devices]") | ||
| if not cfg.get("disable_by_id_check"): | ||
| check_dev(cfg.get("metadata_dev"), "metadata_dev") | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. check_dev here really doesn't operation on anything except the config, so it probably makes more sense as a function in the Config class. Possibly the entire validate() function should also be defined in the Config class as well, though that makes less difference in terms of code complexity.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point, I'll do that. |
||
| check_dev(cfg.get("data_dev"), "data_dev") | ||
|
|
||
|
|
||
| def read_config(path="config.toml"): | ||
| with open(path, "r") as f: | ||
| config = toml.load(f) | ||
| validate(config) | ||
| return config | ||
| raw = toml.load(f) | ||
| cfg = Config(raw) | ||
| validate(cfg) | ||
| return cfg | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not a big deal, probably but, I'm concerned about what this formatting will look like. tree_line() justifies everything so the test name will take up 50 columns. You're adding another 30 columns, so the results will necessarily show up past column 80 here, which is awkward if you're using an 80-character-wide terminal (like I do).
Another concern is that if there are three or four tags, they may take more than 30 columns to print.
Perhaps you could steal some columns from tree_line? Perhaps you could put the tags after the results columns? Perhaps you could deliberately add a second line for the tags of each test?
(Also tags here is guaranteed to be a frozenset I think, you shouldn't need the if/else.)