diff --git a/devices/clients/attribute_wrapper.py b/devices/clients/attribute_wrapper.py index 20c2ac01feaac5569494e5ce87b0ca5ef3f35121..ced157a1f99283b72d50985f6e5d9ab39cc400ea 100644 --- a/devices/clients/attribute_wrapper.py +++ b/devices/clients/attribute_wrapper.py @@ -146,7 +146,19 @@ class attribute_wrapper(attribute): 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 and return a read and write function that the wrapper will use to get/set data. diff --git a/devices/clients/comms_client.py b/devices/clients/comms_client.py index 0475704b060970f4726a8665c297a82690af1e7f..5e5f459f1fa40c5b18cb4585caa71f2c6d2092c7 100644 --- a/devices/clients/comms_client.py +++ b/devices/clients/comms_client.py @@ -3,6 +3,9 @@ import time import asyncio from abc import ABC, abstractmethod +import logging +logger = logging.getLogger() + class AbstractCommClient(ABC): @abstractmethod def start(self): diff --git a/devices/clients/opcua_client.py b/devices/clients/opcua_client.py index e7634caedf9f26c6484370bf010bb6ec152e9e56..b3c45bc1aa24d632eee25142afd2f0200fe75ff8 100644 --- a/devices/clients/opcua_client.py +++ b/devices/clients/opcua_client.py @@ -98,8 +98,7 @@ class OPCUAConnection(AsyncCommClient): else: raise TypeError(f"Namespace is not of type int or str, but of type {type(self.namespace).__name__}") except Exception as e: - self.streams.error_stream("Could not determine namespace index from namespace: %s: %s", namespace, e) - raise Exception("Could not determine namespace index from namespace %s", namespace) from e + raise ValueError("Could not determine namespace index from namespace %s", self.namespace) from e self.obj = self.client.get_objects_node() diff --git a/devices/test/clients/test_attr_wrapper.py b/devices/test/clients/test_attr_wrapper.py index 453e19c19d67b56eb339462cc1da7e0e8414451b..8711d989a67730667c10aed91de7c9929c500fcb 100644 --- a/devices/test/clients/test_attr_wrapper.py +++ b/devices/test/clients/test_attr_wrapper.py @@ -18,6 +18,8 @@ from devices.hardware_device import * from tango.test_context import DeviceTestContext from test import base +import asyncio + scalar_dims = (1,) spectrum_dims = (4,) image_dims = (3,2) @@ -31,7 +33,7 @@ def dev_init(device): device.set_state(DevState.INIT) device.test_client = test_client(device.Fault, device) 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() @@ -361,6 +363,9 @@ class TestAttributeTypes(base.TestCase): def read_RW_test(self, dev, dtype, test_type): '''Test device''' + expected = None + val = None + try: with DeviceTestContext(dev, process=True) as proxy: diff --git a/devices/test/clients/test_client.py b/devices/test/clients/test_client.py index 2c5a2df9c42431f28e6e8a8c3180b8902c4a4597..039974a1e34ae1a0c9779fd29c2c87f545bc38b7 100644 --- a/devices/test/clients/test_client.py +++ b/devices/test/clients/test_client.py @@ -91,7 +91,7 @@ class test_client(CommClient): self.streams.debug_stream("created and bound example_client read/write functions to attribute_wrapper object") 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. must return the read and write functions diff --git a/devices/test/clients/test_opcua_client.py b/devices/test/clients/test_opcua_client.py index df9296c417857683955aa73ee3cbc0b7985ade76..7214a00bc4fc06577f8d13f31701137bb98e5cb3 100644 --- a/devices/test/clients/test_opcua_client.py +++ b/devices/test/clients/test_opcua_client.py @@ -2,13 +2,15 @@ import numpy from clients.opcua_client import OPCUAConnection from clients import opcua_client -import opcua +import asyncua import io +import asyncio from unittest import mock import unittest from test import base +import asynctest class attr_props: @@ -38,35 +40,33 @@ dimension_tests = [scalar_shape, spectrum_shape, image_shape] class TestOPCua(base.TestCase): - @mock.patch.object(OPCUAConnection, "check_nodes") - @mock.patch.object(OPCUAConnection, "connect") - @mock.patch.object(opcua_client, "Client") - def test_opcua_connection(self, m_opc_client, m_connect, m_check): + @asynctest.patch.object(OPCUAConnection, "ping") + @asynctest.patch.object(opcua_client, "Client") + def test_opcua_connection(self, m_opc_client, m_ping): """ 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. """ - m_get_namespace = mock.Mock() - m_get_namespace.get_namespace_index.return_value = 42 - m_opc_client.return_value = m_get_namespace + m_opc_client_members = asynctest.asynctest.CoroutineMock() + m_opc_client_members.get_namespace_index = asynctest.asynctest.CoroutineMock(return_value=42) + 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_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) - @mock.patch.object(OPCUAConnection, "check_nodes") - @mock.patch.object(OPCUAConnection, "connect") - @mock.patch.object(opcua_client, "Client") + @asynctest.patch.object(OPCUAConnection, "ping") + @asynctest.patch.object(opcua_client, "Client") @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. In normal circumstances called by he attribute wrapper. @@ -75,6 +75,14 @@ class TestOPCua(base.TestCase): 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: class mock_attr: def __init__(self, dtype, x, y): @@ -96,8 +104,10 @@ class TestOPCua(base.TestCase): # 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)}"] - test = OPCUAConnection("opc.tcp://localhost:4874/freeopcua/server/", "http://lofar.eu", 5, mock.Mock(), mock.Mock()) - test.setup_attribute(m_annotation, m_attribute) + test_client = OPCUAConnection("opc.tcp://localhost:4874/freeopcua/server/", "http://lofar.eu", 5, mock.Mock()) + asyncio.run(test_client.start()) + + asyncio.run(test_client.setup_attribute(m_annotation, m_attribute)) # success if there are no errors. @@ -146,10 +156,10 @@ class TestOPCua(base.TestCase): def get_test_value(): return numpy.zeros(j, i.numpy_type) - def get_flat_value(): + async def get_flat_value(): return get_test_value().flatten() - m_node = mock.Mock() + m_node = asynctest.asynctest.CoroutineMock() if len(j) == 1: 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): default_value = 42.25 # 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 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 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: raise Exception(f"Conversion {numpy_type} -> {opcua_type} failed.") from e @@ -192,7 +202,7 @@ class TestOPCua(base.TestCase): # does the OPC-UA type have the same datasize (and thus, precision?) 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): # get opcua Varianttype array of the test 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 if len(j) == 1: @@ -225,14 +235,14 @@ class TestOPCua(base.TestCase): else: 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 - def compare_values(val): + async def compare_values(val): # test values val = val.tolist() if type(val) == numpy.ndarray else val 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(), "Array attempting to write unequal to expected array: \n\t got: {} \n\texpected: {}".format(val,get_mock_value(get_test_value()))) else: