1#!/usr/bin/env python
2#
3# Copyright (c) 2020, The OpenThread Authors.
4# All rights reserved.
5#
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions are met:
8# 1. Redistributions of source code must retain the above copyright
9#    notice, this list of conditions and the following disclaimer.
10# 2. Redistributions in binary form must reproduce the above copyright
11#    notice, this list of conditions and the following disclaimer in the
12#    documentation and/or other materials provided with the distribution.
13# 3. Neither the name of the copyright holder nor the
14#    names of its contributors may be used to endorse or promote products
15#    derived from this software without specific prior written permission.
16#
17# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
21# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27# POSSIBILITY OF SUCH DAMAGE.
28#
29"""
30parse_topofile.py
31-----------------
32This script is used to parse TopologyConfig.txt file to list vendor device info
33when preparing for the Thread Certification testbed
34
35usage: parse_topofile.py [-h] [-f TOPO_FILE] [-c CASE_LIST [CASE_LIST ...]]
36
37    parse TopologyConfig file and list all devices by case
38
39    optional arguments:
40      -h, --help            show this help message and exit
41      -f TOPO_FILE          Topology config file (default: C:/GRL/Thread1.1/Thread
42                            _Harness/TestScripts/TopologyConfig.txt)
43      -c CASE_LIST [CASE_LIST ...]
44                            Test case list (e.g. 5.1.1 9.2.1, default: all)
45
46Examples:
47    1. Get case 5.1.1 vendor info
48       cmd:   python parse_topofile.py -f TopologyConfig.txt -c 5.1.1
49       output:
50       case 5.1.1:
51            role-vendor pair: [('Leader', 'ARM'), ('Router_1', 'ARM')]
52            vendor devices  : {'ARM': 2}
53
54       Testbed needed vendor devices:{'ARM': 2}
55
56    2. Get case 5.1.1 and 5.2.1 vendor info
57       cmd:    python parse_topofile.py -f TopologyConfig.txt -c 5.1.1 5.2.1
58       output:
59       case 5.1.1:
60           role-vendor pair: [('Leader', 'ARM'), ('Router_1', 'ARM')]
61           vendor devices  : {'ARM': 2}
62       case 5.2.1:
63           role-vendor pair: [('Leader', 'OpenThread'), ('REED_1', 'Kirale'), ('MED_1', 'SiLabs')]
64           vendor devices  : {'OpenThread': 1, 'Kirale': 1, 'SiLabs': 1}
65
66       Testbed needed vendor devices:{'ARM': 2, 'OpenThread': 1, 'Kirale': 1, 'SiLabs': 1}
67
68    3. Get all cases vendor info
69       cmd:    python parse_topofile.py -f TopologyConfig.txt
70       output:
71           ... ... ...
72
73       Testbed needed vendor devices:{'ARM': 4, 'NXP': 5, 'OpenThread': 5, 'Kirale': 6, 'SiLabs': 5, 'Any': 7}
74
75Notes:
76    The result is just for reference, the exact requirement per vendor may be equal or less than what is generated by the script.
77
78"""
79
80import argparse
81import logging
82import re
83from collections import Counter
84
85logging.basicConfig(format='%(message)s', level=logging.DEBUG)
86MAX_VENDOR_DEVICE = 32
87
88
89def device_calculate(topo_file, case_list):
90    testbed_vendor_dict = Counter()
91    with open(topo_file, 'r') as f:
92        for line in f:
93
94            case_vendor_dict = Counter()
95
96            if not line:
97                break
98            line = line.strip()
99
100            # line example :
101            # 5.5.1-Leader:Kirale,Router_1:OpenThread
102            try:
103                if re.match(r'\s*#.*', line):
104                    continue
105
106                matched_case = re.match(r'(.*)-(.*)', line, re.M | re.I)
107
108                if 'all' not in case_list and matched_case.group(1) not in case_list:
109                    continue
110
111                logging.info('case %s:' % matched_case.group(1))
112                if 'all' not in case_list:
113                    case_list.remove(matched_case.group(1))
114                role_vendor_str = matched_case.group(2)
115                role_vendor_raw_list = re.split(',', role_vendor_str)
116                role_vendor_list = []
117
118                for device_pair in role_vendor_raw_list:
119                    device_pair = re.split(':', device_pair)
120                    role_vendor_list.append(tuple(device_pair))
121                logging.info('\trole-vendor pair: %s' % role_vendor_list)
122            except Exception as e:
123                logging.info('Unrecognized format: %s\n%s' % (line, format(e)))
124                raise
125
126            for _, vendor in role_vendor_list:
127                case_vendor_dict[vendor] += 1
128                testbed_vendor_dict[vendor] = max(testbed_vendor_dict[vendor], case_vendor_dict[vendor])
129
130            logging.info('\tvendor devices  : %s' % dict(case_vendor_dict))
131
132    if case_list and 'all' not in case_list:
133        logging.info('Case %s not found' % str(case_list)[1:-1])
134
135    # vendor 'Any' stands for any other vendors
136    # override 'Any' counter when overlapping with other vendors
137    count_any = MAX_VENDOR_DEVICE
138    for key in testbed_vendor_dict:
139        if key != 'Any':
140            count_any -= testbed_vendor_dict[key]
141        if 'Any' in testbed_vendor_dict:
142            testbed_vendor_dict['Any'] = count_any
143
144    logging.info('\nTestbed needed vendor devices:%s' % dict(testbed_vendor_dict))
145
146
147def main():
148    parser = argparse.ArgumentParser(description='parse TopologyConfig file and list all devices by case')
149    parser.add_argument(
150        '-f',
151        dest='topo_file',
152        default='C:/GRL/Thread1.1/Thread_Harness/TestScripts/TopologyConfig.txt',
153        help='Topology config file (default: C:/GRL/Thread1.1/Thread_Harness/TestScripts/TopologyConfig.txt)',
154    )
155
156    parser.add_argument('-c',
157                        dest='case_list',
158                        nargs='+',
159                        default=['all'],
160                        help='Test case list (e.g. 5.1.1 9.2.1, default: all) ')
161    args = parser.parse_args()
162    device_calculate(args.topo_file, args.case_list)
163
164
165if __name__ == '__main__':
166    main()
167