From bb064cd074939cddb0d4020e772857584d147dfd Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Wed, 25 Jul 2018 02:26:16 -0700 Subject: [PATCH] [toranj] add test-case for "Thread:NeighborTable" (#2917) --- tests/toranj/start.sh | 1 + tests/toranj/test-016-neighbor-table.py | 149 ++++++++++++++++++++++++ tests/toranj/wpan.py | 57 ++++++++- 3 files changed, 204 insertions(+), 3 deletions(-) create mode 100644 tests/toranj/test-016-neighbor-table.py diff --git a/tests/toranj/start.sh b/tests/toranj/start.sh index dfb517cbb..0b4c6e623 100755 --- a/tests/toranj/start.sh +++ b/tests/toranj/start.sh @@ -127,6 +127,7 @@ run test-011-child-table.py run test-012-multi-hop-traffic.py run test-013-off-mesh-route-traffic.py run test-014-ip6-address-add.py +run test-016-neighbor-table.py run test-100-mcu-power-state.py run test-600-channel-manager-properties.py run test-601-channel-manager-channel-change.py diff --git a/tests/toranj/test-016-neighbor-table.py b/tests/toranj/test-016-neighbor-table.py new file mode 100644 index 000000000..4ae49f88a --- /dev/null +++ b/tests/toranj/test-016-neighbor-table.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python +# +# Copyright (c) 2018, The OpenThread Authors. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the name of the copyright holder nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +import time +import wpan +from wpan import verify + +#----------------------------------------------------------------------------------------------------------------------- +# Test description: Neighbor table +# +# - Network with NUM_ROUTERS(= 5) routers, all within range of each other. +# - The first router has NUM_CHILDREN(= 4) sleepy children attached to it. +# - The test verifies that all children and routers are observed in the "Thread:NeighborTable" of the first router. +# + +test_name = __file__[:-3] if __file__.endswith('.py') else __file__ +print '-' * 120 +print 'Starting \'{}\''.format(test_name) + +#----------------------------------------------------------------------------------------------------------------------- +# Creating `wpan.Nodes` instances + +speedup = 4 +wpan.Node.set_time_speedup_factor(speedup) + +NUM_ROUTERS = 5 +NUM_CHILDREN = 4 + +routers = [] +for num in range(NUM_ROUTERS): + routers.append(wpan.Node()) + +children = [] +for num in range(NUM_CHILDREN): + children.append(wpan.Node()) + +# end-device per router used for quick promotion to router role +ed = [ 0 ] +for num in range(1, NUM_ROUTERS): + ed.append(wpan.Node()) + +all_nodes = routers + children + ed + +#----------------------------------------------------------------------------------------------------------------------- +# Init all nodes + +wpan.Node.init_all_nodes() + +#----------------------------------------------------------------------------------------------------------------------- +# Build network topology +# + +# whitelist all routers with one another +for i in range(NUM_ROUTERS): + for j in range(NUM_ROUTERS): + if i != j: + routers[i].whitelist_node(routers[j]) + +# All children should attach to routers[0] +for num in range(NUM_CHILDREN): + children[num].whitelist_node(routers[0]) + routers[0].whitelist_node(children[num]) + +# whiltelist the end-device ed with its corresponding router +for num in range(1, NUM_ROUTERS): + ed[num].whitelist_node(routers[num]) + routers[num].whitelist_node(ed[num]) + +routers[0].form("neigh-table") + +for router in routers[1:]: + router.join_node(routers[0], wpan.JOIN_TYPE_ROUTER) + +for num in range(1, NUM_ROUTERS): + ed[num].join_node(routers[num], wpan.JOIN_TYPE_END_DEVICE) + +for num in range(NUM_CHILDREN): + children[num].join_node(routers[0], wpan.JOIN_TYPE_SLEEPY_END_DEVICE) + children[num].set(wpan.WPAN_POLL_INTERVAL,'300') + +#----------------------------------------------------------------------------------------------------------------------- +# Test implementation + +for router in routers[1:]: + verify(router.get(wpan.WPAN_NODE_TYPE) == wpan.NODE_TYPE_ROUTER) + +# Get and parse the neighbor table on routers[0]. +neighbor_table = wpan.parse_neighbor_table_result(routers[0].get(wpan.WPAN_THREAD_NEIGHBOR_TABLE)) + +verify(len(neighbor_table) == NUM_ROUTERS - 1 + NUM_CHILDREN) + +# Verify that all children are seen in the neighbor table +for child in children: + ext_addr = child.get(wpan.WPAN_EXT_ADDRESS)[1:-1] + for entry in neighbor_table: + if entry.ext_address == ext_addr: + break; + else: + raise wpan.VerifyError('Failed to find a child entry for extended address {} in table'.format(ext_addr)) + + verify(int(entry.rloc16, 16) == int(child.get(wpan.WPAN_THREAD_RLOC16), 16)) + verify(entry.is_rx_on_when_idle() == False) + verify(entry.is_ffd() == False) + verify(entry.is_child() == True) + +# Verify that all other routers are seen in the neighbor table +for router in routers[1:]: + ext_addr = router.get(wpan.WPAN_EXT_ADDRESS)[1:-1] + for entry in neighbor_table: + if entry.ext_address == ext_addr: + break; + else: + raise wpan.VerifyError('Failed to find a router entry for extended address {} in table'.format(ext_addr)) + + verify(int(entry.rloc16, 16) == int(router.get(wpan.WPAN_THREAD_RLOC16), 16)) + verify(entry.is_rx_on_when_idle() == True) + verify(entry.is_ffd() == True) + verify(entry.is_child() == False) + + +#----------------------------------------------------------------------------------------------------------------------- +# Test finished + +print '\'{}\' passed.'.format(test_name) diff --git a/tests/toranj/wpan.py b/tests/toranj/wpan.py index 927adba30..039774db3 100644 --- a/tests/toranj/wpan.py +++ b/tests/toranj/wpan.py @@ -1019,7 +1019,7 @@ def parse_on_mesh_prefix_result(on_mesh_prefix_list): #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - class ChildEntry(object): - """ This object encapsulates an child entry""" + """ This object encapsulates a child entry""" def __init__(self, text): @@ -1029,14 +1029,14 @@ class ChildEntry(object): # `RxOnIdle:no, FFD:no, SecDataReq:yes, FullNetData:yes"` # - # We get rid of the first two chars `\t"' and last char '"', split the rest using whitespace as seperator. + # We get rid of the first two chars `\t"' and last char '"', split the rest using whitespace as separator. # Then remove any ',' at end of items in the list. items = [item[:-1] if item[-1] ==',' else item for item in text[2:-1].split()] # First item in the extended address self._ext_address = items[0] - # Convert the rest into a dictionary by splitting using ':' as seperator + # Convert the rest into a dictionary by splitting using ':' as separator dict = {item.split(':')[0] : item.split(':')[1] for item in items[1:]} self._rloc16 = dict['RLOC16'] @@ -1068,3 +1068,54 @@ class ChildEntry(object): def parse_child_table_result(child_table_list): """ Parses child table list string and returns an array of `ChildEntry` objects""" return [ ChildEntry(item) for item in child_table_list.split('\n')[1:-1] ] + +#- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +class NeighborEntry(object): + """ This object encapsulates a neighbor entry""" + + def __init__(self, text): + + # Example of expected text: + # + # `\t"5AC95ED4646D6565, RLOC16:9403, LQIn:3, AveRssi:-20, LastRssi:-20, Age:0, LinkFC:8, MleFC:0, IsChild:yes, ' + # 'RxOnIdle:no, FFD:no, SecDataReq:yes, FullNetData:yes"' + # + + # We get rid of the first two chars `\t"' and last char '"', split the rest using whitespace as separator. + # Then remove any ',' at end of items in the list. + items = [item[:-1] if item[-1] ==',' else item for item in text[2:-1].split()] + + # First item in the extended address + self._ext_address = items[0] + + # Convert the rest into a dictionary by splitting the text using ':' as separator + dict = {item.split(':')[0] : item.split(':')[1] for item in items[1:]} + + self._rloc16 = dict['RLOC16'] + self._is_child = (dict['IsChild'] == 'yes') + self._rx_on_idle = (dict['RxOnIdle'] == 'yes') + self._ffd = (dict['FFD'] == 'yes') + + @property + def ext_address(self): + return self._ext_address + + @property + def rloc16(self): + return self._rloc16 + + def is_rx_on_when_idle(self): + return self._rx_on_idle + + def is_ffd(self): + return self._ffd + + def is_child(self): + return self._is_child + + def __repr__(self): + return 'NeighborEntry({})'.format(self.__dict__) + +def parse_neighbor_table_result(neighbor_table_list): + """ Parses neighbor table list string and returns an array of `NeighborEntry` objects""" + return [ NeighborEntry(item) for item in neighbor_table_list.split('\n')[1:-1] ]