From 48cc40deae5a17c459b7adfb59ce84d057e53cb9 Mon Sep 17 00:00:00 2001 From: Joel Bender Date: Sat, 11 Aug 2018 13:53:40 -0400 Subject: [PATCH] merge (#149) --- py25/bacpypes/basetypes.py | 4 +- py25/bacpypes/constructeddata.py | 111 ++++++++++--- py27/bacpypes/basetypes.py | 4 +- py27/bacpypes/constructeddata.py | 106 ++++++++++--- py27/bacpypes/object.py | 2 + py34/bacpypes/basetypes.py | 4 +- py34/bacpypes/constructeddata.py | 108 ++++++++++--- samples/CommandableMixin.py | 5 +- tests/test_constructed_data/test_array_of.py | 158 ++++++++++++++++++- 9 files changed, 423 insertions(+), 79 deletions(-) diff --git a/py25/bacpypes/basetypes.py b/py25/bacpypes/basetypes.py index d45c212..dcbd5ed 100755 --- a/py25/bacpypes/basetypes.py +++ b/py25/bacpypes/basetypes.py @@ -2365,7 +2365,9 @@ class PriorityValue(Choice): , Element('datetime', DateTime, 1) ] -class PriorityArray(ArrayOf(PriorityValue)): +class PriorityArray(ArrayOf( + PriorityValue, fixed_length=16, prototype=PriorityValue(null=()), + )): pass class PropertyAccessResultAccessResult(Choice): diff --git a/py25/bacpypes/constructeddata.py b/py25/bacpypes/constructeddata.py index cdc096e..6d04f93 100755 --- a/py25/bacpypes/constructeddata.py +++ b/py25/bacpypes/constructeddata.py @@ -5,6 +5,7 @@ Constructed Data """ import sys +from copy import deepcopy as _deepcopy from .errors import DecodingError, \ MissingRequiredParameter, InvalidParameterDatatype, InvalidTag @@ -677,7 +678,7 @@ def ListOf(klass): # return this new type return _ListOf -@bacpypes_debugging(ListOf) +bacpypes_debugging(ListOf) # # Array @@ -696,15 +697,33 @@ class Array(object): _array_of_map = {} _array_of_classes = {} -def ArrayOf(klass): +def ArrayOf(klass, fixed_length=None, prototype=None): """Function to return a class that can encode and decode a list of some other type.""" global _array_of_map global _array_of_classes, _sequence_of_classes + # check the parameters for consistency + if issubclass(klass, Atomic): + if prototype is None: + pass + elif not klass.is_valid(prototype): + raise ValueError("prototype %r not valid for %s" % (prototype, klass.__name__)) + else: + if prototype is None: + ### TODO This should be an error, a prototype should always be + ### required for non-atomic types, even if it's only klass() + ### for a default object which will be deep copied + pass + elif not isinstance(prototype, klass): + raise ValueError("prototype %r not valid for %s" % (prototype, klass.__name__)) + + # build a signature of the parameters + array_signature = (klass, fixed_length, prototype) + # if this has already been built, return the cached one - if klass in _array_of_map: - return _array_of_map[klass] + if array_signature in _array_of_map: + return _array_of_map[array_signature] # no ArrayOf(ArrayOf(...)) allowed if klass in _array_of_classes: @@ -717,23 +736,60 @@ def ArrayOf(klass): class ArrayOf(Array): subtype = None + fixed_length = None + prototype = None def __init__(self, value=None): if value is None: self.value = [0] + if self.fixed_length is not None: + self.fix_length(self.fixed_length) + elif isinstance(value, list): + if (self.fixed_length is not None) and (len(value) != self.fixed_length): + raise ValueError("invalid array length") + self.value = [len(value)] self.value.extend(value) else: raise TypeError("invalid constructor datatype") + def fix_length(self, new_length): + if len(self.value) > new_length + 1: + # trim off the excess + del self.value[new_length + 1:] + elif len(self.value) < new_length + 1: + # how many do we need + element_count = new_length - len(self.value) + 1 + + # extend or append + if issubclass(self.subtype, Atomic): + if self.prototype is None: + extend_value = self.subtype().value + else: + extend_value = self.prototype + self.value.extend( [extend_value] * element_count ) + else: + for i in range(element_count): + if self.prototype is None: + append_value = self.subtype() + else: + append_value = _deepcopy(self.prototype) + self.value.append(append_value) + + self.value[0] = new_length + def append(self, value): + if self.fixed_length is not None: + raise TypeError("fixed length array") + if issubclass(self.subtype, Atomic): pass elif issubclass(self.subtype, AnyAtomic) and not isinstance(value, Atomic): raise TypeError("instance of an atomic type required") elif not isinstance(value, self.subtype): raise TypeError("%s value required" % (self.subtype.__name__,)) + self.value.append(value) self.value[0] = len(self.value) - 1 @@ -754,23 +810,19 @@ def ArrayOf(klass): # special length handling for index 0 if item == 0: - if value < self.value[0]: - # trim - self.value = self.value[0:value + 1] - elif value > self.value[0]: - # extend - if issubclass(self.subtype, Atomic): - self.value.extend( [self.subtype().value] * (value - self.value[0]) ) - else: - for i in range(value - self.value[0]): - self.value.append(self.subtype()) - else: + if (self.fixed_length is not None): + if (value != self.value[0]): + raise TypeError("fixed length array") return - self.value[0] = value + + self.fix_length(value) else: self.value[item] = value def __delitem__(self, item): + if self.fixed_length is not None: + raise TypeError("fixed length array") + # no wrapping index if (item < 1) or (item > self.value[0]): raise IndexError("index out of range") @@ -792,6 +844,9 @@ def ArrayOf(klass): raise ValueError("%r not in array" % (value,)) def remove(self, item): + if self.fixed_length is not None: + raise TypeError("fixed length array") + # find the index of the item and delete it indx = self.index(item) self.__delitem__(indx) @@ -820,7 +875,7 @@ def ArrayOf(klass): if _debug: ArrayOf._debug("(%r)decode %r", self.__class__.__name__, taglist) # start with an empty array - self.value = [0] + new_value = [] while len(taglist) != 0: tag = taglist.Peek() @@ -835,7 +890,7 @@ def ArrayOf(klass): helper = self.subtype(tag) # save the value - self.value.append(helper.value) + new_value.append(helper.value) else: if _debug: ArrayOf._debug(" - building value: %r", self.subtype) # build an element @@ -845,10 +900,15 @@ def ArrayOf(klass): value.decode(taglist) # save what was built - self.value.append(value) + new_value.append(value) + + # check the length + if self.fixed_length is not None: + if self.fixed_length != len(new_value): + raise ValueError("invalid array length") # update the length - self.value[0] = len(self.value) - 1 + self.value = [len(new_value)] + new_value def encode_item(self, item, taglist): if _debug: ArrayOf._debug("(%r)encode_item %r %r", self.__class__.__name__, item, taglist) @@ -947,10 +1007,14 @@ def ArrayOf(klass): # constrain it to a list of a specific type of item setattr(ArrayOf, 'subtype', klass) + setattr(ArrayOf, 'fixed_length', fixed_length) + setattr(ArrayOf, 'prototype', prototype) + + # update the name ArrayOf.__name__ = 'ArrayOf' + klass.__name__ # cache this type - _array_of_map[klass] = ArrayOf + _array_of_map[array_signature] = ArrayOf _array_of_classes[ArrayOf] = 1 # return this new type @@ -1153,7 +1217,7 @@ class Choice(object): def dict_contents(self, use_dict=None, as_class=dict): """Return the contents of an object as a dict.""" - if _debug: _log.debug("dict_contents use_dict=%r as_class=%r", use_dict, as_class) + if _debug: Choice._debug("dict_contents use_dict=%r as_class=%r", use_dict, as_class) # make/extend the dictionary of content if use_dict is None: @@ -1347,7 +1411,7 @@ bacpypes_debugging(Any) # AnyAtomic # -class AnyAtomic: +class AnyAtomic(Atomic): def __init__(self, arg=None): if _debug: AnyAtomic._debug("__init__ %r", arg) @@ -1396,3 +1460,4 @@ class AnyAtomic: return '<' + desc + ' instance at 0x%08x' % (id(self),) + '>' bacpypes_debugging(AnyAtomic) + diff --git a/py27/bacpypes/basetypes.py b/py27/bacpypes/basetypes.py index d45c212..dcbd5ed 100755 --- a/py27/bacpypes/basetypes.py +++ b/py27/bacpypes/basetypes.py @@ -2365,7 +2365,9 @@ class PriorityValue(Choice): , Element('datetime', DateTime, 1) ] -class PriorityArray(ArrayOf(PriorityValue)): +class PriorityArray(ArrayOf( + PriorityValue, fixed_length=16, prototype=PriorityValue(null=()), + )): pass class PropertyAccessResultAccessResult(Choice): diff --git a/py27/bacpypes/constructeddata.py b/py27/bacpypes/constructeddata.py index bad87d0..8dd27af 100755 --- a/py27/bacpypes/constructeddata.py +++ b/py27/bacpypes/constructeddata.py @@ -5,6 +5,7 @@ Constructed Data """ import sys +from copy import deepcopy as _deepcopy from .errors import DecodingError, \ MissingRequiredParameter, InvalidParameterDatatype, InvalidTag @@ -691,15 +692,33 @@ class Array(object): _array_of_map = {} _array_of_classes = {} -def ArrayOf(klass): +def ArrayOf(klass, fixed_length=None, prototype=None): """Function to return a class that can encode and decode a list of some other type.""" global _array_of_map global _array_of_classes, _sequence_of_classes + # check the parameters for consistency + if issubclass(klass, Atomic): + if prototype is None: + pass + elif not klass.is_valid(prototype): + raise ValueError("prototype %r not valid for %s" % (prototype, klass.__name__)) + else: + if prototype is None: + ### TODO This should be an error, a prototype should always be + ### required for non-atomic types, even if it's only klass() + ### for a default object which will be deep copied + pass + elif not isinstance(prototype, klass): + raise ValueError("prototype %r not valid for %s" % (prototype, klass.__name__)) + + # build a signature of the parameters + array_signature = (klass, fixed_length, prototype) + # if this has already been built, return the cached one - if klass in _array_of_map: - return _array_of_map[klass] + if array_signature in _array_of_map: + return _array_of_map[array_signature] # no ArrayOf(ArrayOf(...)) allowed if klass in _array_of_classes: @@ -713,23 +732,60 @@ def ArrayOf(klass): class ArrayOf(Array): subtype = None + fixed_length = None + prototype = None def __init__(self, value=None): if value is None: self.value = [0] + if self.fixed_length is not None: + self.fix_length(self.fixed_length) + elif isinstance(value, list): + if (self.fixed_length is not None) and (len(value) != self.fixed_length): + raise ValueError("invalid array length") + self.value = [len(value)] self.value.extend(value) else: raise TypeError("invalid constructor datatype") + def fix_length(self, new_length): + if len(self.value) > new_length + 1: + # trim off the excess + del self.value[new_length + 1:] + elif len(self.value) < new_length + 1: + # how many do we need + element_count = new_length - len(self.value) + 1 + + # extend or append + if issubclass(self.subtype, Atomic): + if self.prototype is None: + extend_value = self.subtype().value + else: + extend_value = self.prototype + self.value.extend( [extend_value] * element_count ) + else: + for i in range(element_count): + if self.prototype is None: + append_value = self.subtype() + else: + append_value = _deepcopy(self.prototype) + self.value.append(append_value) + + self.value[0] = new_length + def append(self, value): + if self.fixed_length is not None: + raise TypeError("fixed length array") + if issubclass(self.subtype, Atomic): pass elif issubclass(self.subtype, AnyAtomic) and not isinstance(value, Atomic): raise TypeError("instance of an atomic type required") elif not isinstance(value, self.subtype): raise TypeError("%s value required" % (self.subtype.__name__,)) + self.value.append(value) self.value[0] = len(self.value) - 1 @@ -750,23 +806,19 @@ def ArrayOf(klass): # special length handling for index 0 if item == 0: - if value < self.value[0]: - # trim - self.value = self.value[0:value + 1] - elif value > self.value[0]: - # extend - if issubclass(self.subtype, Atomic): - self.value.extend( [self.subtype().value] * (value - self.value[0]) ) - else: - for i in range(value - self.value[0]): - self.value.append(self.subtype()) - else: + if (self.fixed_length is not None): + if (value != self.value[0]): + raise TypeError("fixed length array") return - self.value[0] = value + + self.fix_length(value) else: self.value[item] = value def __delitem__(self, item): + if self.fixed_length is not None: + raise TypeError("fixed length array") + # no wrapping index if (item < 1) or (item > self.value[0]): raise IndexError("index out of range") @@ -788,6 +840,9 @@ def ArrayOf(klass): raise ValueError("%r not in array" % (value,)) def remove(self, item): + if self.fixed_length is not None: + raise TypeError("fixed length array") + # find the index of the item and delete it indx = self.index(item) self.__delitem__(indx) @@ -816,7 +871,7 @@ def ArrayOf(klass): if _debug: ArrayOf._debug("(%r)decode %r", self.__class__.__name__, taglist) # start with an empty array - self.value = [0] + new_value = [] while len(taglist) != 0: tag = taglist.Peek() @@ -831,7 +886,7 @@ def ArrayOf(klass): helper = self.subtype(tag) # save the value - self.value.append(helper.value) + new_value.append(helper.value) else: if _debug: ArrayOf._debug(" - building value: %r", self.subtype) # build an element @@ -841,10 +896,15 @@ def ArrayOf(klass): value.decode(taglist) # save what was built - self.value.append(value) + new_value.append(value) + + # check the length + if self.fixed_length is not None: + if self.fixed_length != len(new_value): + raise ValueError("invalid array length") # update the length - self.value[0] = len(self.value) - 1 + self.value = [len(new_value)] + new_value def encode_item(self, item, taglist): if _debug: ArrayOf._debug("(%r)encode_item %r %r", self.__class__.__name__, item, taglist) @@ -941,10 +1001,14 @@ def ArrayOf(klass): # constrain it to a list of a specific type of item setattr(ArrayOf, 'subtype', klass) + setattr(ArrayOf, 'fixed_length', fixed_length) + setattr(ArrayOf, 'prototype', prototype) + + # update the name ArrayOf.__name__ = 'ArrayOf' + klass.__name__ # cache this type - _array_of_map[klass] = ArrayOf + _array_of_map[array_signature] = ArrayOf _array_of_classes[ArrayOf] = 1 # return this new type @@ -1148,7 +1212,7 @@ class Choice(object): def dict_contents(self, use_dict=None, as_class=dict): """Return the contents of an object as a dict.""" - if _debug: _log.debug("dict_contents use_dict=%r as_class=%r", use_dict, as_class) + if _debug: Choice._debug("dict_contents use_dict=%r as_class=%r", use_dict, as_class) # make/extend the dictionary of content if use_dict is None: diff --git a/py27/bacpypes/object.py b/py27/bacpypes/object.py index ac39cc0..735cd62 100755 --- a/py27/bacpypes/object.py +++ b/py27/bacpypes/object.py @@ -329,6 +329,8 @@ class Property: arry[arrayIndex] = value except IndexError: raise ExecutionError(errorClass='property', errorCode='invalidArrayIndex') + except TypeError: + raise ExecutionError(errorClass='property', errorCode='valueOutOfRange') # check for monitors, call each one with the old and new value if is_monitored: diff --git a/py34/bacpypes/basetypes.py b/py34/bacpypes/basetypes.py index d45c212..dcbd5ed 100755 --- a/py34/bacpypes/basetypes.py +++ b/py34/bacpypes/basetypes.py @@ -2365,7 +2365,9 @@ class PriorityValue(Choice): , Element('datetime', DateTime, 1) ] -class PriorityArray(ArrayOf(PriorityValue)): +class PriorityArray(ArrayOf( + PriorityValue, fixed_length=16, prototype=PriorityValue(null=()), + )): pass class PropertyAccessResultAccessResult(Choice): diff --git a/py34/bacpypes/constructeddata.py b/py34/bacpypes/constructeddata.py index 574901a..8dd27af 100755 --- a/py34/bacpypes/constructeddata.py +++ b/py34/bacpypes/constructeddata.py @@ -5,6 +5,7 @@ Constructed Data """ import sys +from copy import deepcopy as _deepcopy from .errors import DecodingError, \ MissingRequiredParameter, InvalidParameterDatatype, InvalidTag @@ -691,15 +692,33 @@ class Array(object): _array_of_map = {} _array_of_classes = {} -def ArrayOf(klass): +def ArrayOf(klass, fixed_length=None, prototype=None): """Function to return a class that can encode and decode a list of some other type.""" global _array_of_map global _array_of_classes, _sequence_of_classes + # check the parameters for consistency + if issubclass(klass, Atomic): + if prototype is None: + pass + elif not klass.is_valid(prototype): + raise ValueError("prototype %r not valid for %s" % (prototype, klass.__name__)) + else: + if prototype is None: + ### TODO This should be an error, a prototype should always be + ### required for non-atomic types, even if it's only klass() + ### for a default object which will be deep copied + pass + elif not isinstance(prototype, klass): + raise ValueError("prototype %r not valid for %s" % (prototype, klass.__name__)) + + # build a signature of the parameters + array_signature = (klass, fixed_length, prototype) + # if this has already been built, return the cached one - if klass in _array_of_map: - return _array_of_map[klass] + if array_signature in _array_of_map: + return _array_of_map[array_signature] # no ArrayOf(ArrayOf(...)) allowed if klass in _array_of_classes: @@ -713,23 +732,60 @@ def ArrayOf(klass): class ArrayOf(Array): subtype = None + fixed_length = None + prototype = None def __init__(self, value=None): if value is None: self.value = [0] + if self.fixed_length is not None: + self.fix_length(self.fixed_length) + elif isinstance(value, list): + if (self.fixed_length is not None) and (len(value) != self.fixed_length): + raise ValueError("invalid array length") + self.value = [len(value)] self.value.extend(value) else: raise TypeError("invalid constructor datatype") + def fix_length(self, new_length): + if len(self.value) > new_length + 1: + # trim off the excess + del self.value[new_length + 1:] + elif len(self.value) < new_length + 1: + # how many do we need + element_count = new_length - len(self.value) + 1 + + # extend or append + if issubclass(self.subtype, Atomic): + if self.prototype is None: + extend_value = self.subtype().value + else: + extend_value = self.prototype + self.value.extend( [extend_value] * element_count ) + else: + for i in range(element_count): + if self.prototype is None: + append_value = self.subtype() + else: + append_value = _deepcopy(self.prototype) + self.value.append(append_value) + + self.value[0] = new_length + def append(self, value): + if self.fixed_length is not None: + raise TypeError("fixed length array") + if issubclass(self.subtype, Atomic): pass elif issubclass(self.subtype, AnyAtomic) and not isinstance(value, Atomic): raise TypeError("instance of an atomic type required") elif not isinstance(value, self.subtype): raise TypeError("%s value required" % (self.subtype.__name__,)) + self.value.append(value) self.value[0] = len(self.value) - 1 @@ -750,23 +806,19 @@ def ArrayOf(klass): # special length handling for index 0 if item == 0: - if value < self.value[0]: - # trim - self.value = self.value[0:value + 1] - elif value > self.value[0]: - # extend - if issubclass(self.subtype, Atomic): - self.value.extend( [self.subtype().value] * (value - self.value[0]) ) - else: - for i in range(value - self.value[0]): - self.value.append(self.subtype()) - else: + if (self.fixed_length is not None): + if (value != self.value[0]): + raise TypeError("fixed length array") return - self.value[0] = value + + self.fix_length(value) else: self.value[item] = value def __delitem__(self, item): + if self.fixed_length is not None: + raise TypeError("fixed length array") + # no wrapping index if (item < 1) or (item > self.value[0]): raise IndexError("index out of range") @@ -788,6 +840,9 @@ def ArrayOf(klass): raise ValueError("%r not in array" % (value,)) def remove(self, item): + if self.fixed_length is not None: + raise TypeError("fixed length array") + # find the index of the item and delete it indx = self.index(item) self.__delitem__(indx) @@ -816,7 +871,7 @@ def ArrayOf(klass): if _debug: ArrayOf._debug("(%r)decode %r", self.__class__.__name__, taglist) # start with an empty array - self.value = [0] + new_value = [] while len(taglist) != 0: tag = taglist.Peek() @@ -831,7 +886,7 @@ def ArrayOf(klass): helper = self.subtype(tag) # save the value - self.value.append(helper.value) + new_value.append(helper.value) else: if _debug: ArrayOf._debug(" - building value: %r", self.subtype) # build an element @@ -841,10 +896,15 @@ def ArrayOf(klass): value.decode(taglist) # save what was built - self.value.append(value) + new_value.append(value) + + # check the length + if self.fixed_length is not None: + if self.fixed_length != len(new_value): + raise ValueError("invalid array length") # update the length - self.value[0] = len(self.value) - 1 + self.value = [len(new_value)] + new_value def encode_item(self, item, taglist): if _debug: ArrayOf._debug("(%r)encode_item %r %r", self.__class__.__name__, item, taglist) @@ -941,10 +1001,14 @@ def ArrayOf(klass): # constrain it to a list of a specific type of item setattr(ArrayOf, 'subtype', klass) + setattr(ArrayOf, 'fixed_length', fixed_length) + setattr(ArrayOf, 'prototype', prototype) + + # update the name ArrayOf.__name__ = 'ArrayOf' + klass.__name__ # cache this type - _array_of_map[klass] = ArrayOf + _array_of_map[array_signature] = ArrayOf _array_of_classes[ArrayOf] = 1 # return this new type @@ -1148,7 +1212,7 @@ class Choice(object): def dict_contents(self, use_dict=None, as_class=dict): """Return the contents of an object as a dict.""" - if _debug: _log.debug("dict_contents use_dict=%r as_class=%r", use_dict, as_class) + if _debug: Choice._debug("dict_contents use_dict=%r as_class=%r", use_dict, as_class) # make/extend the dictionary of content if use_dict is None: @@ -1340,7 +1404,7 @@ class Any: # @bacpypes_debugging -class AnyAtomic: +class AnyAtomic(Atomic): def __init__(self, arg=None): if _debug: AnyAtomic._debug("__init__ %r", arg) diff --git a/samples/CommandableMixin.py b/samples/CommandableMixin.py index 2a268e0..8e9a85f 100644 --- a/samples/CommandableMixin.py +++ b/samples/CommandableMixin.py @@ -67,10 +67,7 @@ def Commandable(datatype, presentValue='presentValue', priorityArray='priorityAr # see if a priority array was provided if (priorityArray not in kwargs): - new_priority_array = PriorityArray() - for i in range(16): - new_priority_array.append(PriorityValue(null=())) - setattr(self, priorityArray, new_priority_array) + setattr(self, priorityArray, PriorityArray()) # see if a present value was provided if (relinquishDefault not in kwargs): diff --git a/tests/test_constructed_data/test_array_of.py b/tests/test_constructed_data/test_array_of.py index 8e84833..e448254 100644 --- a/tests/test_constructed_data/test_array_of.py +++ b/tests/test_constructed_data/test_array_of.py @@ -8,11 +8,11 @@ Test Array import unittest -from bacpypes.debugging import bacpypes_debugging, ModuleLogger, xtob +from bacpypes.debugging import bacpypes_debugging, ModuleLogger -from bacpypes.errors import MissingRequiredParameter -from bacpypes.primitivedata import Integer, Tag, TagList -from bacpypes.constructeddata import Element, Sequence, ArrayOf +from bacpypes.primitivedata import TagList, Integer, Time +from bacpypes.constructeddata import ArrayOf +from bacpypes.basetypes import TimeStamp from .helpers import SimpleSequence @@ -46,7 +46,7 @@ class TestIntegerArray(unittest.TestCase): # create another sequence and decode the tag list ary = IntegerArray() ary.decode(tag_list) - if _debug: TestIntegerArray._debug(" - seq: %r", seq) + if _debug: TestIntegerArray._debug(" - ary: %r", ary) def test_append(self): if _debug: TestIntegerArray._debug("test_append") @@ -86,7 +86,7 @@ class TestIntegerArray(unittest.TestCase): # not find something with self.assertRaises(ValueError): - indx = ary.index(4) + ary.index(4) def test_remove_item(self): if _debug: TestIntegerArray._debug("test_remove_item") @@ -163,6 +163,132 @@ class TestIntegerArray(unittest.TestCase): assert ary.value[1:] == ary_value +# fixed length array of integers +IntegerArray5 = ArrayOf(Integer, fixed_length=5) + +@bacpypes_debugging +class TestIntegerArray5(unittest.TestCase): + + def test_empty_array(self): + if _debug: TestIntegerArray5._debug("test_empty_array") + + # create an empty array + ary = IntegerArray5() + if _debug: TestIntegerArray5._debug(" - ary: %r", ary) + + # array sematics + assert len(ary) == 5 + assert ary[0] == 5 + + # value correct + assert ary.value[1:] == [0, 0, 0, 0, 0] + + def test_append(self): + if _debug: TestIntegerArray5._debug("test_append") + + # create an empty array + ary = IntegerArray5() + if _debug: TestIntegerArray5._debug(" - ary: %r", ary) + + # append an integer + with self.assertRaises(TypeError): + ary.append(2) + + def test_delete_item(self): + if _debug: TestIntegerArray5._debug("test_delete_item") + + # create an array + ary = IntegerArray5([1, 2, 3, 4, 5]) + if _debug: TestIntegerArray5._debug(" - ary: %r", ary) + + # delete something + with self.assertRaises(TypeError): + del ary[2] + + def test_index_item(self): + if _debug: TestIntegerArray5._debug("test_index_item") + + # create an array + ary = IntegerArray5([1, 2, 3, 4, 5]) + if _debug: TestIntegerArray5._debug(" - ary: %r", ary) + + # find something + assert ary.index(3) == 3 + + # not find something + with self.assertRaises(ValueError): + ary.index(100) + + def test_remove_item(self): + if _debug: TestIntegerArray5._debug("test_remove_item") + + # create an array + ary = IntegerArray5([1, 2, 3, 4, 5]) + if _debug: TestIntegerArray5._debug(" - ary: %r", ary) + + # remove something + with self.assertRaises(TypeError): + ary.remove(4) + + def test_resize(self): + if _debug: TestIntegerArray5._debug("test_resize") + + # create an array + ary = IntegerArray5([1, 2, 3, 4, 5]) + if _debug: TestIntegerArray5._debug(" - ary: %r", ary) + + # make it the same length (noop) + ary[0] = 5 + + # changing it to something else fails + with self.assertRaises(TypeError): + ary[0] = 4 + + def test_get_item(self): + if _debug: TestIntegerArray5._debug("test_get_item") + + # create an array + ary = IntegerArray5([1, 2, 3, 4, 5]) + if _debug: TestIntegerArray5._debug(" - ary: %r", ary) + + # BACnet semantics + assert ary[1] == 1 + + def test_set_item(self): + if _debug: TestIntegerArray5._debug("test_set_item") + + # create an array + ary = IntegerArray5([1, 2, 3, 4, 5]) + if _debug: TestIntegerArray5._debug(" - ary: %r", ary) + + # BACnet semantics, no type checking + ary[1] = 10 + assert ary[1] == 10 + + def test_codec(self): + if _debug: TestIntegerArray5._debug("test_codec") + + # test array contents + ary_value = [1, 2, 3, 4, 5] + + # create an array + ary = IntegerArray5(ary_value) + if _debug: TestIntegerArray5._debug(" - ary: %r", ary) + + # encode it in a tag list + tag_list = TagList() + ary.encode(tag_list) + if _debug: TestIntegerArray5._debug(" - tag_list: %r", tag_list) + + # create another sequence and decode the tag list + ary = IntegerArray() + ary.decode(tag_list) + if _debug: TestIntegerArray5._debug(" - ary %r", ary) + + # value matches + assert ary.value[1:] == ary_value + + # array of a sequence SimpleSequenceArray = ArrayOf(SimpleSequence) @@ -196,3 +322,23 @@ class TestSimpleSequenceArray(unittest.TestCase): # value matches assert ary.value[1:] == ary_value + +# fixed length array of TimeStamps +ArrayOfTimeStamp = ArrayOf(TimeStamp, fixed_length=16, + prototype=TimeStamp(time=Time().value), + ) + +@bacpypes_debugging +class TestArrayOfTimeStamp(unittest.TestCase): + + def test_empty_array(self): + if _debug: TestArrayOfTimeStamp._debug("test_empty_array") + + # create an empty array + ary = ArrayOfTimeStamp() + if _debug: TestArrayOfTimeStamp._debug(" - ary: %r", ary) + + # array sematics + assert len(ary) == 16 + assert ary[0] == 16 +