papi: improve unit testability
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>
diff --git a/src/vpp-api/python/vpp_papi/tests/test_vpp_papi.py b/src/vpp-api/python/vpp_papi/tests/test_vpp_papi.py
index 774b4e1..d0c7249 100644
--- a/src/vpp-api/python/vpp_papi/tests/test_vpp_papi.py
+++ b/src/vpp-api/python/vpp_papi/tests/test_vpp_papi.py
@@ -14,8 +14,12 @@
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 @@
# 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)
+
diff --git a/src/vpp-api/python/vpp_papi/vpp_papi.py b/src/vpp-api/python/vpp_papi/vpp_papi.py
index 1921687..1b4df06 100644
--- a/src/vpp-api/python/vpp_papi/vpp_papi.py
+++ b/src/vpp-api/python/vpp_papi/vpp_papi.py
@@ -33,6 +33,19 @@
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 @@
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 @@
@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 @@
.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 @@
# 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 = []
diff --git a/src/vpp-api/python/vpp_papi/vpp_transport_shmem.py b/src/vpp-api/python/vpp_papi/vpp_transport_shmem.py
index fa8943f..a7ba26b 100644
--- a/src/vpp-api/python/vpp_papi/vpp_transport_shmem.py
+++ b/src/vpp-api/python/vpp_papi/vpp_transport_shmem.py
@@ -29,8 +29,12 @@
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)")