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>
This commit is contained in:
Paul Vinciguerra
2020-12-01 02:00:35 -05:00
committed by Ole Tr�an
parent 41f15ae1df
commit 46d6864b9d
3 changed files with 133 additions and 23 deletions

View File

@ -14,8 +14,12 @@
import ctypes import ctypes
import multiprocessing as mp import multiprocessing as mp
import sys
import unittest import unittest
from unittest import mock
from vpp_papi import vpp_papi from vpp_papi import vpp_papi
from vpp_papi import vpp_transport_shmem
class TestVppPapiVPPApiClient(unittest.TestCase): class TestVppPapiVPPApiClient(unittest.TestCase):
@ -51,3 +55,62 @@ class TestVppPapiVPPApiClientMp(unittest.TestCase):
# AssertionError: 11 != 1 # AssertionError: 11 != 1
self.assertEqual(11, c.get_context()) 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)

View File

@ -33,6 +33,19 @@ from . vpp_format import verify_enum_hint
from . vpp_serializer import VPPType, VPPEnumType, VPPUnionType from . vpp_serializer import VPPType, VPPEnumType, VPPUnionType
from . vpp_serializer import VPPMessage, vpp_get_type, VPPTypeAlias 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': if sys.version[0] == '2':
import Queue as queue import Queue as queue
else: else:
@ -218,7 +231,7 @@ class VPPApiJSONFiles(object):
return None return None
@classmethod @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 """Find API definition files from the given directory tree with the
given pattern. If no directory is given then find_api_dir() is used 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 to locate one. If no pattern is given then all definition files found
@ -260,21 +273,49 @@ class VPPApiJSONFiles(object):
@classmethod @classmethod
def process_json_file(self, apidef_file): def process_json_file(self, apidef_file):
api = json.load(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 = {} types = {}
services = {} services = {}
messages = {} messages = {}
for t in api['enums']: try:
t[0] = 'vl_api_' + t[0] + '_t' for t in api['enums']:
types[t[0]] = {'type': 'enum', 'data': t} t[0] = 'vl_api_' + t[0] + '_t'
for t in api['unions']: types[t[0]] = {'type': 'enum', 'data': t}
t[0] = 'vl_api_' + t[0] + '_t' except KeyError:
types[t[0]] = {'type': 'union', 'data': t} pass
for t in api['types']:
t[0] = 'vl_api_' + t[0] + '_t' try:
types[t[0]] = {'type': 'type', 'data': t} for t in api['unions']:
for t, v in api['aliases'].items(): t[0] = 'vl_api_' + t[0] + '_t'
types['vl_api_' + t + '_t'] = {'type': 'alias', 'data': v} types[t[0]] = {'type': 'union', 'data': t}
services.update(api['services']) 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 i = 0
while True: while True:
@ -309,13 +350,15 @@ class VPPApiJSONFiles(object):
.format(unresolved)) .format(unresolved))
types = unresolved types = unresolved
i += 1 i += 1
try:
for m in api['messages']: for m in api['messages']:
try: try:
messages[m[0]] = VPPMessage(m[0], m[1:]) messages[m[0]] = VPPMessage(m[0], m[1:])
except VPPNotImplementedError: except VPPNotImplementedError:
### OLE FIXME ### OLE FIXME
self.logger.error('Not implemented error for {}'.format(m[0])) logger.error('Not implemented error for {}'.format(m[0]))
except KeyError:
pass
return messages, services return messages, services
@ -389,7 +432,7 @@ class VPPApiClient(object):
# Pick up API definitions from default directory # Pick up API definitions from default directory
try: try:
apifiles = VPPApiJSONFiles.find_api_files(self.apidir) 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 # In test mode we don't care that we can't find the API files
if testmode: if testmode:
apifiles = [] apifiles = []

View File

@ -29,8 +29,12 @@ void vac_mem_init (size_t size);
vpp_object = None vpp_object = None
# Barfs on failure, no need to check success. # allow file to be imported so it can be mocked in tests.
vpp_api = ffi.dlopen('libvppapiclient.so') # 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)") @ffi.callback("void(unsigned char *, int)")