| # Copyright lowRISC contributors. | |
| # Licensed under the Apache License, Version 2.0, see LICENSE for details. | |
| # SPDX-License-Identifier: Apache-2.0 | |
| r""" | |
| Class describing simulation results | |
| """ | |
| import collections | |
| import re | |
| from Testplan import Result | |
| _REGEX_REMOVE = [ | |
| # Remove UVM time. | |
| re.compile(r'@\s+[\d.]+\s+[np]s: '), | |
| re.compile(r'\[[\d.]+\s+[np]s\] '), | |
| # Remove assertion time. | |
| re.compile(r'\(time [\d.]+ [PF]S\) '), | |
| # Remove leading spaces. | |
| re.compile(r'^\s+'), | |
| # Remove extra white spaces. | |
| re.compile(r'\s+(?=\s)'), | |
| ] | |
| _REGEX_STRIP = [ | |
| # Strip TB instance name. | |
| re.compile(r'[\w_]*top\.\S+\.(\w+)'), | |
| # Strip assertion. | |
| re.compile(r'(?<=Assertion )\S+\.(\w+)'), | |
| ] | |
| # Regular expression for a separator: EOL or some of punctuation marks. | |
| _SEPARATOR_RE = '($|[ ,.:;])' | |
| _REGEX_STAR = [ | |
| # Replace hex numbers with 0x (needs to be called before other numbers). | |
| re.compile(r'0x\s*[\da-fA-F]+'), | |
| # Replace hex numbers with 'h (needs to be called before other numbers). | |
| re.compile(r'\'h\s*[\da-fA-F]+'), | |
| # Floating point numbers at the beginning of a word, example "10.1ns". | |
| # (needs to be called before other numbers). | |
| re.compile(r'(?<=[^a-zA-Z0-9])\d+\.\d+'), | |
| # Replace all isolated numbers. Isolated numbers are numbers surrounded by | |
| # special symbols, for example ':' or '+' or '_', excluding parenthesis. | |
| # So a number with a letter or a round bracket on any one side, is | |
| # considered non-isolated number and is not starred by these expressions. | |
| re.compile(r'(?<=[^a-zA-Z0-9\(\)])\d+(?=($|[^a-zA-Z0-9\(\)]))'), | |
| # Replace numbers surrounded by parenthesis after a space and followed by a | |
| # separator. | |
| re.compile(r'(?<= \()\s*\d+\s*(?=\)%s)' % _SEPARATOR_RE), | |
| # Replace hex/decimal numbers after an equal sign or a semicolon and | |
| # followed by a separator. Uses look-behind pattern which need a | |
| # fixed width, thus the apparent redundancy. | |
| re.compile(r'(?<=[\w\]][=:])[\da-fA-F]+(?=%s)' % _SEPARATOR_RE), | |
| re.compile(r'(?<=[\w\]][=:] )[\da-fA-F]+(?=%s)' % _SEPARATOR_RE), | |
| re.compile(r'(?<=[\w\]] [=:])[\da-fA-F]+(?=%s)' % _SEPARATOR_RE), | |
| re.compile(r'(?<=[\w\]] [=:] )[\da-fA-F]+(?=%s)' % _SEPARATOR_RE), | |
| # Replace decimal number at the beginning of the word. | |
| re.compile(r'(?<= )\d+(?=\S)'), | |
| # Remove decimal number at end of the word and before '=' or '[' or | |
| # ',' or '.' or '('. | |
| re.compile(r'(?<=\S)\d+(?=($|[ =\[,\.\(]))'), | |
| # Replace the instance string. | |
| re.compile(r'(?<=instance)\s*=\s*\S+'), | |
| ] | |
| class SimResults: | |
| '''An object wrapping up a table of results for some tests | |
| self.table is a list of Result objects, each of which | |
| corresponds to one or more runs of the test with a given name. | |
| self.buckets contains a dictionary accessed by the failure signature, | |
| holding all failing tests with the same signature. | |
| ''' | |
| def __init__(self, items, results): | |
| self.table = [] | |
| self.buckets = collections.defaultdict(list) | |
| self._name_to_row = {} | |
| for item in items: | |
| self._add_item(item, results) | |
| def _add_item(self, item, results): | |
| '''Recursively add a single item to the table of results''' | |
| status = results[item] | |
| if status in ["F", "K"]: | |
| bucket = self._bucketize(item.launcher.fail_msg.message) | |
| self.buckets[bucket].append( | |
| (item, item.launcher.fail_msg.line_number, | |
| item.launcher.fail_msg.context)) | |
| # Runs get added to the table directly | |
| if item.target == "run": | |
| self._add_run(item, status) | |
| def _add_run(self, item, status): | |
| '''Add an entry to table for item''' | |
| row = self._name_to_row.get(item.name) | |
| if row is None: | |
| row = Result(item.name, | |
| job_runtime=item.job_runtime, | |
| simulated_time=item.simulated_time) | |
| self.table.append(row) | |
| self._name_to_row[item.name] = row | |
| else: | |
| # Record the max job_runtime of all reseeds. | |
| if item.job_runtime > row.job_runtime: | |
| row.job_runtime = item.job_runtime | |
| row.simulated_time = item.simulated_time | |
| if status == 'P': | |
| row.passing += 1 | |
| row.total += 1 | |
| def _bucketize(self, fail_msg): | |
| bucket = fail_msg | |
| # Remove stuff. | |
| for regex in _REGEX_REMOVE: | |
| bucket = regex.sub('', bucket) | |
| # Strip stuff. | |
| for regex in _REGEX_STRIP: | |
| bucket = regex.sub(r'\g<1>', bucket) | |
| # Replace with '*'. | |
| for regex in _REGEX_STAR: | |
| bucket = regex.sub('*', bucket) | |
| return bucket |