Skip to content
Snippets Groups Projects
Commit 09358d62 authored by Jan David Mol's avatar Jan David Mol
Browse files

L2SS-412: Fixed tests for asyncio OPCUAConnection, and provide async and...

L2SS-412: Fixed tests for asyncio OPCUAConnection, and provide async and non-async version of key attribute_wrapper calls
parent 9d787815
No related branches found
No related tags found
1 merge request!142L2SS-412: Use asyncio for opcua and other clients
...@@ -146,7 +146,19 @@ class attribute_wrapper(attribute): ...@@ -146,7 +146,19 @@ class attribute_wrapper(attribute):
return value return value
async def set_comm_client(self, client): def set_comm_client(self, client):
"""
takes a communications client as input arguments This client should be of a class containing a "get_mapping" function
and return a read and write function that the wrapper will use to get/set data.
"""
try:
self.read_function, self.write_function = client.setup_attribute(self.comms_annotation, self)
except Exception as e:
logger.error("Exception while setting {} attribute with annotation: '{}' {}".format(client.__class__.__name__, self.comms_annotation, e))
raise Exception("Exception while setting %s attribute with annotation: '%s'", client.__class__.__name__, self.comms_annotation) from e
async def async_set_comm_client(self, client):
""" """
takes a communications client as input arguments This client should be of a class containing a "get_mapping" function takes a communications client as input arguments This client should be of a class containing a "get_mapping" function
and return a read and write function that the wrapper will use to get/set data. and return a read and write function that the wrapper will use to get/set data.
......
...@@ -3,6 +3,9 @@ import time ...@@ -3,6 +3,9 @@ import time
import asyncio import asyncio
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
import logging
logger = logging.getLogger()
class AbstractCommClient(ABC): class AbstractCommClient(ABC):
@abstractmethod @abstractmethod
def start(self): def start(self):
......
...@@ -98,8 +98,7 @@ class OPCUAConnection(AsyncCommClient): ...@@ -98,8 +98,7 @@ class OPCUAConnection(AsyncCommClient):
else: else:
raise TypeError(f"Namespace is not of type int or str, but of type {type(self.namespace).__name__}") raise TypeError(f"Namespace is not of type int or str, but of type {type(self.namespace).__name__}")
except Exception as e: except Exception as e:
self.streams.error_stream("Could not determine namespace index from namespace: %s: %s", namespace, e) raise ValueError("Could not determine namespace index from namespace %s", self.namespace) from e
raise Exception("Could not determine namespace index from namespace %s", namespace) from e
self.obj = self.client.get_objects_node() self.obj = self.client.get_objects_node()
......
...@@ -18,6 +18,8 @@ from devices.hardware_device import * ...@@ -18,6 +18,8 @@ from devices.hardware_device import *
from tango.test_context import DeviceTestContext from tango.test_context import DeviceTestContext
from test import base from test import base
import asyncio
scalar_dims = (1,) scalar_dims = (1,)
spectrum_dims = (4,) spectrum_dims = (4,)
image_dims = (3,2) image_dims = (3,2)
...@@ -31,7 +33,7 @@ def dev_init(device): ...@@ -31,7 +33,7 @@ def dev_init(device):
device.set_state(DevState.INIT) device.set_state(DevState.INIT)
device.test_client = test_client(device.Fault, device) device.test_client = test_client(device.Fault, device)
for i in device.attr_list(): for i in device.attr_list():
i.set_comm_client(device.test_client) asyncio.run(i.async_set_comm_client(device.test_client))
device.test_client.start() device.test_client.start()
...@@ -361,6 +363,9 @@ class TestAttributeTypes(base.TestCase): ...@@ -361,6 +363,9 @@ class TestAttributeTypes(base.TestCase):
def read_RW_test(self, dev, dtype, test_type): def read_RW_test(self, dev, dtype, test_type):
'''Test device''' '''Test device'''
expected = None
val = None
try: try:
with DeviceTestContext(dev, process=True) as proxy: with DeviceTestContext(dev, process=True) as proxy:
......
...@@ -91,7 +91,7 @@ class test_client(CommClient): ...@@ -91,7 +91,7 @@ class test_client(CommClient):
self.streams.debug_stream("created and bound example_client read/write functions to attribute_wrapper object") self.streams.debug_stream("created and bound example_client read/write functions to attribute_wrapper object")
return read_function, write_function return read_function, write_function
def setup_attribute(self, annotation=None, attribute=None): async def setup_attribute(self, annotation=None, attribute=None):
""" """
MANDATORY function: is used by the attribute wrapper to get read/write functions. MANDATORY function: is used by the attribute wrapper to get read/write functions.
must return the read and write functions must return the read and write functions
......
...@@ -2,13 +2,15 @@ import numpy ...@@ -2,13 +2,15 @@ import numpy
from clients.opcua_client import OPCUAConnection from clients.opcua_client import OPCUAConnection
from clients import opcua_client from clients import opcua_client
import opcua import asyncua
import io import io
import asyncio
from unittest import mock from unittest import mock
import unittest import unittest
from test import base from test import base
import asynctest
class attr_props: class attr_props:
...@@ -38,35 +40,33 @@ dimension_tests = [scalar_shape, spectrum_shape, image_shape] ...@@ -38,35 +40,33 @@ dimension_tests = [scalar_shape, spectrum_shape, image_shape]
class TestOPCua(base.TestCase): class TestOPCua(base.TestCase):
@mock.patch.object(OPCUAConnection, "check_nodes") @asynctest.patch.object(OPCUAConnection, "ping")
@mock.patch.object(OPCUAConnection, "connect") @asynctest.patch.object(opcua_client, "Client")
@mock.patch.object(opcua_client, "Client") def test_opcua_connection(self, m_opc_client, m_ping):
def test_opcua_connection(self, m_opc_client, m_connect, m_check):
""" """
This tests verifies whether the correct connection steps happen. It checks whether we can init an OPCUAConnection object This tests verifies whether the correct connection steps happen. It checks whether we can init an OPCUAConnection object
Whether we can set the namespace, and the OPCua client. Whether we can set the namespace, and the OPCua client.
""" """
m_get_namespace = mock.Mock() m_opc_client_members = asynctest.asynctest.CoroutineMock()
m_get_namespace.get_namespace_index.return_value = 42 m_opc_client_members.get_namespace_index = asynctest.asynctest.CoroutineMock(return_value=42)
m_opc_client.return_value = m_get_namespace m_opc_client_members.connect = asynctest.asynctest.CoroutineMock()
m_opc_client.return_value = m_opc_client_members
test_client = OPCUAConnection("opc.tcp://localhost:4874/freeopcua/server/", "http://lofar.eu", 5, mock.Mock(), mock.Mock()) test_client = OPCUAConnection("opc.tcp://localhost:4874/freeopcua/server/", "http://lofar.eu", 5, mock.Mock())
asyncio.run(test_client.start())
"""Verify that construction of OPCUAConnection calls self.connect"""
m_connect.assert_called_once() # the connect function in the opcua client
m_check.assert_called_once() # debug function that prints out all nodes
m_opc_client.assert_called_once() # makes sure the actual freeOPCua client object is created only once m_opc_client.assert_called_once() # makes sure the actual freeOPCua client object is created only once
m_get_namespace.get_namespace_index.assert_called_once_with("http://lofar.eu") # this also implies test_client.connect() is called
m_opc_client_members.get_namespace_index.assert_called_once_with("http://lofar.eu")
self.assertEqual(42, test_client.name_space_index) self.assertEqual(42, test_client.name_space_index)
@mock.patch.object(OPCUAConnection, "check_nodes") @asynctest.patch.object(OPCUAConnection, "ping")
@mock.patch.object(OPCUAConnection, "connect") @asynctest.patch.object(opcua_client, "Client")
@mock.patch.object(opcua_client, "Client")
@mock.patch.object(opcua_client, 'ProtocolAttribute') @mock.patch.object(opcua_client, 'ProtocolAttribute')
def test_opcua_attr_setup(self, m_protocol_attr, m_opc_client, m_connect, m_check): def test_opcua_attr_setup(self, m_protocol_attr, m_opc_client, m_ping):
""" """
This tests covers the correct creation of read/write functions. This tests covers the correct creation of read/write functions.
In normal circumstances called by he attribute wrapper. In normal circumstances called by he attribute wrapper.
...@@ -75,6 +75,14 @@ class TestOPCua(base.TestCase): ...@@ -75,6 +75,14 @@ class TestOPCua(base.TestCase):
Test succeeds if there are no errors. Test succeeds if there are no errors.
""" """
m_opc_client_members = asynctest.asynctest.CoroutineMock()
m_opc_client_members.get_namespace_index = asynctest.asynctest.CoroutineMock(return_value=2)
m_opc_client_members.connect = asynctest.asynctest.CoroutineMock()
m_objects_node = asynctest.Mock()
m_objects_node.get_child = asynctest.asynctest.CoroutineMock()
m_opc_client_members.get_objects_node = asynctest.Mock(return_value=m_objects_node)
m_opc_client.return_value = m_opc_client_members
for i in attr_test_types: for i in attr_test_types:
class mock_attr: class mock_attr:
def __init__(self, dtype, x, y): def __init__(self, dtype, x, y):
...@@ -96,8 +104,10 @@ class TestOPCua(base.TestCase): ...@@ -96,8 +104,10 @@ class TestOPCua(base.TestCase):
# pretend like there is a running OPCua server with a node that has this name # pretend like there is a running OPCua server with a node that has this name
m_annotation = ["2:PCC", f"2:testNode_{str(i.numpy_type)}_{str(dim_x)}_{str(dim_y)}"] m_annotation = ["2:PCC", f"2:testNode_{str(i.numpy_type)}_{str(dim_x)}_{str(dim_y)}"]
test = OPCUAConnection("opc.tcp://localhost:4874/freeopcua/server/", "http://lofar.eu", 5, mock.Mock(), mock.Mock()) test_client = OPCUAConnection("opc.tcp://localhost:4874/freeopcua/server/", "http://lofar.eu", 5, mock.Mock())
test.setup_attribute(m_annotation, m_attribute) asyncio.run(test_client.start())
asyncio.run(test_client.setup_attribute(m_annotation, m_attribute))
# success if there are no errors. # success if there are no errors.
...@@ -146,10 +156,10 @@ class TestOPCua(base.TestCase): ...@@ -146,10 +156,10 @@ class TestOPCua(base.TestCase):
def get_test_value(): def get_test_value():
return numpy.zeros(j, i.numpy_type) return numpy.zeros(j, i.numpy_type)
def get_flat_value(): async def get_flat_value():
return get_test_value().flatten() return get_test_value().flatten()
m_node = mock.Mock() m_node = asynctest.asynctest.CoroutineMock()
if len(j) == 1: if len(j) == 1:
test = opcua_client.ProtocolAttribute(m_node, j[0], 0, opcua_client.numpy_to_OPCua_dict[i.numpy_type]) test = opcua_client.ProtocolAttribute(m_node, j[0], 0, opcua_client.numpy_to_OPCua_dict[i.numpy_type])
...@@ -175,15 +185,15 @@ class TestOPCua(base.TestCase): ...@@ -175,15 +185,15 @@ class TestOPCua(base.TestCase):
default_value = 42.25 default_value = 42.25
# apply our mapping # apply our mapping
v = opcua.ua.uatypes.Variant(value=numpy_type(default_value), varianttype=opcua_type) v = asyncua.ua.uatypes.Variant(Value=numpy_type(default_value), VariantType=opcua_type)
try: try:
# try to convert it to binary to force opcua to parse the value as the type # try to convert it to binary to force opcua to parse the value as the type
binary = opcua.ua.ua_binary.variant_to_binary(v) binary = asyncua.ua.ua_binary.variant_to_binary(v)
# reinterpret the resulting binary to obtain what opcua made of our value # reinterpret the resulting binary to obtain what opcua made of our value
binary_stream = io.BytesIO(binary) binary_stream = io.BytesIO(binary)
reparsed_v = opcua.ua.ua_binary.variant_from_binary(binary_stream) reparsed_v = asyncua.ua.ua_binary.variant_from_binary(binary_stream)
except Exception as e: except Exception as e:
raise Exception(f"Conversion {numpy_type} -> {opcua_type} failed.") from e raise Exception(f"Conversion {numpy_type} -> {opcua_type} failed.") from e
...@@ -192,7 +202,7 @@ class TestOPCua(base.TestCase): ...@@ -192,7 +202,7 @@ class TestOPCua(base.TestCase):
# does the OPC-UA type have the same datasize (and thus, precision?) # does the OPC-UA type have the same datasize (and thus, precision?)
if numpy_type not in [str, numpy.str]: if numpy_type not in [str, numpy.str]:
self.assertEqual(numpy_type().itemsize, getattr(opcua.ua.ua_binary.Primitives, opcua_type.name).size, msg=f"Conversion {numpy_type} -> {opcua_type} failed: precision mismatch") self.assertEqual(numpy_type().itemsize, getattr(asyncua.ua.ua_binary.Primitives, opcua_type.name).size, msg=f"Conversion {numpy_type} -> {opcua_type} failed: precision mismatch")
...@@ -215,9 +225,9 @@ class TestOPCua(base.TestCase): ...@@ -215,9 +225,9 @@ class TestOPCua(base.TestCase):
# get opcua Varianttype array of the test value # get opcua Varianttype array of the test value
def get_mock_value(value): def get_mock_value(value):
return opcua.ua.uatypes.Variant(value=value, varianttype=opcua_client.numpy_to_OPCua_dict[i.numpy_type]) return asyncua.ua.uatypes.Variant(Value=value, VariantType=opcua_client.numpy_to_OPCua_dict[i.numpy_type])
m_node = mock.Mock() m_node = asynctest.asynctest.CoroutineMock()
# create the protocolattribute # create the protocolattribute
if len(j) == 1: if len(j) == 1:
...@@ -225,14 +235,14 @@ class TestOPCua(base.TestCase): ...@@ -225,14 +235,14 @@ class TestOPCua(base.TestCase):
else: else:
test = opcua_client.ProtocolAttribute(m_node, j[1], j[0], opcua_client.numpy_to_OPCua_dict[i.numpy_type]) test = opcua_client.ProtocolAttribute(m_node, j[1], j[0], opcua_client.numpy_to_OPCua_dict[i.numpy_type])
test.node.get_data_value = mock.Mock() test.node.get_data_value = asynctest.asynctest.CoroutineMock()
# comparison function that replaces `set_data_value` inside the attributes write function # comparison function that replaces `set_data_value` inside the attributes write function
def compare_values(val): async def compare_values(val):
# test values # test values
val = val.tolist() if type(val) == numpy.ndarray else val val = val.tolist() if type(val) == numpy.ndarray else val
if j != dimension_tests[0]: if j != dimension_tests[0]:
comp = val._value == get_mock_value(get_test_value().flatten())._value comp = val.Value == get_mock_value(get_test_value().flatten()).Value
self.assertTrue(comp.all(), self.assertTrue(comp.all(),
"Array attempting to write unequal to expected array: \n\t got: {} \n\texpected: {}".format(val,get_mock_value(get_test_value()))) "Array attempting to write unequal to expected array: \n\t got: {} \n\texpected: {}".format(val,get_mock_value(get_test_value())))
else: else:
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment