diff options
author | Mikhail Shchatko <mikhail.shchatko@mongodb.com> | 2021-07-22 10:36:16 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2021-07-22 14:25:55 +0000 |
commit | 73b4bcdabaddc1e2b5b59a19e0d7bae969a85669 (patch) | |
tree | ad9be2cc61d7fb1990f3d3b58caba2a94571af69 | |
parent | adecbaa346c6e8d8dbf292a3c08aa22b0350b62e (diff) | |
download | mongo-73b4bcdabaddc1e2b5b59a19e0d7bae969a85669.tar.gz |
SERVER-58613 Add list-tags subcommand to resmoke
-rw-r--r-- | buildscripts/resmokelib/run/__init__.py | 45 | ||||
-rw-r--r-- | buildscripts/resmokelib/run/list_tags.py | 66 | ||||
-rw-r--r-- | buildscripts/tests/resmokelib/run/__init__.py | 1 | ||||
-rw-r--r-- | buildscripts/tests/resmokelib/run/test_list_tags.py | 236 |
4 files changed, 347 insertions, 1 deletions
diff --git a/buildscripts/resmokelib/run/__init__.py b/buildscripts/resmokelib/run/__init__.py index ab624e9027b..ede083f8454 100644 --- a/buildscripts/resmokelib/run/__init__.py +++ b/buildscripts/resmokelib/run/__init__.py @@ -37,6 +37,7 @@ from buildscripts.resmokelib.core import process from buildscripts.resmokelib.core import jasper_process from buildscripts.resmokelib.plugin import PluginInterface, Subcommand from buildscripts.resmokelib.run import runtime_recorder +from buildscripts.resmokelib.run import list_tags from buildscripts.resmokelib.run.runtime_recorder import compare_start_time _INTERNAL_OPTIONS_TITLE = "Internal Options" @@ -121,6 +122,8 @@ class TestRunner(Subcommand): # pylint: disable=too-many-instance-attributes self.list_suites() elif self.__command == "find-suites": self.find_suites() + elif self.__command == "list-tags": + self.list_tags() elif config.DRY_RUN == "tests": self.dry_run() else: @@ -146,6 +149,34 @@ class TestRunner(Subcommand): # pylint: disable=too-many-instance-attributes self._resmoke_logger.info("%s will be run by the following suite(s): %s", test, suite_names) + def list_tags(self): + """List the tags and its documentation available in the suites.""" + tag_docs = {} + out_tag_names = [] + for suite_name in suitesconfig.get_named_suites(): + suite_file = config.NAMED_SUITES.get(suite_name, "") + tags_blocks = list_tags.get_tags_blocks(suite_file) + + for tags_block in tags_blocks: + splitted_tags_block = list_tags.split_into_tags(tags_block) + + for single_tag_block in splitted_tags_block: + tag_name, doc = list_tags.get_tag_doc(single_tag_block) + + if tag_name and (tag_name not in tag_docs + or len(doc) > len(tag_docs[tag_name])): + tag_docs[tag_name] = doc + + if suite_name in config.SUITE_FILES: # pylint: disable=unsupported-membership-test + out_tag_names.append(tag_name) + + if config.SUITE_FILES == [config.DEFAULTS["suite_files"]]: + out_tag_docs = tag_docs + else: + out_tag_docs = {tag: doc for tag, doc in tag_docs.items() if tag in out_tag_names} + + self._resmoke_logger.info("Found tags in suites:%s", list_tags.make_output(out_tag_docs)) + @staticmethod def _find_suites_by_test(suites): """ @@ -562,6 +593,7 @@ class RunPlugin(PluginInterface): RunPlugin._add_run(subparsers) RunPlugin._add_list_suites(subparsers) RunPlugin._add_find_suites(subparsers) + RunPlugin._add_list_tags(subparsers) def parse(self, subcommand, parser, parsed_args, **kwargs): """ @@ -573,7 +605,7 @@ class RunPlugin(PluginInterface): :param kwargs: additional args :return: None or a Subcommand """ - if subcommand in ('find-suites', 'list-suites', 'run'): + if subcommand in ('find-suites', 'list-suites', 'list-tags', 'run'): configure_resmoke.validate_and_update_config(parser, parsed_args) if config.EVERGREEN_TASK_ID is not None: return TestRunnerEvg(subcommand, **kwargs) @@ -1101,6 +1133,17 @@ class RunPlugin(PluginInterface): parser.add_argument("test_files", metavar="TEST_FILES", nargs="*", help="Explicit test files to run") + @classmethod + def _add_list_tags(cls, subparsers): + """Create and add the parser for the list-tags subcommand.""" + parser = subparsers.add_parser( + "list-tags", help="Lists the tags and their documentation available in the suites.") + parser.set_defaults(logger_file="console") + parser.add_argument( + "--suites", dest="suite_files", metavar="SUITE1,SUITE2", + help=("Comma separated list of suite names to get tags from." + " All suites are used if unspecified.")) + def to_local_args(input_args=None): # pylint: disable=too-many-branches,too-many-locals """ diff --git a/buildscripts/resmokelib/run/list_tags.py b/buildscripts/resmokelib/run/list_tags.py new file mode 100644 index 00000000000..05ea6725b83 --- /dev/null +++ b/buildscripts/resmokelib/run/list_tags.py @@ -0,0 +1,66 @@ +"""Functions to parse suite yaml files to get tags and comments around them.""" + +import textwrap +import re + + +def parse_tags_blocks(suite): + """Get substrings from the suite string where tags are defined.""" + yaml_tag_keys = ["include_with_any_tags", "exclude_with_any_tags"] + yaml_tag_keys_regex = "|".join(yaml_tag_keys) + all_tags_regex = re.compile(rf"(({yaml_tag_keys_regex}):\n(\s*(-|#)\s*.*)*)") + return [tag_block[0] for tag_block in all_tags_regex.findall(suite)] + + +def get_tags_blocks(suite_file): + """Get substrings from the suite file where tags are defined.""" + tags_blocks = [] + try: + with open(suite_file, "r") as fh: + tags_blocks = parse_tags_blocks(fh.read()) + except FileNotFoundError: + pass + return tags_blocks + + +def split_into_tags(tags_block): + """Split tag block into lines representing each tag.""" + tags_block_lines = tags_block.split("\n")[1:] + splitted_tags_block = [] + i = 0 + for line in tags_block_lines: + if len(splitted_tags_block) <= i: + splitted_tags_block.append([]) + line = line.strip() + splitted_tags_block[i].append(line) + if line.startswith("-"): + i += 1 + return splitted_tags_block + + +def get_tag_doc(single_tag_block): + """Get tag name with its documentation string.""" + tag_name = "" + doc = "" + for line in single_tag_block: + if line.startswith("#"): + doc += re.sub(r"^#+\s*", "", line).strip() + "\n" + elif line.startswith("-"): + if "#" in line: + tag_name, comment = line.split("#", 1) + tag_name = tag_name.replace("- ", "").strip() + doc += comment.strip() + else: + tag_name = line.replace("- ", "").strip() + doc = doc.strip() + return tag_name, doc + + +def make_output(tag_docs): + """Make output string.""" + output = "" + for tag, doc in sorted(tag_docs.items()): + newline = "\n" + wrapped_doc = textwrap.indent(doc, '\t') + output = f"{output}{newline}{tag}:{newline}{wrapped_doc}" + return output diff --git a/buildscripts/tests/resmokelib/run/__init__.py b/buildscripts/tests/resmokelib/run/__init__.py new file mode 100644 index 00000000000..4b7a2bb941b --- /dev/null +++ b/buildscripts/tests/resmokelib/run/__init__.py @@ -0,0 +1 @@ +"""Empty.""" diff --git a/buildscripts/tests/resmokelib/run/test_list_tags.py b/buildscripts/tests/resmokelib/run/test_list_tags.py new file mode 100644 index 00000000000..6649734c512 --- /dev/null +++ b/buildscripts/tests/resmokelib/run/test_list_tags.py @@ -0,0 +1,236 @@ +"""Unit tests for buildscripts/resmokelib/run/list_tags.py.""" +# pylint: disable=missing-docstring +import unittest + +from buildscripts.resmokelib.run import list_tags + + +def _get_suite(tags_blocks): + block = "" + for tags_block in tags_blocks: + block = f"{block} {tags_block}" + "\n" + return (("test_kind: js_test\n" + "\n" + "selector:\n" + " roots: []\n") + block + ("\n" + "executor:\n" + " config: {}\n" + " fixture:\n" + " class: MongoDFixture\n" + " mongod_options:\n" + " set_parameters:\n" + " enableTestCommands: 1\n")) + + +class TestParseTagsBlocks(unittest.TestCase): + def test_no_tags_blocks(self): + suite = _get_suite([]) + result = list_tags.parse_tags_blocks(suite) + self.assertCountEqual([], result) + + def test_empty_tags_block(self): + tags_blocks = ["exclude_with_any_tags:\n"] + suite = _get_suite(tags_blocks) + result = list_tags.parse_tags_blocks(suite) + self.assertCountEqual(tags_blocks, result) + + def test_two_tags_blocks(self): + tags_blocks = [("exclude_with_any_tags:\n" + " - dummy_tag_1\n" + " - dummy_tag_2\n" + " - dummy_tag_3"), + ("include_with_any_tags:\n" + " - dummy_tag_4\n" + " - dummy_tag_5\n" + " - dummy_tag_6")] + suite = _get_suite(tags_blocks) + result = list_tags.parse_tags_blocks(suite) + self.assertCountEqual(tags_blocks, result) + + def test_tags_block_with_tags_and_above_comments(self): + tags_blocks = [("exclude_with_any_tags:\n" + " # comment\n" + " - dummy_tag_1\n" + " # comment line 1\n" + " # comment line 2\n" + " - dummy_tag_2\n" + " #################\n" + " # fancy comment #\n" + " #################\n" + " - dummy_tag_3")] + suite = _get_suite(tags_blocks) + result = list_tags.parse_tags_blocks(suite) + self.assertCountEqual(tags_blocks, result) + + def test_tags_block_with_tags_and_inline_comments(self): + tags_blocks = [("exclude_with_any_tags:\n" + " - dummy_tag_1 # inline comment\n" + " - dummy_tag_2 # another one\n" + " - dummy_tag_3 # and another one")] + suite = _get_suite(tags_blocks) + result = list_tags.parse_tags_blocks(suite) + self.assertCountEqual(tags_blocks, result) + + def test_tags_block_with_tags_and_both_comments(self): + tags_blocks = [("exclude_with_any_tags:\n" + " # above comment\n" + " - dummy_tag_1 # inline comment\n" + " # above comment line 1\n" + " # above comment line 2\n" + " - dummy_tag_2 # another one inline\n" + " #######################\n" + " # above fancy comment #\n" + " #######################\n" + " - dummy_tag_3 # and another one inline")] + suite = _get_suite(tags_blocks) + result = list_tags.parse_tags_blocks(suite) + self.assertCountEqual(tags_blocks, result) + + +class TestSplitIntoTags(unittest.TestCase): + def test_empty_block(self): + result = list_tags.split_into_tags("") + self.assertCountEqual([], result) + + def test_block_with_no_tags(self): + block = "exclude_with_any_tags:\n" + result = list_tags.split_into_tags(block) + self.assertCountEqual([[""]], result) + + def test_block_with_tags_no_comments(self): + block = ("exclude_with_any_tags:\n" + " - dummy_tag_1\n" + " - dummy_tag_2\n" + " - dummy_tag_3") + expected = [ + ["- dummy_tag_1"], + ["- dummy_tag_2"], + ["- dummy_tag_3"], + ] + result = list_tags.split_into_tags(block) + self.assertCountEqual(expected, result) + + def test_block_with_tags_and_above_comments(self): + block = ("exclude_with_any_tags:\n" + " # comment\n" + " - dummy_tag_1\n" + " # comment line 1\n" + " # comment line 2\n" + " - dummy_tag_2\n" + " #################\n" + " # fancy comment #\n" + " #################\n" + " - dummy_tag_3") + expected = [ + [ + "# comment", + "- dummy_tag_1", + ], + [ + "# comment line 1", + "# comment line 2", + "- dummy_tag_2", + ], + [ + "#################", + "# fancy comment #", + "#################", + "- dummy_tag_3", + ], + ] + result = list_tags.split_into_tags(block) + self.assertCountEqual(expected, result) + + def test_block_with_tags_and_inline_comments(self): + block = (("exclude_with_any_tags:\n" + " - dummy_tag_1 # inline comment\n" + " - dummy_tag_2 # another one\n" + " - dummy_tag_3 # and another one")) + expected = [ + ["- dummy_tag_1 # inline comment"], + ["- dummy_tag_2 # another one"], + ["- dummy_tag_3 # and another one"], + ] + result = list_tags.split_into_tags(block) + self.assertCountEqual(expected, result) + + def test_block_with_tags_and_both_comments(self): + block = ("exclude_with_any_tags:\n" + " # above comment\n" + " - dummy_tag_1 # inline comment\n" + " # above comment line 1\n" + " # above comment line 2\n" + " - dummy_tag_2 # another one inline\n" + " #######################\n" + " # above fancy comment #\n" + " #######################\n" + " - dummy_tag_3 # and another one inline") + expected = [ + [ + "# above comment", + "- dummy_tag_1 # inline comment", + ], + [ + "# above comment line 1", + "# above comment line 2", + "- dummy_tag_2 # another one inline", + ], + [ + "#######################", + "# above fancy comment #", + "#######################", + "- dummy_tag_3 # and another one inline", + ], + ] + result = list_tags.split_into_tags(block) + self.assertCountEqual(expected, result) + + +class TestGetTagDoc(unittest.TestCase): + def test_empty_tag_block(self): + expected = ("", "") + result = list_tags.get_tag_doc("") + self.assertEqual(expected, result) + + def test_tag_no_comment(self): + tag_block = ["- dummy_tag"] + expected = ("dummy_tag", "") + result = list_tags.get_tag_doc(tag_block) + self.assertEqual(expected, result) + + def test_tag_with_above_comment(self): + tag_block = [ + "# comment", + "- dummy_tag", + ] + expected = ("dummy_tag", "comment") + result = list_tags.get_tag_doc(tag_block) + self.assertEqual(expected, result) + + def test_tag_with_multiline_above_comment(self): + tag_block = [ + "# comment line 1", + "# comment line 2", + "- dummy_tag", + ] + expected = ("dummy_tag", ("comment line 1\ncomment line 2")) + result = list_tags.get_tag_doc(tag_block) + self.assertEqual(expected, result) + + def test_tag_with_inline_comment(self): + tag_block = ["- dummy_tag # comment"] + expected = ("dummy_tag", "comment") + result = list_tags.get_tag_doc(tag_block) + self.assertEqual(expected, result) + + def test_tag_with_both_comment(self): + tag_block = [ + "# above comment line 1", + "# above comment line 2", + "- dummy_tag # inline comment", + ] + expected = ("dummy_tag", ("above comment line 1\n" + "above comment line 2\n" + "inline comment")) + result = list_tags.get_tag_doc(tag_block) + self.assertEqual(expected, result) |