44
55"""Class describing simulation results."""
66
7- import collections
87import re
9- from collections .abc import Sequence
8+ from collections .abc import Mapping , Sequence
9+ from typing import TYPE_CHECKING
10+
11+ from pydantic import BaseModel , ConfigDict
1012
11- from dvsim .job .data import CompletedJobStatus
1213from dvsim .testplan import Result
1314
15+ if TYPE_CHECKING :
16+ from dvsim .job .data import CompletedJobStatus
17+
18+ __all__ = ()
19+
1420_REGEX_REMOVE = [
1521 # Remove UVM time.
1622 re .compile (r"@\s+[\d.]+\s+[np]s: " ),
6672]
6773
6874
75+ def _bucketize (fail_msg : str ) -> str :
76+ """Generalise error messages to create common error buckets."""
77+ bucket = fail_msg
78+ # Remove stuff.
79+ for regex in _REGEX_REMOVE :
80+ bucket = regex .sub ("" , bucket )
81+ # Strip stuff.
82+ for regex in _REGEX_STRIP :
83+ bucket = regex .sub (r"\g<1>" , bucket )
84+ # Replace with '*'.
85+ for regex in _REGEX_STAR :
86+ bucket = regex .sub ("*" , bucket )
87+
88+ return bucket
89+
90+
91+ class JobFailureOverview (BaseModel ):
92+ """Overview of the Job failure."""
93+
94+ model_config = ConfigDict (frozen = True , extra = "forbid" )
95+
96+ name : str
97+ """Name of the job."""
98+
99+ seed : int | None
100+ """Test seed."""
101+
102+ line : int | None
103+ """Line number within the log if there is one."""
104+
105+ log_context : Sequence [str ]
106+ """Context within the log."""
107+
108+
109+ class BucketedFailures (BaseModel ):
110+ """Bucketed failed runs.
111+
112+ The runs are grouped into failure buckets based on the error messages they
113+ reported. This makes it easier to see the classes of errors.
114+ """
115+
116+ model_config = ConfigDict (frozen = True , extra = "forbid" )
117+
118+ buckets : Mapping [str , Sequence ["JobFailureOverview" ]]
119+ """Mapping of common error message strings to the full job failure summary."""
120+
121+ @staticmethod
122+ def from_job_status (results : Sequence ["CompletedJobStatus" ]) -> "BucketedFailures" :
123+ """Construct from CompletedJobStatus objects."""
124+ buckets = {}
125+
126+ for job_status in results :
127+ if job_status .status in ["F" , "K" ]:
128+ bucket = _bucketize (job_status .fail_msg .message )
129+
130+ if bucket not in buckets :
131+ buckets [bucket ] = []
132+
133+ buckets [bucket ].append (
134+ JobFailureOverview (
135+ name = job_status .full_name ,
136+ seed = job_status .seed ,
137+ line = job_status .fail_msg .line_number ,
138+ log_context = job_status .fail_msg .context ,
139+ ),
140+ )
141+
142+ return BucketedFailures (
143+ buckets = buckets ,
144+ )
145+
146+
69147class SimResults :
70148 """An object wrapping up a table of results for some tests.
71149
@@ -76,30 +154,22 @@ class SimResults:
76154 holding all failing tests with the same signature.
77155 """
78156
79- def __init__ (self , results : Sequence [CompletedJobStatus ]) -> None :
157+ def __init__ (self , results : Sequence [" CompletedJobStatus" ]) -> None :
80158 self .table = []
81- self .buckets = collections .defaultdict (list )
159+ self .buckets : Mapping [str , JobFailureOverview ] = {}
160+
82161 self ._name_to_row = {}
162+
83163 for job_status in results :
84164 self ._add_item (job_status = job_status )
85165
86- def _add_item (self , job_status : CompletedJobStatus ) -> None :
166+ def _add_item (self , job_status : " CompletedJobStatus" ) -> None :
87167 """Recursively add a single item to the table of results."""
88- if job_status .status in ["F" , "K" ]:
89- bucket = self ._bucketize (job_status .fail_msg .message )
90- self .buckets [bucket ].append (
91- (
92- job_status ,
93- job_status .fail_msg .line_number ,
94- job_status .fail_msg .context ,
95- ),
96- )
97-
98168 # Runs get added to the table directly
99169 if job_status .target == "run" :
100170 self ._add_run (job_status )
101171
102- def _add_run (self , job_status : CompletedJobStatus ) -> None :
172+ def _add_run (self , job_status : " CompletedJobStatus" ) -> None :
103173 """Add an entry to table for item."""
104174 row = self ._name_to_row .get (job_status .name )
105175 if row is None :
@@ -119,16 +189,3 @@ def _add_run(self, job_status: CompletedJobStatus) -> None:
119189 if job_status .status == "P" :
120190 row .passing += 1
121191 row .total += 1
122-
123- def _bucketize (self , fail_msg ):
124- bucket = fail_msg
125- # Remove stuff.
126- for regex in _REGEX_REMOVE :
127- bucket = regex .sub ("" , bucket )
128- # Strip stuff.
129- for regex in _REGEX_STRIP :
130- bucket = regex .sub (r"\g<1>" , bucket )
131- # Replace with '*'.
132- for regex in _REGEX_STAR :
133- bucket = regex .sub ("*" , bucket )
134- return bucket
0 commit comments