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:

committed by
Ole Tr�an

parent
41f15ae1df
commit
46d6864b9d
@ -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)
|
||||||
|
|
||||||
|
@ -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 = []
|
||||||
|
@ -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)")
|
||||||
|
Reference in New Issue
Block a user