#!/usr/bin/env python

# ###########################################################################
#
# This file is part of Taurus
#
# http://taurus-scada.org
#
# Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain
#
# Taurus is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Taurus is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Taurus.  If not, see <http://www.gnu.org/licenses/>.
#
# ###########################################################################

"""Test for taurus.core.tango.test.test_tangovalidator...
"""

# __all__ = []

__docformat__ = "restructuredtext"

import numpy
import tango
from taurus.core.units import Quantity, UR
from pint import UndefinedUnitError

import taurus
from taurus.core import DataType, DataFormat
from taurus.core.tango.tangoattribute import TangoAttrValue
from taurus.core.taurusbasetypes import AttrQuality
import pytest
from .nodb import nodb_dev  # noqa

_INT_IMG = numpy.arange(2 * 3, dtype="int16").reshape((2, 3))
_INT_SPE = _INT_IMG[1, :]
_FLOAT_IMG = _INT_IMG * 0.1
_FLOAT_SPE = _INT_SPE * 0.1
_BOOL_IMG = numpy.array([[True, False], [False, True]])
_BOOL_SPE = [True, False]
_STR = "foo BAR |-+#@!?_[]{}"
_UINT8_IMG = numpy.array([[1, 2], [3, 4]], dtype="uint8")
_UINT8_SPE = _UINT8_IMG[1, :]


def _assertValidValue(exp, got, msg):
    # if we are dealing with quantities, use the magnitude for comparing
    if isinstance(got, Quantity):
        got = got.to(Quantity(exp).units).magnitude
    if isinstance(exp, Quantity):
        exp = exp.magnitude
    try:
        # first try the most generic equality
        chk = bool(got == exp)
    except Exception:
        chk = False
    if not chk:
        # some cases may fail the simple equality but still be True
        try:
            # for those values that can be handled by numpy.allclose()
            chk = numpy.allclose(got, exp)
        except Exception:
            if isinstance(got, numpy.ndarray):
                # uchars were not handled with allclose
                # UGLY!! but numpy.all does not work
                chk = got.tolist() == exp.tolist()
    assert chk, msg


def _getDecodePyTangoAttr(dev_name, attr_name, cfg):
    """Helper for decode the PyTango attribute infoex"""
    if dev_name.startswith("tango-nodb:"):
        tg_dev_name = "tango" + dev_name[10:] + "#dbase=no"
    dev = tango.DeviceProxy(tg_dev_name)
    infoex = dev.get_attribute_config_ex(attr_name)[0]
    try:
        unit = UR.parse_units(infoex.unit)
    except (UndefinedUnitError, UnicodeDecodeError):
        unit = UR.parse_units(None)
    if cfg in ["range", "alarms", "warnings"]:
        if cfg == "range":
            low = infoex.min_value
            high = infoex.max_value
        elif cfg == "alarms":
            low = infoex.alarms.min_alarm
            high = infoex.alarms.max_alarm
        elif cfg == "warnings":
            low = infoex.alarms.min_warning
            high = infoex.alarms.max_warning
        if low == "Not specified":
            low = "-inf"
        if high == "Not specified":
            high = "inf"
        return [Quantity(float(low), unit), Quantity(float(high), unit)]
    elif cfg == "label":
        return infoex.label
    else:
        return None


