3 # Copyright (c) 2017 Cisco and/or its affiliates.
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at:
8 # http://www.apache.org/licenses/LICENSE-2.0
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
17 Script extracts required data from robot framework output file (output.xml) and
18 saves it in JSON format. Its structure is:
22 "vdevice": "VPP version",
28 "parent": "Name of the parent of the test",
29 "tags": ["tag 1", "tag 2", "tag n"],
30 "type": "PDR" | "NDR",
33 "unit": "pps" | "bps" | "percentage"
42 "50": { # Only for NDR
47 "10": { # Only for NDR
59 "50": { # Only for NDR
64 "10": { # Only for NDR
71 "lossTolerance": "lossTolerance" # Only for PDR
79 .. note:: ID is the lowercase full path to the test.
83 run_robot_json_data.py -i "output.xml" -o "data.json" -v "17.07-rc0~144"
92 from robot.api import ExecutionResult, ResultVisitor
95 class ExecutionChecker(ResultVisitor):
96 """Class to traverse through the test suite structure.
98 The functionality implemented in this class generates a json structure.
101 REGEX_RATE = re.compile(r'^[\D\d]*FINAL_RATE:\s(\d+\.\d+)\s(\w+)')
103 REGEX_LAT_NDR = re.compile(r'^[\D\d]*'
104 r'LAT_\d+%NDR:\s\[\'(-?\d+\/-?\d+\/-?\d+)\','
105 r'\s\'(-?\d+\/-?\d+\/-?\d+)\'\]\s\n'
106 r'LAT_\d+%NDR:\s\[\'(-?\d+\/-?\d+\/-?\d+)\','
107 r'\s\'(-?\d+\/-?\d+\/-?\d+)\'\]\s\n'
108 r'LAT_\d+%NDR:\s\[\'(-?\d+\/-?\d+\/-?\d+)\','
109 r'\s\'(-?\d+\/-?\d+\/-?\d+)\'\]')
111 REGEX_LAT_PDR = re.compile(r'^[\D\d]*'
112 r'LAT_\d+%PDR:\s\[\'(-?\d+\/-?\d+\/-?\d+)\','
113 r'\s\'(-?\d+\/-?\d+\/-?\d+)\'\][\D\d]*')
115 REGEX_TOLERANCE = re.compile(r'^[\D\d]*LOSS_ACCEPTANCE:\s(\d*\.\d*)\s'
118 def __init__(self, **metadata):
121 :param metadata: Key-value pairs to be included to "metadata" part of
132 for key, val in metadata.items():
133 self._data["metadata"][key] = val
139 def _get_latency(self, msg, test_type):
140 """Get the latency data from the test message.
142 :param msg: Message to be parsed.
143 :param test_type: Type of the test - NDR or PDR.
146 :returns: Latencies parsed from the message.
150 if test_type == "NDR":
151 groups = re.search(self.REGEX_LAT_NDR, msg)
152 groups_range = range(1, 7)
153 elif test_type == "PDR":
154 groups = re.search(self.REGEX_LAT_PDR, msg)
155 groups_range = range(1, 3)
160 for idx in groups_range:
162 lat = [int(item) for item in str(groups.group(idx)).split('/')]
163 except (AttributeError, ValueError):
165 latencies.append(lat)
167 keys = ("min", "avg", "max")
175 latency["direction1"]["100"] = dict(zip(keys, latencies[0]))
176 latency["direction2"]["100"] = dict(zip(keys, latencies[1]))
177 if test_type == "NDR":
178 latency["direction1"]["50"] = dict(zip(keys, latencies[2]))
179 latency["direction2"]["50"] = dict(zip(keys, latencies[3]))
180 latency["direction1"]["10"] = dict(zip(keys, latencies[4]))
181 latency["direction2"]["10"] = dict(zip(keys, latencies[5]))
185 def visit_suite(self, suite):
186 """Implements traversing through the suite and its direct children.
188 :param suite: Suite to process.
192 if self.start_suite(suite) is not False:
193 suite.suites.visit(self)
194 suite.tests.visit(self)
195 self.end_suite(suite)
197 def start_suite(self, suite):
198 """Called when suite starts.
200 :param suite: Suite to process.
206 def end_suite(self, suite):
207 """Called when suite ends.
209 :param suite: Suite to process.
215 def visit_test(self, test):
216 """Implements traversing through the test.
218 :param test: Test to process.
222 if self.start_test(test) is not False:
225 def start_test(self, test):
226 """Called when test starts.
228 :param test: Test to process.
233 tags = [str(tag) for tag in test.tags]
234 if test.status == "PASS" and "NDRPDRDISC" in tags:
236 if "NDRDISC" in tags:
238 elif "PDRDISC" in tags:
244 rate_value = str(re.search(
245 self.REGEX_RATE, test.message).group(1))
246 except AttributeError:
249 rate_unit = str(re.search(
250 self.REGEX_RATE, test.message).group(2))
251 except AttributeError:
255 test_result["name"] = test.name.lower()
256 test_result["parent"] = test.parent.name.lower()
257 test_result["tags"] = tags
258 test_result["type"] = test_type
259 test_result["throughput"] = dict()
260 test_result["throughput"]["value"] = int(rate_value.split('.')[0])
261 test_result["throughput"]["unit"] = rate_unit
262 test_result["latency"] = self._get_latency(test.message, test_type)
263 if test_type == "PDR":
264 test_result["lossTolerance"] = str(re.search(
265 self.REGEX_TOLERANCE, test.message).group(1))
267 self._data["data"][test.longname.lower()] = test_result
269 def end_test(self, test):
270 """Called when test ends.
272 :param test: Test to process.
279 def parse_tests(args):
280 """Process data from robot output.xml file and return JSON data.
282 :param args: Parsed arguments.
283 :type args: ArgumentParser
284 :returns: JSON data structure.
288 result = ExecutionResult(args.input)
289 checker = ExecutionChecker(vdevice=args.vdevice)
290 result.visit(checker)
296 """Parse arguments from cmd line.
298 :returns: Parsed arguments.
299 :rtype: ArgumentParser
302 parser = argparse.ArgumentParser(description=__doc__,
303 formatter_class=argparse.
304 RawDescriptionHelpFormatter)
305 parser.add_argument("-i", "--input",
307 type=argparse.FileType('r'),
308 help="Robot XML log file.")
309 parser.add_argument("-o", "--output",
311 type=argparse.FileType('w'),
312 help="JSON output file")
313 parser.add_argument("-v", "--vdevice",
319 return parser.parse_args()
326 json_data = parse_tests(args)
327 json_data["metadata"]["data-length"] = len(json_data["data"])
328 json.dump(json_data, args.output)
330 if __name__ == "__main__":