utils/getdeveloperlib.py: add runtime test parsing
authorVictor Huesca <victor.huesca@bootlin.com>
Sun, 4 Aug 2019 14:21:42 +0000 (16:21 +0200)
committerThomas Petazzoni <thomas.petazzoni@bootlin.com>
Sun, 4 Aug 2019 22:02:09 +0000 (00:02 +0200)
This patch extends the Developer class so that it associates each
developer with the runtime tests (in support/testing) is in
responsible for, according to the DEVELOPERS file.

The implementation relies on the unittest module to list all test
cases and does some manual parsing of these test-case objects to get
the actual list of test-cases per test-suite.

A global variable is used to compute the list of unittest only once.

This feature will allow to use the getdeveloperlib module to find
which developer is responsible for which runtime test, and send e-mail
notifications of runtime tests failures.

Signed-off-by: Victor Huesca <victor.huesca@bootlin.com>
Signed-off-by: Thomas Petazzoni <thomas.petazzoni@bootlin.com>
utils/getdeveloperlib.py

index 81eae7ee9ba019c11526610ab23de0538f650277..c0eff7df295e026f6698843cea5576256feaae58 100644 (file)
@@ -4,6 +4,7 @@ import re
 import glob
 import subprocess
 import sys
+import unittest
 
 #
 # Patch parsing functions
@@ -78,6 +79,35 @@ def analyze_patches(patches):
     return (allfiles, allinfras)
 
 
+# 
+# Unit-test parsing functions
+# 
+
+def get_all_test_cases(suite):
+    """Generate all test-cases from a given test-suite.
+    :return: (test.module, test.name)"""
+    if issubclass(type(suite), unittest.TestSuite):
+        for test in suite:
+            for res in get_all_test_cases(test):
+                yield res
+    else:
+        yield (suite.__module__, suite.__class__.__name__)
+
+
+def list_unittests(path):
+    """Use the unittest module to retreive all test cases from a given
+    directory"""
+    loader = unittest.TestLoader()
+    suite = loader.discover(path)
+    tests = {}
+    for module, test in get_all_test_cases(suite):
+        module_path = os.path.join(path, *module.split('.'))
+        tests.setdefault(module_path, []).append('%s.%s' % (module, test))
+    return tests
+
+
+unittests = {}
+
 #
 # DEVELOPERS file parsing functions
 #
@@ -89,6 +119,7 @@ class Developer:
         self.packages = parse_developer_packages(files)
         self.architectures = parse_developer_architectures(files)
         self.infras = parse_developer_infras(files)
+        self.runtime_tests = parse_developer_runtime_tests(files)
 
     def hasfile(self, f):
         f = os.path.abspath(f)
@@ -108,6 +139,8 @@ class Developer:
             things.append('{} archs'.format(len(self.architectures)))
         if len(self.infras):
             things.append('{} infras'.format(len(self.infras)))
+        if len(self.runtime_tests):
+            things.append('{} tests'.format(len(self.runtime_tests)))
         if things:
             return 'Developer <{} ({})>'.format(name, ', '.join(things))
         else:
@@ -170,12 +203,35 @@ def parse_developer_infras(fnames):
     return infras
 
 
+def parse_developer_runtime_tests(fnames):
+    """Given a list of file names, returns the runtime tests
+    corresponding to the file."""
+    all_files = []
+    # List all files recursively
+    for fname in fnames:
+        if os.path.isdir(fname):
+            for root, _dirs, files in os.walk(fname):
+                all_files += [os.path.join(root, f) for f in files]
+        else:
+            all_files.append(fname)
+
+    # Get all runtime tests
+    runtimes = set()
+    for f in all_files:
+        name = os.path.splitext(f)[0]
+        if name in unittests:
+            runtimes |= set(unittests[name])
+    return runtimes
+
+
 def parse_developers(basepath=None):
     """Parse the DEVELOPERS file and return a list of Developer objects."""
     developers = []
     linen = 0
     if basepath is None:
         basepath = os.getcwd()
+    global unittests
+    unittests = list_unittests(os.path.join(basepath, 'support/testing'))
     with open(os.path.join(basepath, "DEVELOPERS"), "r") as f:
         files = []
         name = None