@pytest.mark.parametrize(
    "attr_name, setvalue, expected, expected_attrv, expectedshape",
    [
        (
            "short_spectrum",
            Quantity(numpy.empty(0, dtype="int16"), "km"),
            dict(rvalue=Quantity([], "mm"), type=DataType.Integer, unit="mm"),
            dict(rvalue=Quantity([], "mm"), type=DataType.Integer, unit="mm"),
            (0,),
        ),
        (
            "short_image",
            Quantity(numpy.empty((0, 0), dtype="int16"), "mm"),
            dict(rvalue=Quantity([], "mm"), type=DataType.Integer),
            None,
            (0, 0),
        ),
        (
            "boolean_spectrum",
            numpy.empty(0, dtype="bool8"),
            dict(type=DataType.Boolean),
            None,
            (0,),
        ),
        (
            "boolean_image",
            numpy.empty((0, 0), dtype="bool8"),
            dict(type=DataType.Boolean),
            None,
            (0, 0),
        ),
        # Test encode-decode of strings, booleans and uchars
        (
            "uchar_image",
            _UINT8_IMG,
            dict(
                rvalue=_UINT8_IMG,
                wvalue=_UINT8_IMG,
                type=DataType.Bytes,
                label="uchar_image",
                writable=True,
            ),
            dict(
                rvalue=_UINT8_IMG,
                value=_UINT8_IMG,
                wvalue=_UINT8_IMG,
                w_value=_UINT8_IMG,
                quality=AttrQuality.ATTR_VALID,
            ),
            None,
        ),
        (
            "uchar_spectrum",
            _UINT8_SPE,
            dict(
                rvalue=_UINT8_SPE,
                wvalue=_UINT8_SPE,
                type=DataType.Bytes,
                writable=True,
            ),
            dict(
                rvalue=_UINT8_SPE,
                value=_UINT8_SPE,
                wvalue=_UINT8_SPE,
                w_value=_UINT8_SPE,
                quality=AttrQuality.ATTR_VALID,
            ),
            None,
        ),
        (
            "uchar_scalar",
            12,
            dict(
                rvalue=12,
                wvalue=12,
                type=DataType.Bytes,
                writable=True,
                range=[None, None],
                alarms=[None, None],
                warnings=[None, None],
            ),
            dict(
                rvalue=12,
                value=12,
                wvalue=12,
                w_value=12,
                quality=AttrQuality.ATTR_VALID,
            ),
            None,
        ),
        (
            "uchar_image",
            _UINT8_IMG,
            dict(
                rvalue=_UINT8_IMG,
                wvalue=_UINT8_IMG,
                type=DataType.Bytes,
                label="uchar_image",
                writable=True,
            ),
            dict(
                rvalue=_UINT8_IMG,
                value=_UINT8_IMG,
                wvalue=_UINT8_IMG,
                w_value=_UINT8_IMG,
                quality=AttrQuality.ATTR_VALID,
            ),
            None,
        ),
        (
            "uchar_spectrum",
            _UINT8_SPE,
            dict(
                rvalue=_UINT8_SPE,
                wvalue=_UINT8_SPE,
                type=DataType.Bytes,
                writable=True,
            ),
            dict(
                rvalue=_UINT8_SPE,
                value=_UINT8_SPE,
                wvalue=_UINT8_SPE,
                w_value=_UINT8_SPE,
                quality=AttrQuality.ATTR_VALID,
            ),
            None,
        ),
        (
            "uchar_scalar",
            12,
            dict(
                rvalue=12,
                wvalue=12,
                type=DataType.Bytes,
                writable=True,
                range=[None, None],
                alarms=[None, None],
                warnings=[None, None],
            ),
            dict(
                rvalue=Quantity(12, "mm"),
                value=12,
                wvalue=Quantity(12, "mm"),
                w_value=12,
                quality=AttrQuality.ATTR_VALID,
            ),
            None,
        ),
        (
            "string_image",
            ((_STR,) * 3,) * 2,
            dict(
                rvalue=((_STR,) * 3,) * 2,
                wvalue=((_STR,) * 3,) * 2,
                type=DataType.String,
                label="string_image",
                writable=True,
            ),
            dict(
                value=((_STR,) * 3,) * 2,
                w_value=((_STR,) * 3,) * 2,
                rvalue=((_STR,) * 3,) * 2,
                wvalue=((_STR,) * 3,) * 2,
                quality=AttrQuality.ATTR_VALID,
            ),
            None,
        ),
        (
            "string_spectrum",
            (_STR,) * 3,
            dict(
                rvalue=(_STR,) * 3,
                wvalue=(_STR,) * 3,
                type=DataType.String,
                label="string_spectrum",
                writable=True,
            ),
            dict(
                value=(_STR,) * 3,
                w_value=(_STR,) * 3,
                rvalue=(_STR,) * 3,
                wvalue=(_STR,) * 3,
                quality=AttrQuality.ATTR_VALID,
            ),
            None,
        ),
        (
            "string_scalar",
            _STR,
            dict(
                rvalue=_STR,
                wvalue=_STR,
                type=DataType.String,
                label="string_scalar",
                writable=True,
            ),
            dict(
                value=_STR,
                w_value=_STR,
                rvalue=_STR,
                wvalue=_STR,
                quality=AttrQuality.ATTR_VALID,
            ),
            None,
        ),
        (
            "boolean_image",
            _BOOL_IMG,
            dict(
                rvalue=_BOOL_IMG,
                wvalue=_BOOL_IMG,
                type=DataType.Boolean,
                label="boolean_image",
                writable=True,
            ),
            dict(
                rvalue=_BOOL_IMG,
                value=_BOOL_IMG,
                wvalue=_BOOL_IMG,
                w_value=_BOOL_IMG,
                quality=AttrQuality.ATTR_VALID,
            ),
            None,
        ),
        (
            "boolean_spectrum",
            _BOOL_SPE,
            dict(
                rvalue=_BOOL_SPE,
                wvalue=_BOOL_SPE,
                type=DataType.Boolean,
                writable=True,
            ),
            dict(
                rvalue=_BOOL_SPE,
                value=_BOOL_SPE,
                wvalue=_BOOL_SPE,
                w_value=_BOOL_SPE,
                quality=AttrQuality.ATTR_VALID,
            ),
            None,
        ),
        (
            "boolean_scalar",
            False,
            dict(
                rvalue=False,
                wvalue=False,
                type=DataType.Boolean,
                writable=True,
                range=[None, None],
                alarms=[None, None],
                warnings=[None, None],
            ),
            dict(
                rvalue=False,
                value=False,
                wvalue=False,
                w_value=False,
                quality=AttrQuality.ATTR_VALID,
            ),
            None,
        ),
        # ==================================================================
        # Test encode-decode with quantitiy conversions
        (
            "float_image",
            Quantity(_FLOAT_IMG, "m"),
            dict(
                rvalue=Quantity(_FLOAT_IMG, "m"),
                wvalue=Quantity(_FLOAT_IMG, "m"),
                type=DataType.Float,
                writable=True,
                quality=AttrQuality.ATTR_VALID,
                label="float_image",
                range=[
                    Quantity(float("-inf"), "mm"),
                    Quantity(float("inf"), "mm"),
                ],
                alarms=[
                    Quantity(float("-inf"), "mm"),
                    Quantity(float("inf"), "mm"),
                ],
                warnings=[
                    Quantity(float("-inf"), "mm"),
                    Quantity(float("inf"), "mm"),
                ],
            ),
            dict(
                rvalue=Quantity(_FLOAT_IMG, "m"),
                value=1000 * _FLOAT_IMG,
                wvalue=Quantity(_FLOAT_IMG, "m"),
                w_value=1000 * _FLOAT_IMG,
                quality=AttrQuality.ATTR_VALID,
            ),
            numpy.shape(_FLOAT_IMG),
        ),
        (
            "float_spectrum",
            Quantity(_FLOAT_SPE, "m"),
            dict(
                rvalue=Quantity(_FLOAT_SPE, "m"),
                wvalue=Quantity(_FLOAT_SPE, "m"),
                type=DataType.Float,
                quality=AttrQuality.ATTR_VALID,
            ),
            dict(
                rvalue=Quantity(_FLOAT_SPE, "m"),
                value=1000 * _FLOAT_SPE,
                wvalue=Quantity(_FLOAT_SPE, "m"),
                w_value=1000 * _FLOAT_SPE,
                quality=AttrQuality.ATTR_VALID,
            ),
            numpy.shape(_FLOAT_SPE),
        ),
        (
            "float_scalar",
            Quantity(0.01, "m"),
            dict(
                rvalue=Quantity(0.01, "m"),
                wvalue=Quantity(10, "mm"),
                type=DataType.Float,
                name="float_scalar",
                range=[Quantity(-12.30, "mm"), Quantity(12.30, "mm")],
                alarms=[Quantity(-6.15, "mm"), Quantity(6.15, "mm")],
                warnings=[Quantity(-3.69, "mm"), Quantity(3.69, "mm")],
            ),
            dict(
                value=10,
                rvalue=Quantity(0.01, "m"),
                w_value=10,
                wvalue=Quantity(10, "mm"),
                quality=AttrQuality.ATTR_ALARM,
            ),
            None,
        ),
        (
            "float_scalar",
            Quantity(0.004, "m"),
            dict(rvalue=Quantity(4, "mm"), wvalue=Quantity(4, "mm")),
            dict(
                rvalue=Quantity(4, "mm"),
                wvalue=Quantity(0.004, "m"),
                quality=AttrQuality.ATTR_WARNING,
            ),
            None,
        ),
        (
            "float_scalar",
            Quantity(3, "mm"),
            dict(
                rvalue=Quantity(3, "mm"),
                wvalue=Quantity(3, "mm"),
                type=DataType.Float,
            ),
            dict(value=3, w_value=3, quality=AttrQuality.ATTR_VALID),
            None,
        ),
        (
            "double_scalar",
            Quantity(0.01, "m"),
            dict(
                rvalue=Quantity(0.01, "m"),
                wvalue=Quantity(10, "mm"),
                type=DataType.Float,
                name="double_scalar",
                range=[Quantity(-12.30, "mm"), Quantity(12.30, "mm")],
                alarms=[Quantity(-6.15, "mm"), Quantity(6.15, "mm")],
                warnings=[Quantity(-3.69, "mm"), Quantity(3.69, "mm")],
            ),
            dict(
                value=10,
                rvalue=Quantity(0.01, "m"),
                w_value=10,
                wvalue=Quantity(10, "mm"),
                quality=AttrQuality.ATTR_ALARM,
            ),
            None,
        ),
        (
            "double_scalar",
            Quantity(0.004, "m"),
            dict(rvalue=Quantity(4, "mm"), wvalue=Quantity(4, "mm")),
            dict(
                rvalue=Quantity(4, "mm"),
                wvalue=Quantity(0.004, "m"),
                quality=AttrQuality.ATTR_WARNING,
            ),
            None,
        ),
        (
            "double_scalar",
            Quantity(3, "mm"),
            dict(
                rvalue=Quantity(3, "mm"),
                wvalue=Quantity(3, "mm"),
                type=DataType.Float,
            ),
            dict(value=3, w_value=3, quality=AttrQuality.ATTR_VALID),
            None,
        ),
        (
            "short_image",
            Quantity(_INT_IMG, "m"),
            dict(
                rvalue=Quantity(_INT_IMG, "m"),
                wvalue=Quantity(_INT_IMG, "m"),
                type=DataType.Integer,
            ),
            dict(
                value=1000 * _INT_IMG,
                rvalue=Quantity(_INT_IMG, "m"),
                wvalue=Quantity(_INT_IMG, "m"),
                w_value=1000 * _INT_IMG,
                quality=AttrQuality.ATTR_VALID,
            ),
            numpy.shape(_INT_IMG),
        ),
        (
            "short_spectrum",
            Quantity(_INT_SPE, "m"),
            dict(
                rvalue=Quantity(_INT_SPE, "m"),
                wvalue=Quantity(_INT_SPE, "m"),
                type=DataType.Integer,
            ),
            dict(value=1000 * _INT_SPE, w_value=1000 * _INT_SPE),
            numpy.shape(_INT_SPE),
        ),
        (
            "short_scalar",
            Quantity(1, "m"),
            dict(
                rvalue=Quantity(1000, "mm"),
                wvalue=Quantity(1000, "mm"),
                type=DataType.Integer,
                label="short_scalar",
                data_format=DataFormat._0D,
                writable=True,
                range=[Quantity(-1230, "mm"), Quantity(1230, "mm")],
                alarms=[Quantity(-615, "mm"), Quantity(615, "mm")],
                warnings=[Quantity(-369, "mm"), Quantity(369, "mm")],
            ),
            dict(value=1000, w_value=1000, quality=AttrQuality.ATTR_ALARM),
            None,
        ),
        # ===================================================================
        # Test read of tango attributes
        (
            "uchar_image_ro",
            None,
            dict(
                rvalue=Quantity([[1] * 3] * 3, "mm"),
                wvalue=None,
                type=DataType.Bytes,
            ),
            dict(
                value=[[1] * 3] * 3,
                w_value=None,
                quality=AttrQuality.ATTR_VALID,
            ),
            (3, 3),
        ),
        (
            "uchar_scalar_ro",
            None,
            dict(
                rvalue=1,
                wvalue=None,
                type=DataType.Bytes,
                data_format=DataFormat._0D,
                writable=False,
                range=[None, None],
                alarms=[None, None],
                warnings=[None, None],
            ),
            dict(
                rvalue=Quantity(1, "mm"),
                value=1,
                quality=AttrQuality.ATTR_VALID,
                wvalue=None,
                w_value=None,
            ),
            None,
        ),
        # ------------------------------------------------------------------
        # Disable these 2tests because of known (Py)Tango bug for empty
        # string arrays
        #
        # (
        #     "string_image_ro",
        #     None,
        #     dict(
        #         rvalue=(("hello world",) * 3,) * 3,
        #         wvalue=None,
        #         type=DataType.String,
        #     ),
        #     dict(
        #         value=(("hello world",) * 3,) * 3,
        #         w_value=None,
        #         quality=AttrQuality.ATTR_VALID,
        #     ),
        #     (3, 3),
        # ),
        # (
        #     "string_spectrum_ro",
        #     None,
        #     dict(
        #         rvalue=("hello world",) * 3,
        #         wvalue=None,
        #         type=DataType.String
        #     ),
        #     dict(
        #         value=("hello world",) * 3,
        #         w_value=None,
        #         quality=AttrQuality.ATTR_VALID,
        #     ),
        #     (3,),
        # ),
        # ------------------------------------------------------------------
        (
            "string_scalar_ro",
            None,
            dict(rvalue="hello world", wvalue=None, type=DataType.String),
            dict(
                value="hello world",
                w_value=None,
                quality=AttrQuality.ATTR_VALID,
            ),
            None,
        ),
        (
            "boolean_image_ro",
            None,
            dict(
                rvalue=numpy.ones((3, 3), dtype="b"),
                wvalue=None,
                type=DataType.Boolean,
            ),
            dict(
                value=numpy.ones((3, 3), dtype="b"),
                w_value=None,
                quality=AttrQuality.ATTR_VALID,
            ),
            (3, 3),
        ),
        (
            "boolean_spectrum_ro",
            None,
            dict(
                rvalue=numpy.array([True] * 3),
                wvalue=None,
                type=DataType.Boolean,
                label="boolean_spectrum_ro",
            ),
            dict(
                value=numpy.array([True] * 3),
                w_value=None,
                quality=AttrQuality.ATTR_VALID,
            ),
            (3,),
        ),
        (
            "boolean_scalar_ro",
            None,
            dict(
                rvalue=True,
                wvalue=None,
                type=DataType.Boolean,
                range=[None, None],
                alarms=[None, None],
                warnings=[None, None],
                data_format=DataFormat._0D,
                writable=False,
            ),
            dict(value=True, w_value=None, quality=AttrQuality.ATTR_VALID),
            None,
        ),
        (
            "float_image_ro",
            None,
            dict(
                rvalue=Quantity([[1.23] * 3] * 3, "mm"),
                wvalue=None,
                type=DataType.Float,
            ),
            dict(
                value=[[1.23] * 3] * 3,
                w_value=None,
                quality=AttrQuality.ATTR_VALID,
                wvalue=None,
            ),
            (3, 3),
        ),
        (
            "float_spectrum_ro",
            None,
            dict(
                rvalue=Quantity([1.23] * 3, "mm"),
                wvalue=None,
                type=DataType.Float,
            ),
            dict(
                value=[1.23] * 3, w_value=None, quality=AttrQuality.ATTR_VALID
            ),
            (3,),
        ),
        (
            "float_scalar_ro",
            None,
            dict(
                rvalue=Quantity(1.23, "mm"),
                wvalue=None,
                type=DataType.Float,
                writable=False,
                range=[
                    Quantity(float("-inf"), "mm"),
                    Quantity(float("inf"), "mm"),
                ],
                alarms=[
                    Quantity(float("-inf"), "mm"),
                    Quantity(float("inf"), "mm"),
                ],
                warnings=[
                    Quantity(float("-inf"), "mm"),
                    Quantity(float("inf"), "mm"),
                ],
            ),
            dict(value=1.23, w_value=None, quality=AttrQuality.ATTR_VALID),
            None,
        ),
        (
            "short_image_ro",
            None,
            dict(
                rvalue=Quantity([[123] * 3] * 3, "mm"),
                wvalue=None,
                type=DataType.Integer,
            ),
            dict(
                rvalue=Quantity([[123] * 3] * 3, "mm"),
                value=[[123] * 3] * 3,
                quality=AttrQuality.ATTR_VALID,
                wvalue=None,
                w_value=None,
            ),
            (3, 3),
        ),
        (
            "short_spectrum_ro",
            None,
            dict(
                rvalue=Quantity([123] * 3, "mm"),
                wvalue=None,
                type=DataType.Integer,
                data_format=DataFormat._1D,
                writable=False,
            ),
            dict(
                rvalue=Quantity([123] * 3, "mm"),
                value=[123] * 3,
                quality=AttrQuality.ATTR_VALID,
                wvalue=None,
                w_value=None,
            ),
            (3,),
        ),
        (
            "short_scalar_ro",
            None,
            dict(
                rvalue=Quantity(123, "mm"),
                wvalue=None,
                type=DataType.Integer,
                data_format=DataFormat._0D,
                writable=False,
                range=[
                    Quantity(float("-inf"), "mm"),
                    Quantity(float("inf"), "mm"),
                ],
                alarms=[
                    Quantity(float("-inf"), "mm"),
                    Quantity(float("inf"), "mm"),
                ],
                warnings=[
                    Quantity(float("-inf"), "mm"),
                    Quantity(float("inf"), "mm"),
                ],
            ),
            dict(
                rvalue=Quantity(123, "mm"),
                value=123,
                quality=AttrQuality.ATTR_VALID,
                wvalue=None,
                w_value=None,
            ),
            None,
        ),
    ],
)
def test_write_read_attr(
    nodb_dev,  # noqa: F811
    attr_name,
    setvalue,
    expected,
    expected_attrv,
    expectedshape,
):
    """check creation and correct write-and-read of an attribute"""

    if expected is None:
        expected = {}
    if expected_attrv is None:
        expected_attrv = {}

    attr_fullname = "{}/{}".format(nodb_dev, attr_name)
    a = taurus.Attribute(attr_fullname)

    if setvalue is None:
        read_value = a.read()
    else:
        a.write(setvalue)
        read_value = a.read(cache=False)

    msg = 'read() for "%s" did not return a TangoAttrValue (got a %s)' % (
        attr_name,
        read_value.__class__.__name__,
    )
    assert isinstance(read_value, TangoAttrValue), msg

    # Test attribute
    for k, exp in expected.items():
        try:
            got = getattr(a, k)
        except AttributeError:
            msg = 'The attribute, "%s" does not provide info on %s' % (
                attr_name,
                k,
            )
            pytest.fail(msg)
        msg = '%s for "%s" should be %r (got %r)' % (k, attr_name, exp, got)
        _assertValidValue(exp, got, msg)

    # Test attribute value
    for k, exp in expected_attrv.items():
        try:
            got = getattr(read_value, k)
        except AttributeError:
            msg = 'The read value for "%s" does not provide info on %s' % (
                attr_name,
                k,
            )
            pytest.fail(msg)
        msg = "%s for the value of %s should be %r (got %r)" % (
            k,
            attr_name,
            exp,
            got,
        )
        _assertValidValue(exp, got, msg)

    if expectedshape is not None:
        msg = "rvalue.shape for %s should be %r (got %r)" % (
            attr_name,
            expectedshape,
            read_value.rvalue.shape,
        )
        assert read_value.rvalue.shape == expectedshape, msg


