diff --git a/docker-compose/lofar-device-base/Dockerfile b/docker-compose/lofar-device-base/Dockerfile index d280bf4f3f76f4b71e17cf0fcf6c8fb7ff32e572..0d7c98235c903bfea6a66339a7feb6b2a5440f56 100644 --- a/docker-compose/lofar-device-base/Dockerfile +++ b/docker-compose/lofar-device-base/Dockerfile @@ -9,13 +9,13 @@ RUN sudo apt-get install -y python3-dev libboost-python-dev pkg-config && sudo a RUN sudo apt-get install -y rsync && sudo apt-get clean COPY lofar-device-base/lofar-requirements.txt /lofar-requirements.txt -RUN sudo pip3 install -r /lofar-requirements.txt # Manually install all requirements from the .txt as part of the base image # This reduces runtime overhead as well as preventing issues around dependency # installation for development builds (pip install ./ ignores requirements.txt) COPY tmp/requirements.txt /tangostationcontrol-requirements.txt -RUN sudo pip3 install -r /tangostationcontrol-requirements.txt + +RUN sudo pip3 install -r /tangostationcontrol-requirements.txt -r /lofar-requirements.txt # install and use ephimerides and geodetic ("measures") tables for casacore. # we install a _stub_ since the tables need to be deployed explicitly from within the software. diff --git a/docker-compose/loki.yml b/docker-compose/loki.yml index 2007e16aa7e06726cc5b8be75553e4ef2df56475..6f634767bd04610a70e969cff2105c062f7ecb1e 100644 --- a/docker-compose/loki.yml +++ b/docker-compose/loki.yml @@ -4,7 +4,7 @@ # # -version: "3" +version: "2.1" services: loki: diff --git a/tangostationcontrol/requirements.txt b/tangostationcontrol/requirements.txt index a6662e4b263ca26aa93ad216c9b104ffd0e8e735..8d628544905096b9a717719b18bb25293bb0e72d 100644 --- a/tangostationcontrol/requirements.txt +++ b/tangostationcontrol/requirements.txt @@ -3,7 +3,7 @@ # integration process, which may cause wedges in the gate later. importlib-metadata<2.0.0,>=0.12;python_version<"3.8" -lofar-station-client@git+https://git.astron.nl/lofar2.0/lofar-station-client@0.9.1 +lofar-station-client@git+https://git.astron.nl/lofar2.0/lofar-station-client@0.9.2 numpy mock asyncua >= 0.9.90 # LGPLv3 diff --git a/tangostationcontrol/setup.cfg b/tangostationcontrol/setup.cfg index f42357ef73a2f50734d03f26444b32b905ac0042..bea7c5441230fe90a9ee104c788443121e0d7008 100644 --- a/tangostationcontrol/setup.cfg +++ b/tangostationcontrol/setup.cfg @@ -26,7 +26,7 @@ package_dir= packages=find: python_requires => 3.7 install_requires = - importlib-metadata>=0.12, <5.0;python_version<"3.8" + importlib-metadata<2.0.0,>=0.12;python_version<"3.8" pip>=1.5 [options.packages.find] diff --git a/tangostationcontrol/tangostationcontrol/common/type_checking.py b/tangostationcontrol/tangostationcontrol/common/type_checking.py index f6686b54b86b94aa745ac311b9f71e622ec87ef8..ac146170d2274cc950a6bffa326404c9481fd072 100644 --- a/tangostationcontrol/tangostationcontrol/common/type_checking.py +++ b/tangostationcontrol/tangostationcontrol/common/type_checking.py @@ -3,23 +3,14 @@ # Distributed under the terms of the APACHE license. # See LICENSE.txt for more info. -from collections.abc import Sequence - -import numpy - - -def is_sequence(obj): - """True for sequences, positionally ordered collections - See https://www.pythontutorial.net/advanced-python/python-sequences/ - """ - return isinstance(obj, Sequence) or isinstance(obj, numpy.ndarray) +from tango.utils import is_seq def sequence_not_str(obj): """True for sequences that are not str, bytes or bytearray""" - return is_sequence(obj) and not isinstance(obj, (str, bytes, bytearray)) + return is_seq(obj) and not isinstance(obj, (str, bytes, bytearray)) def type_not_sequence(obj): """True for types that are not sequences""" - return not is_sequence(obj) and isinstance(obj, type) + return not is_seq(obj) and isinstance(obj, type) diff --git a/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_observation.py b/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_observation.py new file mode 100644 index 0000000000000000000000000000000000000000..cb9011a00e0f3448a4a84a74d1fef8ac15178291 --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/integration_test/default/devices/test_observation.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +# +# This file is part of the LOFAR 2.0 Station Software +# +# +# +# Distributed under the terms of the APACHE license. +# See LICENSE.txt for more info. + +from tangostationcontrol.test.devices.test_observation_base import TestObservationBase +from tangostationcontrol.integration_test import base +from tangostationcontrol.integration_test.device_proxy import TestDeviceProxy + +from lofar_station_client.observation.observation import Observation + +from os import environ +from json import loads + +from tango import DevState + +class TestObservation(base.IntegrationTestCase): + + def setUp(self): + self.observation_control_proxy = TestDeviceProxy("STAT/ObservationControl/1") + self.observation_control_proxy.off() + self.observation_control_proxy.warm_boot() + + def test_observation(self): + """Test of the observation_wrapper class basic functionality""" + + # convert the JSON specificiation to a dict for this class + specification_dict = loads(TestObservationBase.VALID_JSON) + + # create an observation class using the dict and as host just get it using a util function + observation = Observation(specification=specification_dict, host=environ["TANGO_HOST"]) + + # Assert the observation is running after starting it + observation.start() + self.assertTrue(observation.is_running()) + + # Assert the proxy is on + proxy = observation.observation_proxy() + self.assertTrue(proxy.state() == DevState.ON) + + # Assert the observation has stopped after aborting + observation.abort() + self.assertFalse(observation.is_running()) diff --git a/tangostationcontrol/tangostationcontrol/test/common/test_type_checking.py b/tangostationcontrol/tangostationcontrol/test/common/test_type_checking.py index 253fb6182006a426188e84576bf295344f973549..342e1ba9b7834981397bbaf79a97b06075ba4d96 100644 --- a/tangostationcontrol/tangostationcontrol/test/common/test_type_checking.py +++ b/tangostationcontrol/tangostationcontrol/test/common/test_type_checking.py @@ -6,6 +6,8 @@ # # Distributed under the terms of the APACHE license. # See LICENSE.txt for more info. + +from tango.utils import is_seq import numpy from tangostationcontrol.common import type_checking @@ -32,7 +34,10 @@ class TestTypeChecking(base.TestCase): return False def sequence_test(self, obj): - """Test object is sequence based on properties and verify is_sequence""" + """Test object is sequence based on properties and verify is_sequence + + Recover alternative is_sequence method from commit 73177e9 if tests fail + """ result = ( self.subscriptable(obj) & self.iterable(obj) @@ -40,12 +45,15 @@ class TestTypeChecking(base.TestCase): ) self.assertEqual( - result, type_checking.is_sequence(obj), + result, is_seq(obj), F"Test failed for type {type(obj)}" ) def test_is_sequence_for_types(self): - """Types to be tested by is_sequence""" + """Types to be tested by is_sequence + + Recover alternative is_seq method from commit 73177e9 if tests fail + """ test_types = [ (False,), diff --git a/tangostationcontrol/tangostationcontrol/test/devices/test_antennafield_device.py b/tangostationcontrol/tangostationcontrol/test/devices/test_antennafield_device.py index d14a36871e6ba9cd28491549a587508a9102b1be..8bb7ae978483d54904c9a15825047855afce0f5b 100644 --- a/tangostationcontrol/tangostationcontrol/test/devices/test_antennafield_device.py +++ b/tangostationcontrol/tangostationcontrol/test/devices/test_antennafield_device.py @@ -368,44 +368,6 @@ class TestAntennaToRecvMapper(base.TestCase): actual = mapper.map_write("HBAT_PWR_on_RW", set_values) numpy.testing.assert_equal(expected, actual) - # def test_merge_write(self): - # """Verify all None fields are replaced by merge_write if no control""" - # - # mapper = AntennaToRecvMapper( - # self.CONTROL_NOT_CONNECTED, self.POWER_NOT_CONNECTED, 1 - # ) - # - # merge_values = [[None] * N_rcu] * MAX_ANTENNA - # current_values = [[False] * N_rcu] * MAX_ANTENNA - # - # mapper.merge_write(merge_values, current_values) - # numpy.testing.assert_equal(merge_values, current_values) - # - # results = [] - # for _i in range(25): - # start_time = time.monotonic_ns() - # mapper.merge_write(merge_values, current_values) - # stop_time = time.monotonic_ns() - # results.append(stop_time - start_time) - # - # logging.error( - # f"Merge write performance: Median {statistics.median(results) / 1.e9} " - # f"Stdev {statistics.stdev(results) / 1.e9}" - # ) - # - # def test_merge_write_values(self): - # """Verify all fields with values are retained by merge_write""" - # - # mapper = AntennaToRecvMapper( - # self.CONTROL_NOT_CONNECTED, self.POWER_NOT_CONNECTED, 1 - # ) - # - # merge_values = [[True] * N_rcu] * 2 + [[None] * N_rcu] * (MAX_ANTENNA - 2) - # current_values = [[True] * N_rcu] * 2 + [[False] * N_rcu] * (MAX_ANTENNA - 2) - # - # mapper.merge_write(merge_values, current_values) - # numpy.testing.assert_equal(merge_values, current_values) - class TestAntennafieldDevice(device_base.DeviceTestCase): diff --git a/tangostationcontrol/tangostationcontrol/test/devices/test_lofar_device.py b/tangostationcontrol/tangostationcontrol/test/devices/test_lofar_device.py index a57a2846a18580e3752b175ee82a9a649d752c09..3a91941bb1366c98ef07b04709d72655effc7f00 100644 --- a/tangostationcontrol/tangostationcontrol/test/devices/test_lofar_device.py +++ b/tangostationcontrol/tangostationcontrol/test/devices/test_lofar_device.py @@ -7,12 +7,20 @@ # Distributed under the terms of the APACHE license. # See LICENSE.txt for more info. +import numpy + from tango.test_context import DeviceTestContext from tango.server import attribute -from tango import DevState, DevFailed +from tango.server import command +from tango import AttrWriteType +from tango import DevFailed +from tango import DevState +from tango import DevVarBooleanArray from tangostationcontrol.devices import lofar_device +from unittest import mock + from tangostationcontrol.test.devices import device_base @@ -75,8 +83,54 @@ class TestLofarDevice(device_base.DeviceTestCase): # Just for demo, do not use class variables to store attribute state _bool_array = [False] * BOOL_ARRAY_DIM + bool_array = attribute( + dtype=(bool,), max_dim_x=BOOL_ARRAY_DIM, + access=AttrWriteType.READ_WRITE, fget="get_bool_array", + fset="set_bool_array" + ) - - @attribute(dtype=(bool,), max_dim_x=BOOL_ARRAY_DIM) - def bool_array(self): + def get_bool_array(self): return self._bool_array + + def set_bool_array(self, bool_array): + self._bool_array = bool_array + + @command(dtype_in=DevVarBooleanArray) + def do_read_modify_write(self, values: numpy.array): + bool_array_half = int(self.BOOL_ARRAY_DIM / 2) + + # We have to mock the proxy because lofar_device.proxy will be + # patched + t_write = mock.Mock() + t_proxy = mock.Mock( + read_attribute=mock.Mock( + return_value=mock.Mock( + value=numpy.array(self._bool_array) + ) + ), + write_attribute=t_write + ) + + self.atomic_read_modify_write_attribute( + values, t_proxy, "bool_array", + numpy.array([True, False] * bool_array_half) + ) + + # Fake the write, extract the call argument from t_write mock + self._bool_array = t_write.call_args[0][1] + + with DeviceTestContext( + AttributeLofarDevice, process=True + ) as proxy: + + bool_array_half = int(AttributeLofarDevice.BOOL_ARRAY_DIM / 2) + excepted_result = [True, False] * bool_array_half + + proxy.initialise() + proxy.do_read_modify_write( + [True] * AttributeLofarDevice.BOOL_ARRAY_DIM + ) + + numpy.testing.assert_array_equal( + excepted_result, proxy.bool_array + )