papi: improve unit testability 08/30208/4
authorPaul Vinciguerra <pvinci@vinciconsulting.com>
Tue, 1 Dec 2020 07:00:35 +0000 (02:00 -0500)
committerOle Tr�an <otroan@employees.org>
Wed, 2 Dec 2020 10:11:38 +0000 (10:11 +0000)
refactor the code so that snippets of json can be used to test vpp_papi
example unit test provided

Type: improvement

Change-Id: Ibec608fd2e5b12515aa4db17d85d4319134c22ea
Signed-off-by: Paul Vinciguerra <pvinci@vinciconsulting.com>
src/vpp-api/python/vpp_papi/tests/test_vpp_papi.py
src/vpp-api/python/vpp_papi/vpp_papi.py
src/vpp-api/python/vpp_papi/vpp_transport_shmem.py

index 774b4e1..d0c7249 100644 (file)
 
 import ctypes
 import multiprocessing as mp
+import sys
 import unittest
+from unittest import mock
+
 from vpp_papi import vpp_papi
+from vpp_papi import vpp_transport_shmem
 
 
 class TestVppPapiVPPApiClient(unittest.TestCase):
@@ -51,3 +55,62 @@ class TestVppPapiVPPApiClientMp(unittest.TestCase):
         # AssertionError: 11 != 1
         self.assertEqual(11, c.get_context())
 
+
+class TestVppTypes(unittest.TestCase):
+    
+    def test_enum_from_json(self):
+        json_api = """\
+{
+    "enums": [
+
+        [
+            "address_family",
+            [
+                "ADDRESS_IP4",
+                0
+            ],
+            [
+                "ADDRESS_IP6",
+                1
+            ],
+            {
+                "enumtype": "u8"
+            }
+        ],
+        [
+            "if_type",
+            [
+                "IF_API_TYPE_HARDWARE",
+                0
+            ],
+            [
+                "IF_API_TYPE_SUB",
+                1
+            ],
+            [
+                "IF_API_TYPE_P2P",
+                2
+            ],
+            [
+                "IF_API_TYPE_PIPE",
+                3
+            ],
+            {
+                "enumtype": "u32"
+            }
+        ]
+    ]
+}
+"""
+        processor = vpp_papi.VPPApiJSONFiles()
+
+        # add the types to vpp_serializer
+        processor.process_json_str(json_api)
+
+        vpp_transport_shmem.VppTransport = mock.MagicMock()
+        ac = vpp_papi.VPPApiClient(apifiles=[], testmode=True)
+        type_name = "vl_api_if_type_t"
+        t = ac.get_type(type_name)
+        self.assertTrue(str(t).startswith("VPPEnumType"))
+        self.assertEqual(t.name, type_name)
+
index 1921687..1b4df06 100644 (file)
@@ -33,6 +33,19 @@ from . vpp_format import verify_enum_hint
 from . vpp_serializer import VPPType, VPPEnumType, VPPUnionType
 from . vpp_serializer import VPPMessage, vpp_get_type, VPPTypeAlias
 
+try:
+    import VppTransport
+except ModuleNotFoundError:
+    class V:
+        """placeholder for VppTransport as the implementation is dependent on
+        VPPAPIClient's initialization values
+        """
+
+    VppTransport = V
+
+logger = logging.getLogger(__name__)
+logger.addHandler(logging.NullHandler())
+
 if sys.version[0] == '2':
     import Queue as queue
 else:
@@ -218,7 +231,7 @@ class VPPApiJSONFiles(object):
         return None
 
     @classmethod
-    def find_api_files(cls, api_dir=None, patterns='*'):
+    def find_api_files(cls, api_dir=None, patterns='*'):  # -> list
         """Find API definition files from the given directory tree with the
         given pattern. If no directory is given then find_api_dir() is used
         to locate one. If no pattern is given then all definition files found
@@ -260,21 +273,49 @@ class VPPApiJSONFiles(object):
     @classmethod
     def process_json_file(self, apidef_file):
         api = json.load(apidef_file)
+        return self._process_json(api)
+
+    @classmethod
+    def process_json_str(self, json_str):
+        api = json.loads(json_str)
+        return self._process_json(api)
+
+    @staticmethod
+    def _process_json(api):  # -> Tuple[Dict, Dict]
         types = {}
         services = {}
         messages = {}
-        for t in api['enums']:
-            t[0] = 'vl_api_' + t[0] + '_t'
-            types[t[0]] = {'type': 'enum', 'data': t}
-        for t in api['unions']:
-            t[0] = 'vl_api_' + t[0] + '_t'
-            types[t[0]] = {'type': 'union', 'data': t}
-        for t in api['types']:
-            t[0] = 'vl_api_' + t[0] + '_t'
-            types[t[0]] = {'type': 'type', 'data': t}
-        for t, v in api['aliases'].items():
-            types['vl_api_' + t + '_t'] = {'type': 'alias', 'data': v}
-        services.update(api['services'])
+        try:
+            for t in api['enums']:
+                t[0] = 'vl_api_' + t[0] + '_t'
+                types[t[0]] = {'type': 'enum', 'data': t}
+        except KeyError:
+            pass
+
+        try:
+            for t in api['unions']:
+                t[0] = 'vl_api_' + t[0] + '_t'
+                types[t[0]] = {'type': 'union', 'data': t}
+        except KeyError:
+            pass
+
+        try:
+            for t in api['types']:
+                t[0] = 'vl_api_' + t[0] + '_t'
+                types[t[0]] = {'type': 'type', 'data': t}
+        except KeyError:
+            pass
+
+        try:
+            for t, v in api['aliases'].items():
+                types['vl_api_' + t + '_t'] = {'type': 'alias', 'data': v}
+        except KeyError:
+            pass
+
+        try:
+            services.update(api['services'])
+        except KeyError:
+            pass
 
         i = 0
         while True:
@@ -309,13 +350,15 @@ class VPPApiJSONFiles(object):
                                     .format(unresolved))
             types = unresolved
             i += 1
-
-        for m in api['messages']:
-            try:
-                messages[m[0]] = VPPMessage(m[0], m[1:])
-            except VPPNotImplementedError:
-                ### OLE FIXME
-                self.logger.error('Not implemented error for {}'.format(m[0]))
+        try:
+            for m in api['messages']:
+                try:
+                    messages[m[0]] = VPPMessage(m[0], m[1:])
+                except VPPNotImplementedError:
+                    ### OLE FIXME
+                    logger.error('Not implemented error for {}'.format(m[0]))
+        except KeyError:
+            pass
         return messages, services
 
 
@@ -389,7 +432,7 @@ class VPPApiClient(object):
             # Pick up API definitions from default directory
             try:
                 apifiles = VPPApiJSONFiles.find_api_files(self.apidir)
-            except RuntimeError:
+            except (RuntimeError, VPPApiError):
                 # In test mode we don't care that we can't find the API files
                 if testmode:
                     apifiles = []
index fa8943f..a7ba26b 100644 (file)
@@ -29,8 +29,12 @@ void vac_mem_init (size_t size);
 
 vpp_object = None
 
-# Barfs on failure, no need to check success.
-vpp_api = ffi.dlopen('libvppapiclient.so')
+# allow file to be imported so it can be mocked in tests.
+# If the shared library fails, VppTransport cannot be initialized.
+try:
+    vpp_api = ffi.dlopen('libvppapiclient.so')
+except OSError:
+    vpp_api = None
 
 
 @ffi.callback("void(unsigned char *, int)")