@pytest.mark.parametrize(
    "attr_name, cfg, value, expected",
    [
        (
            "short_scalar_nu",
            "range",
            [float("-inf"), float("inf")],
            [Quantity(float("-inf")), Quantity(float("inf"))],
        ),
        (
            "short_scalar_nu",
            "range",
            [Quantity(float("-inf")), Quantity(float("inf"))],
            [Quantity(float("-inf")), Quantity(float("inf"))],
        ),
        (
            "short_scalar_nu",
            "range",
            [100, 300],
            [Quantity(100), Quantity(300)],
        ),
        (
            "short_scalar_nu",
            "range",
            [Quantity(100), Quantity(300)],
            [Quantity(100), Quantity(300)],
        ),
        (
            "float_scalar",
            "range",
            [Quantity(-5, "mm"), Quantity(5, "mm")],
            [Quantity(-0.005, "m"), Quantity(5, "mm")],
        ),
        ("short_spectrum", "label", "Just a Test", "Just a Test"),
        ("boolean_spectrum", "label", "Just_a_Test", "Just_a_Test"),
        (
            "short_scalar",
            "warnings",
            [Quantity(-2, "mm"), Quantity(2, "mm")],
            [Quantity(-2, "mm"), Quantity(0.002, "m")],
        ),
        (
            "short_image",
            "warnings",
            [Quantity(-2, "mm"), Quantity(2, "mm")],
            [Quantity(-0.002, "m"), Quantity(2, "mm")],
        ),
        (
            "float_image",
            "warnings",
            [Quantity(-0.75, "mm"), Quantity(0.75, "mm")],
            [Quantity(-0.00075, "m"), Quantity(0.75, "mm")],
        ),
        (
            "short_scalar_nu",
            "warnings",
            [100, 300],
            [Quantity(100), Quantity(300)],
        ),
        (
            "short_scalar",
            "alarms",
            [Quantity(-50, "mm"), Quantity(50, "mm")],
            [Quantity(-50, "mm"), Quantity(50, "mm")],
        ),
        (
            "short_image",
            "alarms",
            [Quantity(-2, "mm"), Quantity(2, "mm")],
            [Quantity(-0.002, "m"), Quantity(2, "mm")],
        ),
        (
            "float_image",
            "alarms",
            [Quantity(-0.75, "mm"), Quantity(0.75, "mm")],
            [Quantity(-0.00075, "m"), Quantity(0.75, "mm")],
        ),
        (
            "short_scalar_nu",
            "alarms",
            [100, 300],
            [Quantity(100), Quantity(300)],
        ),
    ],
)
def test_write_read_conf(
    nodb_dev,  # noqa: F811
    attr_name,
    cfg,
    value,
    expected,
):
    """Check the write-and-read of the TangoAttribute configuration"""
    attr_fullname = "{}/{}".format(nodb_dev, attr_name)
    attr = taurus.Attribute(attr_fullname)
    # write the property
    setattr(attr, cfg, value)
    # read the property
    got = getattr(attr, cfg)
    msg = "%s.%s from Taurus do not mach, expected %s read %s" % (
        attr_name,
        cfg,
        expected,
        got,
    )
    map(_assertValidValue, got, expected, msg)

    msg = "%s.%s from Tango do not mach, expected %s read %s" % (
        attr_name,
        cfg,
        expected,
        got,
    )
    tangovalue = _getDecodePyTangoAttr(nodb_dev, attr_name, cfg)
    map(_assertValidValue, got, tangovalue, msg)
