From ac35e0015bb325d0e649151d9477fc470b41fd44 Mon Sep 17 00:00:00 2001 From: Jan David Mol <mol@astron.nl> Date: Thu, 28 Oct 2021 20:08:56 +0200 Subject: [PATCH] L2SS-392: Add opcua client test that talks to a server instead of mocking one. Fix call_method. --- .../clients/opcua_client.py | 11 +- .../test_opcua_client_against_server.py | 134 ++++++++++++++++++ 2 files changed, 143 insertions(+), 2 deletions(-) create mode 100644 tangostationcontrol/tangostationcontrol/test/clients/test_opcua_client_against_server.py diff --git a/tangostationcontrol/tangostationcontrol/clients/opcua_client.py b/tangostationcontrol/tangostationcontrol/clients/opcua_client.py index ceacf690f..7eda319d6 100644 --- a/tangostationcontrol/tangostationcontrol/clients/opcua_client.py +++ b/tangostationcontrol/tangostationcontrol/clients/opcua_client.py @@ -117,7 +117,7 @@ class OPCUAConnection(AsyncCommClient): return path - async def setup_attribute(self, annotation, attribute): + async def setup_protocol_attribute(self, annotation, attribute): # process the annotation path = self.get_node_path(annotation) @@ -142,6 +142,11 @@ class OPCUAConnection(AsyncCommClient): except: pass + return prot_attr + + async def setup_attribute(self, annotation, attribute): + prot_attr = await self.setup_protocol_attribute(annotation, attribute) + # Tango will call these from a separate polling thread. def read_function(): return asyncio.run_coroutine_threadsafe(prot_attr.read_function(), self.event_loop).result() @@ -157,11 +162,13 @@ class OPCUAConnection(AsyncCommClient): method_path = self.get_node_path(method_path) try: - node = await self.obj.get_child(method_path[:-1]) + node = await self.obj.get_child(method_path[:-1]) if len(method_path) > 1 else self.obj result = await node.call_method(method_path[-1], *args) except Exception as e: raise Exception(f"Calling method {method_path} failed") from e + return result + def call_method(self, method_path, *args): return asyncio.run_coroutine_threadsafe(self._call_method(method_path, *args), self.event_loop).result() diff --git a/tangostationcontrol/tangostationcontrol/test/clients/test_opcua_client_against_server.py b/tangostationcontrol/tangostationcontrol/test/clients/test_opcua_client_against_server.py new file mode 100644 index 000000000..5230b92dc --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/test/clients/test_opcua_client_against_server.py @@ -0,0 +1,134 @@ +import asyncua +import numpy + +from clients.opcua_client import OPCUAConnection + +from test import base + + +class TestClientServer(base.AsyncTestCase): + """ Test the OPCUAConnection against an OPCUA server we instantiate ourselves. """ + + async def setup_server(self, port): + """ Setup a server on a dedicated port for the test, to allow + the tests to be run in parallel. """ + + # where we will run the server + self.endpoint = f"opc.tcp://127.0.0.1:{port}" + self.namespace = "http://example.com" + + # setup an OPC-UA server + self.server = asyncua.Server() + await self.server.init() + self.server.set_endpoint(self.endpoint) + self.server.set_server_name(f"Test server spawned by {__file__}") + + # create an interface + idx = await self.server.register_namespace(self.namespace) + obj = self.server.get_objects_node() + + # add double_R/double_RW + double_R = await obj.add_variable(idx, "double_R", 42.0) + double_RW = await obj.add_variable(idx, "double_RW", 42.0) + await double_RW.set_writable() + + # add methods + @asyncua.uamethod + def multiply(parent, x, y): + return x * y + + @asyncua.uamethod + def procedure(parent): + return + + multiply_method = await obj.add_method(idx, "multiply", multiply, [asyncua.ua.VariantType.Double, asyncua.ua.VariantType.Int64], [asyncua.ua.VariantType.Double]) + procedure_method = await obj.add_method(idx, "procedure", procedure, [], []) + + # run the server + await self.server.start() + + async def setUp(self): + self.server = None + + async def tearDown(self): + if self.server: + await self.server.stop() + + def fault_func(self): + raise Exception("FAULT") + + async def test_opcua_connection(self): + await self.setup_server(14840) + + test_client = OPCUAConnection(self.endpoint, self.namespace, 5, self.fault_func, self.loop) + try: + await test_client.start() + finally: + await test_client.stop() + + async def test_read_attribute(self): + await self.setup_server(14841) + + test_client = OPCUAConnection(self.endpoint, self.namespace, 5, self.fault_func, self.loop) + try: + await test_client.start() + + # setup the attribute + class attribute(object): + dim_x = 1 + dim_y = 0 + numpy_type = numpy.double + + prot_attr = await test_client.setup_protocol_attribute(["double_R"], attribute()) + + # read it from the server + self.assertEqual(42.0, await prot_attr.read_function()) + finally: + await test_client.stop() + + async def test_write_attribute(self): + await self.setup_server(14842) + + test_client = OPCUAConnection(self.endpoint, self.namespace, 5, self.fault_func, self.loop) + try: + await test_client.start() + + # setup the attribute + class attribute(object): + dim_x = 1 + dim_y = 0 + numpy_type = numpy.double + + prot_attr = await test_client.setup_protocol_attribute(["double_RW"], attribute()) + + # write it to the server and read it back to verify + await prot_attr.write_function(123.0) + + self.assertEqual(123.0, await prot_attr.read_function()) + finally: + await test_client.stop() + + async def test_method_without_args(self): + await self.setup_server(14843) + + test_client = OPCUAConnection(self.endpoint, self.namespace, 5, self.fault_func, self.loop) + try: + await test_client.start() + + self.assertEqual(None, await test_client._call_method(["procedure"])) + finally: + await test_client.stop() + + async def test_method_with_args(self): + await self.setup_server(14843) + + test_client = OPCUAConnection(self.endpoint, self.namespace, 5, self.fault_func, self.loop) + try: + await test_client.start() + + self.assertEqual(21.0, await test_client._call_method( + ["multiply"], + asyncua.ua.Variant(3.0, asyncua.ua.VariantType.Double), + asyncua.ua.Variant(7, asyncua.ua.VariantType.Int64))) + finally: + await test_client.stop() -- GitLab