DPDK CI discussions
 help / color / mirror / Atom feed
* [dpdk-ci] [PATCH 1/2] patch-tagging: Added tagging script for patches
@ 2021-02-05 20:37 ohilyard
  2021-02-05 20:37 ` [dpdk-ci] [PATCH 2/2] patch-tagging: Added execution file creator ohilyard
  0 siblings, 1 reply; 2+ messages in thread
From: ohilyard @ 2021-02-05 20:37 UTC (permalink / raw)
  To: ci; +Cc: thomas, Owen Hilyard

From: Owen Hilyard <ohilyard@iol.unh.edu>

Relevant to Bug 511

This commit contains a script, patch_parser.py, and a config file,
patch_parser.cfg. These are tooling that the UNH CI team has been
testing in order to reduce the number of tests that need to be run
per patch. This resulted from our push to increase the number of
functional tests running in the CI. While working on expanding test
coverage, we found that DTS could easily take over 6 hours to run, so
we decided to begin work on tagging patches and then only running the
required tests.

The script works by taking in an address for the config file and then
a list of patch files, which it will parse and then produce a list of
tags for that list of patches based on the config file. The config file
is designed to work as a mapping for a base path to a set of tags. It
also contains an ordered list of priorities for tags so that this may
also be used by hierarchical tools rather than modular ones.

The intention of the UNH team with giving this tooling to the wider
DPDK community is to have people more familiar with the internal
functionality of DPDK provide most of the tagging. This would allow
UNH to have a better turn around time for testing by eliminating
unnecessary tests, while still increasing the number of tests in the
CI.

The different patch tags are currently defined as such:

core:
    Core DPDK functionality. Examples include kernel modules and
    librte_eal. This tag should be used sparingly as it is intended
     to signal to automated test suites that it is necessary to
     run most of the tests for DPDK and as such will consume CI
     resources for a long period of time.

driver:
    For NIC drivers and other hardware interface code. This should be
    used as a generic tag with each driver getting it's own tag.

application:
    Used in a similar manner to "driver". This tag is intended for
    code used in only in applications that DPDK provides, such as
    testpmd or helloworld. This tag should be accompanied by a tag
    which denotes which application specifically has been changed.

documentation:
    This is intended to be used as a tag for paths which only contain
    documentation, such as "doc/". It's intended use is as a way to
    trigger the automatic re-building of the documentation website.

Signed-off-by: Owen Hilyard <ohilyard@iol.unh.edu>
---
 config/patch_parser.cfg |  25 ++++++++++
 tools/patch_parser.py   | 100 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 125 insertions(+)
 create mode 100644 config/patch_parser.cfg
 create mode 100755 tools/patch_parser.py

diff --git a/config/patch_parser.cfg b/config/patch_parser.cfg
new file mode 100644
index 0000000..5757f9a
--- /dev/null
+++ b/config/patch_parser.cfg
@@ -0,0 +1,25 @@
+# Description of the categories as initially designed
+
+[Paths]
+drivers =
+    driver,
+    core
+kernel = core
+doc = documentation
+lib = core
+meson_options.txt = core
+examples = application
+app = application
+license = documentation
+VERSION = documentation
+build = core
+
+# This is an ordered list of the importance of each patch classification.
+# It should be used to determine which classification to use on tools which
+# do not support multiple patch classifications.
+[Priority]
+priority_list =
+    core,
+    driver,
+    application,
+    documentation
diff --git a/tools/patch_parser.py b/tools/patch_parser.py
new file mode 100755
index 0000000..92d50dd
--- /dev/null
+++ b/tools/patch_parser.py
@@ -0,0 +1,100 @@
+#!/usr/bin/env python3
+
+# BSD LICENSE
+#
+# Copyright(c) 2020 Intel Corporation. All rights reserved.
+# Copyright © 2018[, 2019] The University of New Hampshire. All rights reserved.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+#   * Redistributions of source code must retain the above copyright
+#     notice, this list of conditions and the following disclaimer.
+#   * Redistributions in binary form must reproduce the above copyright
+#     notice, this list of conditions and the following disclaimer in
+#     the documentation and/or other materials provided with the
+#     distribution.
+#   * Neither the name of Intel Corporation nor the names of its
+#     contributors may be used to endorse or promote products derived
+#     from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+import subprocess
+import sys
+
+import itertools
+from configparser import ConfigParser
+from typing import List, Dict, Set
+import argparse
+
+try:
+    import whatthepatch
+except ImportError as e:
+    print("Please install whatthepatch, a patch parsing library", file=sys.stderr)
+    exit(1)
+
+
+def get_changed_files_in_patch(patch_file: str) -> List[str]:
+    with open(patch_file, 'r') as f:
+        filenames = map(lambda diff: diff.header.new_path, whatthepatch.parse_patch(f.read()))
+        return list(filenames)
+
+
+def get_all_files_from_patches(patch_files: List[str]) -> Set[str]:
+    return set(itertools.chain.from_iterable(map(get_changed_files_in_patch, patch_files)))
+
+
+def parse_comma_delimited_list_from_string(mod_str: str) -> List[str]:
+    return list(map(str.strip, mod_str.split(',')))
+
+
+def get_dictionary_attributes_from_config_file(conf_obj: ConfigParser) -> Dict[str, Set[str]]:
+    return {
+        directory: parse_comma_delimited_list_from_string(module_string) for directory, module_string in
+        conf_obj['Paths'].items()
+    }
+
+
+def get_tags_for_patch_file(patch_file: str, dir_attrs: Dict[str, Set[str]]) -> Set[str]:
+    return set(itertools.chain.from_iterable(
+        tags for directory, tags in dir_attrs.items() if patch_file.startswith(directory)
+    ))
+
+
+def get_tags_for_patches(patch_files: Set[str], dir_attrs: Dict[str, Set[str]]) -> Set[str]:
+    return set(itertools.chain.from_iterable(
+        map(lambda patch_file: get_tags_for_patch_file(patch_file, dir_attrs), patch_files)
+    ))
+
+
+if __name__ == '__main__':
+    parser = argparse.ArgumentParser(
+        description='Takes a patch file and a config file and creates a list of tags for that patch')
+    parser.add_argument('config_file_path', help='The path to patch_parser.cfg', default='config/patch_parser.cfg')
+    parser.add_argument('patch_file_paths', help='A list of patch files', type=str, metavar='patch file', nargs='+')
+
+    args = parser.parse_args()
+
+    conf_obj = ConfigParser()
+    conf_obj.read(args.config_file_path)
+
+    patch_files = get_all_files_from_patches(args.patch_file_paths)
+    dir_attrs = get_dictionary_attributes_from_config_file(conf_obj)
+    priority_list = parse_comma_delimited_list_from_string(conf_obj['Priority']['priority_list'])
+
+    unordered_tags: Set[str] = get_tags_for_patches(patch_files, dir_attrs)
+    ordered_tags: List[str] = [tag for tag in priority_list if tag in unordered_tags]
+
+    print("\n".join(ordered_tags))
-- 
2.27.0


^ permalink raw reply	[flat|nested] 2+ messages in thread

* [dpdk-ci] [PATCH 2/2] patch-tagging: Added execution file creator
  2021-02-05 20:37 [dpdk-ci] [PATCH 1/2] patch-tagging: Added tagging script for patches ohilyard
@ 2021-02-05 20:37 ` ohilyard
  0 siblings, 0 replies; 2+ messages in thread
From: ohilyard @ 2021-02-05 20:37 UTC (permalink / raw)
  To: ci; +Cc: thomas, Owen Hilyard

From: Owen Hilyard <ohilyard@iol.unh.edu>

The new config file contains mappings from a tag to a list of tests to be run. This will be where the community can configure which test suites should run for which tag. Additional tags can be created freely, since the script just performs a dictionary lookup.

The script uses the provided execution file for 2 things. First, uses it to provide all of the other fields in all of the configs. This is to allow preserving configuration information for dts. The otehr way the scripts are used is that the lists of test suites already there is treated as a filter. This was done to allow a CI implimentation to only run test suites that they have verified are functional on their hardware. The resulting execution file will have a "test_suites" field which is the lexographically sorted intersection between this "filter" list and the tests that are configured to run for the provided tags. The script will also append ":func=true:perf=false" or ":func=false:perf=true" onto the parameters as appropriate.

Signed-off-by: Owen Hilyard <ohilyard@iol.unh.edu>
---
 config/tests_for_tag.cfg                     |  33 ++++++
 tools/create_new_execution_file_from_tags.py | 113 +++++++++++++++++++
 2 files changed, 146 insertions(+)
 create mode 100644 config/tests_for_tag.cfg
 create mode 100755 tools/create_new_execution_file_from_tags.py

