[toranj] test-case to check child mode change and parent reset recovery (#3129)
diff --git a/tests/toranj/start.sh b/tests/toranj/start.sh
index 3318bfb..aa15587 100755
--- a/tests/toranj/start.sh
+++ b/tests/toranj/start.sh
@@ -191,6 +191,7 @@
run test-024-partition-merge.py
run test-025-network-data-timeout.py
run test-026-slaac-address.py
+run test-027-child-mode-change.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-027-child-mode-change.py b/tests/toranj/test-027-child-mode-change.py
new file mode 100644
index 0000000..a203f51
--- /dev/null
+++ b/tests/toranj/test-027-child-mode-change.py
@@ -0,0 +1,143 @@
+#!/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: Verify Thread mode change on children and recovery after parent reset
+#
+
+test_name = __file__[:-3] if __file__.endswith('.py') else __file__
+print '-' * 120
+print 'Starting \'{}\''.format(test_name)
+
+#-----------------------------------------------------------------------------------------------------------------------
+# Utility functions
+
+def verify_child_table(parent, children):
+ """
+ This function verifies that child table on `parent` node contains all the entries in `children` list and the child
+ table entry's mode value matches the children Thread mode.
+ """
+ child_table = wpan.parse_child_table_result(parent.get(wpan.WPAN_THREAD_CHILD_TABLE))
+ verify(len(child_table) == len(children))
+ for child in children:
+ ext_addr = child.get(wpan.WPAN_EXT_ADDRESS)[1:-1]
+ for entry in child_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))
+ exit(1)
+
+ verify(int(entry.rloc16, 16) == int(child.get(wpan.WPAN_THREAD_RLOC16), 16))
+ mode = int(child.get(wpan.WPAN_THREAD_DEVICE_MODE), 0)
+ verify(entry.is_rx_on_when_idle() == (mode & wpan.THREAD_MODE_FLAG_RX_ON_WHEN_IDLE != 0))
+ verify(entry.is_ftd() == (mode & wpan.THREAD_MODE_FLAG_FULL_THREAD_DEV != 0))
+ verify(entry.is_full_net_data() == (mode & wpan.THREAD_MODE_FLAG_FULL_NETWORK_DATA != 0))
+ verify(entry.is_sec_data_req() == (mode & wpan.THREAD_MODE_FLAG_SECURE_DATA_REQUEST != 0))
+
+#-----------------------------------------------------------------------------------------------------------------------
+# Creating `wpan.Nodes` instances
+
+speedup = 4
+wpan.Node.set_time_speedup_factor(speedup)
+
+parent = wpan.Node()
+child1 = wpan.Node()
+child2 = wpan.Node()
+
+#-----------------------------------------------------------------------------------------------------------------------
+# Init all nodes
+
+wpan.Node.init_all_nodes()
+
+#-----------------------------------------------------------------------------------------------------------------------
+# Build network topology
+#
+
+parent.form("ModeChangeReset")
+
+child1.join_node(parent, wpan.JOIN_TYPE_END_DEVICE)
+child2.join_node(parent, wpan.JOIN_TYPE_SLEEPY_END_DEVICE)
+
+child1.set(wpan.WPAN_POLL_INTERVAL, '8000')
+child2.set(wpan.WPAN_POLL_INTERVAL, '8000')
+
+children = [child1, child2]
+
+#-----------------------------------------------------------------------------------------------------------------------
+# Test implementation
+
+WAIT_INTERVAL = 6
+
+# Thread Mode for end-device and sleepy end-device
+DEVICE_MODE_SLEEPY_END_DEVICE = wpan.THREAD_MODE_FLAG_FULL_NETWORK_DATA | wpan.THREAD_MODE_FLAG_SECURE_DATA_REQUEST
+DEVICE_MODE_END_DEVICE = wpan.THREAD_MODE_FLAG_FULL_NETWORK_DATA | wpan.THREAD_MODE_FLAG_FULL_THREAD_DEV \
+ | wpan.THREAD_MODE_FLAG_SECURE_DATA_REQUEST | wpan.THREAD_MODE_FLAG_RX_ON_WHEN_IDLE
+
+# Disable child supervision on all devices
+parent.set(wpan.WPAN_CHILD_SUPERVISION_INTERVAL, '0')
+child1.set(wpan.WPAN_CHILD_SUPERVISION_CHECK_TIMEOUT, '0')
+child2.set(wpan.WPAN_CHILD_SUPERVISION_CHECK_TIMEOUT, '0')
+
+# Verify Thread Device Mode on both children
+verify(int(child1.get(wpan.WPAN_THREAD_DEVICE_MODE),0) == DEVICE_MODE_END_DEVICE)
+verify(int(child2.get(wpan.WPAN_THREAD_DEVICE_MODE),0) == DEVICE_MODE_SLEEPY_END_DEVICE)
+
+def check_child_table():
+ verify_child_table(parent, children)
+
+wpan.verify_within(check_child_table, WAIT_INTERVAL)
+
+# Reset parent and verify all children are recovered
+parent.reset()
+wpan.verify_within(check_child_table, WAIT_INTERVAL)
+
+# Change mode on both children (make child1 sleepy, and child2 non-sleepy)
+child1.set(wpan.WPAN_THREAD_DEVICE_MODE, str(DEVICE_MODE_SLEEPY_END_DEVICE))
+verify(int(child1.get(wpan.WPAN_THREAD_DEVICE_MODE),0) == DEVICE_MODE_SLEEPY_END_DEVICE)
+
+child2.set(wpan.WPAN_THREAD_DEVICE_MODE, str(DEVICE_MODE_END_DEVICE))
+verify(int(child2.get(wpan.WPAN_THREAD_DEVICE_MODE),0) == DEVICE_MODE_END_DEVICE)
+
+# Verify that the child table on parent is also updated
+wpan.verify_within(check_child_table, WAIT_INTERVAL)
+
+# Reset parent and verify all children are recovered
+parent.reset()
+wpan.verify_within(check_child_table, WAIT_INTERVAL)
+
+#-----------------------------------------------------------------------------------------------------------------------
+# Test finished
+
+wpan.Node.finalize_all_nodes()
+
+print '\'{}\' passed.'.format(test_name)
diff --git a/tests/toranj/wpan.py b/tests/toranj/wpan.py
index 0c28e27..b791283 100644
--- a/tests/toranj/wpan.py
+++ b/tests/toranj/wpan.py
@@ -183,7 +183,6 @@
NODE_TYPE_NEST_LURKER = '"nl-lurker"'
#-----------------------------------------------------------------------------------------------------------------------
-
# Node types used by `Node.join()`
JOIN_TYPE_ROUTER = 'r'
@@ -191,6 +190,14 @@
JOIN_TYPE_SLEEPY_END_DEVICE = 's'
#-----------------------------------------------------------------------------------------------------------------------
+# Bit Flags for Thread Device Mode `WPAN_THREAD_DEVICE_MODE`
+
+THREAD_MODE_FLAG_FULL_NETWORK_DATA = (1 << 0)
+THREAD_MODE_FLAG_FULL_THREAD_DEV = (1 << 1)
+THREAD_MODE_FLAG_SECURE_DATA_REQUEST = (1 << 2)
+THREAD_MODE_FLAG_RX_ON_WHEN_IDLE = (1 << 3)
+
+#-----------------------------------------------------------------------------------------------------------------------
def _log(text, new_line=True, flush=True):
sys.stdout.write(text)
@@ -1126,10 +1133,12 @@
# 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']
- self._timeout = dict['Timeout']
- self._rx_on_idle = (dict['RxOnIdle'] == 'yes')
- self._ftd = (dict['FTD'] == 'yes')
+ self._rloc16 = dict['RLOC16']
+ self._timeout = dict['Timeout']
+ self._rx_on_idle = (dict['RxOnIdle'] == 'yes')
+ self._ftd = (dict['FTD'] == 'yes')
+ self._sec_data_req = (dict['SecDataReq'] == 'yes')
+ self._full_net_data = (dict['FullNetData'] == 'yes')
@property
def ext_address(self):
@@ -1149,6 +1158,12 @@
def is_ftd(self):
return self._ftd
+ def is_sec_data_req(self):
+ return self._sec_data_req
+
+ def is_full_net_data(self):
+ return self._full_net_data
+
def __repr__(self):
return 'ChildEntry({})'.format(self.__dict__)