Improve test tag string parsing
[csit.git] / resources / libraries / python / VppApiCrc.py
index cb6f5b0..8f2979e 100644 (file)
@@ -36,7 +36,7 @@ class VppApiCrcChecker(object):
     """Holder of data related to tracking VPP API CRCs.
 
     Both message names and crc hexa strings are tracked as
-    ordinary Python2 (bytes) str, so _str() is used when input is
+    ordinary Python2 (bytes) string, so _str() is used when input is
     possibly unicode or otherwise not safe.
 
     Each instance of this class starts with same default state,
@@ -67,14 +67,14 @@ class VppApiCrcChecker(object):
         Order of addition reflects the order colections should be queried.
         If an incompatible CRC is found, affected collections are removed.
         A CRC that would remove all does not, added to _reported instead,
-        while causing a failure in single test."""
+        while causing a failure in single test (if fail_on_mismatch)."""
 
         self._missing = dict()
         """Mapping from collection name to mapping from API name to CRC string.
 
         Starts the same as _expected, but each time an encountered api,crc pair
-        fits the expectation, the pair is removed from this mapping.
-        Ideally, the active mappings will become empty.
+        fits the expectation, the pair is removed from all collections
+        within this mapping. Ideally, the active mappings will become empty.
         If not, it is an error, VPP removed or renamed a message CSIT needs."""
 
         self._found = dict()
@@ -96,15 +96,16 @@ class VppApiCrcChecker(object):
         self._register_all()
         self._check_dir(directory)
 
-    def raise_or_log(self, exception):
-        """If fail_on_mismatch, raise, else log to console the exception.
+    def log_and_raise(self, exc_msg):
+        """Log to console, on fail_on_mismatch also raise runtime exception.
 
-        :param exception: The exception to raise or log.
-        :type exception: RuntimeError
+        :param exc_msg: The message to include in log or exception.
+        :type exception: str
+        :raises RuntimeError: With the message, if fail_on_mismatch.
         """
+        logger.console("RuntimeError:\n{m}".format(m=exc_msg))
         if self.fail_on_mismatch:
-            raise exception
-        logger.console("{exc!r}".format(exc=exception))
+            raise RuntimeError(exc_msg)
 
     def _register_collection(self, collection_name, name_to_crc_mapping):
         """Add a named (copy of) collection of CRCs.
@@ -113,11 +114,12 @@ class VppApiCrcChecker(object):
         :param name_to_crc_mapping: Mapping from API names to CRCs.
         :type collection_name: str or unicode
         :type name_to_crc_mapping: dict from str/unicode to str/unicode
+        :raises RuntimeError: If the name of a collection is registered already.
         """
         collection_name = _str(collection_name)
         if collection_name in self._expected:
-            raise RuntimeError("Collection {cl!r} already registered.".format(
-                cl=collection_name))
+            raise RuntimeError("Collection {cn!r} already registered.".format(
+                cn=collection_name))
         mapping = {_str(k): _str(v) for k, v in name_to_crc_mapping.items()}
         self._expected[collection_name] = mapping
         self._missing[collection_name] = mapping.copy()
@@ -172,6 +174,8 @@ class VppApiCrcChecker(object):
     def _process_crc(self, api_name, crc):
         """Compare API to verified collections, update class state.
 
+        Here, API stands for (message name, CRC) pair.
+
         Conflict is NOT when a collection does not recognize the API.
         Such APIs are merely added to _found for later reporting.
         Conflict is when a collection recognizes the API under a different CRC.
@@ -224,7 +228,7 @@ class VppApiCrcChecker(object):
         """Parse every .api.json found under directory, remember conflicts.
 
         As several collections are supported, each conflict invalidates
-        one of them, failure happens only when no collections would be left.
+        some of them, failure happens only when no collections would be left.
         In that case, set of collections just before the failure is preserved,
         the _reported mapping is filled with conflicting APIs.
         The _found mapping is filled with discovered api names and crcs.
@@ -258,6 +262,9 @@ class VppApiCrcChecker(object):
         Missing reporting is disabled by default, because some messages
         come from plugins that might not be enabled at runtime.
 
+        After the report, clear _reported, so that test cases report them again,
+        thus tracking which message is actually used (by which test).
+
         :param report_missing: Whether to raise on missing messages.
         :type report_missing: bool
         :raises RuntimeError: If CRC mismatch or missing messages are detected,
@@ -267,23 +274,28 @@ class VppApiCrcChecker(object):
             return
         self._initial_conflicts_reported = True
         if self._reported:
-            self.raise_or_log(
-                RuntimeError("Dir check found incompatible API CRCs: {rep!r}"\
-                    .format(rep=self._reported)))
+            reported_indented = json.dumps(
+                self._reported, indent=1, sort_keys=True, separators=[",", ":"])
+            self._reported = dict()
+            self.log_and_raise(
+                "Incompatible API CRCs found in .api.json files:\n{ri}".format(
+                    ri=reported_indented))
         if not report_missing:
             return
         missing = {name: mapp for name, mapp in self._missing.items() if mapp}
         if missing:
-            self.raise_or_log(
-                RuntimeError("Dir check found missing API CRCs: {mis!r}"\
-                    .format(mis=missing)))
+            missing_indented = json.dumps(
+                missing, indent=1, sort_keys=True, separators=[",", ":"])
+            self.log_and_raise("API CRCs missing from .api.json:\n{mi}".format(
+                mi=missing_indented))
 
     def check_api_name(self, api_name):
-        """Fail if the api_name has no known CRC associated.
+        """Fail if the api_name has no, or different from known CRC associated.
 
         Do not fail if this particular failure has been already reported.
 
-        Intended use: Call everytime an API call is queued or response received.
+        Intended use: Call during test (not in initialization),
+        everytime an API call is queued or response received.
 
         :param api_name: VPP API messagee name to check.
         :type api_name: str or unicode
@@ -302,9 +314,19 @@ class VppApiCrcChecker(object):
         if new_expected:
             # Some collections recognized the message name.
             self._expected = new_expected
-            return
         crc = self._found.get(api_name, None)
+        matching = False
+        if crc is not None:
+            # Regardless of how many collections are remaining,
+            # verify the known CRC is on one of them.
+            for name_to_crc_mapping in self._expected.values():
+                if api_name not in name_to_crc_mapping:
+                    continue
+                if name_to_crc_mapping[api_name] == crc:
+                    matching = True
+                    break
+        if matching:
+            return
         self._reported[api_name] = crc
-        self.raise_or_log(
-            RuntimeError("No active collection has API {api!r}"
-                         " CRC found {crc!r}".format(api=api_name, crc=crc)))
+        self.log_and_raise("No active collection contains API {api!r}"
+                           " with CRC {crc!r}".format(api=api_name, crc=crc))