diff --git a/config/tests_for_tag.cfg b/config/tests_for_tag.cfg
new file mode 100644
index 0000000..01c497d
--- /dev/null
+++ b/config/tests_for_tag.cfg
@@ -0,0 +1,33 @@
+[functional]
+core = dynamic_config,
+       link_status_interrupt,
+       mac_filter,
+       mtu_update,
+       scatter,
+       stats_checks,
+       stats_checks,
+       unit_tests_mbuf
+driver = dynamic_config,
+       link_status_interrupt,
+       mac_filter,
+       mtu_update,
+       scatter,
+       stats_checks,
+       stats_checks,
+       unit_tests_mbuf
+application = dynamic_config,
+       link_status_interrupt,
+       mac_filter,
+       mtu_update,
+       scatter,
+       stats_checks,
+       stats_checks,
+       unit_tests_mbuf
+; Nothing in documentation
+documentation =
+
+[performance]
+core = nic_single_core_perf
+driver = nic_single_core_perf
+application = nic_single_core_perf
+documentation =
diff --git a/tools/create_new_execution_file_from_tags.py b/tools/create_new_execution_file_from_tags.py
new file mode 100755
index 0000000..af24615
--- /dev/null
+++ b/tools/create_new_execution_file_from_tags.py
@@ -0,0 +1,113 @@
+#!/usr/bin/env python3
+
+# BSD LICENSE
+#
+# Copyright(c) 2020 Intel Corporation. All rights reserved.
+# Copyright © 2018[, 2019] The University of New Hampshire. All rights reserved.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+#   * Redistributions of source code must retain the above copyright
+#     notice, this list of conditions and the following disclaimer.
+#   * Redistributions in binary form must reproduce the above copyright
+#     notice, this list of conditions and the following disclaimer in
+#     the documentation and/or other materials provided with the
+#     distribution.
+#   * Neither the name of Intel Corporation nor the names of its
+#     contributors may be used to endorse or promote products derived
+#     from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+import sys
+from enum import Enum
+
+import itertools
+from configparser import ConfigParser
+from typing import List, Dict, Set
+import argparse
+
+
+def parse_comma_delimited_list_from_string(mod_str: str) -> List[str]:
+    return list(map(str.strip, mod_str.split(',')))
+
+
+def map_tags_to_tests(tags: List[str], test_map: Dict[str, List[str]]) -> Set[str]:
+    """
+    Returns a list that is the union of all of the map lookups.
+    """
+    try:
+        return set(
+            filter(lambda test: test != '', set(itertools.chain.from_iterable(map(lambda tag: test_map[tag], tags)))))
+    except KeyError as e:
+        print(f'Tag {e} is not present in tests_for_tag.cfg')
+        exit(1)
+
+class TestingType(Enum):
+    functional = 'functional'
+    performance = 'performance'
+
+    def __str__(self):
+        return self.value
+
+
+if __name__ == '__main__':
+    parser = argparse.ArgumentParser(
+        description='Take a template execution file and add the relevant tests'
+                    ' for the given tags to it, creating a new file.')
+    parser.add_argument('config_file_path', help='The path to tests_for_tag.cfg', default='config/tests_for_tag.cfg')
+    parser.add_argument('template_execution_file', help='The path to the execution file to use as a template')
+    parser.add_argument('output_path', help='The path to the output execution file')
+    parser.add_argument('testing_type', type=TestingType, choices=list(TestingType),
+                        help='What type of testing to create an execution file for')
+    parser.add_argument('tags', metavar='tag', type=str, nargs='+', help='The tags to create an execution file for.')
+
+    args = parser.parse_args()
+
+    tag_to_test_map_parser = ConfigParser()
+    tag_to_test_map_parser.read(args.config_file_path)
+
+    template_execution_file_parser = ConfigParser()
+    template_execution_file_parser.read(args.template_execution_file)
+
+    test_map = {key: parse_comma_delimited_list_from_string(value.strip()) for key, value in
+                tag_to_test_map_parser[str(args.testing_type)].items()}
+
+    tests = map_tags_to_tests(args.tags, test_map)
+
+    try:
+        output_file = open(args.output_path, 'x')
+    except FileExistsError:
+        output_file = open(args.output_path, 'w')
+
+    for execution_plan in template_execution_file_parser:
+        # The DEFAULT section is always present and contains top-level items, so it needs to be ignored
+        if execution_plan != 'DEFAULT':
+            test_allowlist = parse_comma_delimited_list_from_string(template_execution_file_parser[execution_plan]['test_suites'])
+            tests_to_run = list(set(test_allowlist).intersection(tests))
+            tests_to_run.sort()
+            template_execution_file_parser[execution_plan]['test_suites'] = ", ".join(tests_to_run)
+
+            if args.testing_type == TestingType.functional:
+                template_execution_file_parser[execution_plan]['parameters'] += ':func=true:perf=false'
+            elif args.testing_type == TestingType.performance:
+                template_execution_file_parser[execution_plan]['parameters'] += ':func=false:perf=true'
+            else:
+                # This code should be unreachable, since this is checked at the top of the file
+                print("Fatal error: testing type is neither performance nor functional", file=sys.stderr)
+                exit(1)
+
+    template_execution_file_parser.write(output_file)
+
-- 
2.27.0


^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2021-02-05 20:37 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-02-05 20:37 [dpdk-ci] [PATCH 1/2] patch-tagging: Added tagging script for patches ohilyard
2021-02-05 20:37 ` [dpdk-ci] [PATCH 2/2] patch-tagging: Added execution file creator ohilyard

DPDK CI discussions

This inbox may be cloned and mirrored by anyone:

	git clone --mirror http://inbox.dpdk.org/ci/0 ci/git/0.git

	# If you have public-inbox 1.1+ installed, you may
	# initialize and index your mirror using the following commands:
	public-inbox-init -V2 ci ci/ http://inbox.dpdk.org/ci \
		ci@dpdk.org
	public-inbox-index ci

Example config snippet for mirrors.
Newsgroup available over NNTP:
	nntp://inbox.dpdk.org/inbox.dpdk.ci


AGPL code for this site: git clone https://public-inbox.org/public-inbox.git