blob: 903660ccdf2820166719cf4965a884d2f657a52c [file] [log] [blame]
Srikrishna Iyer09a81e92019-12-30 10:47:57 -08001#!/usr/bin/env python3
2# Copyright lowRISC contributors.
3# Licensed under the Apache License, Version 2.0, see LICENSE for details.
4# SPDX-License-Identifier: Apache-2.0
5r"""
6Classes
7"""
8
9import logging as log
10import pprint
Srikrishna Iyer7cf7cad2020-01-08 11:32:53 -080011import random
Srikrishna Iyer09a81e92019-12-30 10:47:57 -080012import re
13import shlex
14import sys
15import time
16
17import hjson
Srikrishna Iyer2a710a42020-02-10 10:39:15 -080018from tabulate import tabulate
Srikrishna Iyer09a81e92019-12-30 10:47:57 -080019
20from .utils import *
21
22
23class Deploy():
24 """
25 Abstraction for deploying builds and runs.
26 """
27
Srikrishna Iyer7cf7cad2020-01-08 11:32:53 -080028 # Maintain a list of dispatched items.
Srikrishna Iyer09a81e92019-12-30 10:47:57 -080029 dispatch_counter = 0
Srikrishna Iyer7cf7cad2020-01-08 11:32:53 -080030
31 # Misc common deploy settings.
Srikrishna Iyer09a81e92019-12-30 10:47:57 -080032 print_interval = 5
Srikrishna Iyer3d93afb2020-01-22 17:13:04 -080033 max_parallel = 16
Srikrishna Iyer09a81e92019-12-30 10:47:57 -080034 max_odirs = 5
35
36 def __self_str__(self):
37 if log.getLogger().isEnabledFor(VERBOSE):
38 return pprint.pformat(self.__dict__)
39 else:
40 ret = self.cmd
41 if self.sub != []: ret += "\nSub:\n" + str(self.sub)
42 return ret
43
44 def __str__(self):
45 return self.__self_str__()
46
47 def __repr__(self):
48 return self.__self_str__()
49
Srikrishna Iyer7cf7cad2020-01-08 11:32:53 -080050 def __init__(self, sim_cfg):
51 # Cross ref the whole cfg object for ease.
52 self.sim_cfg = sim_cfg
53
Srikrishna Iyer09a81e92019-12-30 10:47:57 -080054 # Common vars
55 self.cmd = ""
56 self.odir = ""
57 self.log = ""
Srikrishna Iyer4f0b0902020-01-25 02:28:47 -080058 self.fail_msg = ""
Srikrishna Iyer09a81e92019-12-30 10:47:57 -080059
60 # Flag to indicate whether to 'overwrite' if odir already exists,
61 # or to backup the existing one and create a new one.
62 # For builds, we want to overwrite existing to leverage the tools'
63 # incremental / partition compile features. For runs, we may want to
64 # create a new one.
65 self.renew_odir = False
66
67 # List of vars required to be exported to sub-shell
68 self.exports = {}
69
70 # Deploy sub commands
71 self.sub = []
72
73 # Process
74 self.process = None
75 self.log_fd = None
76 self.status = None
77
Srikrishna Iyer2a710a42020-02-10 10:39:15 -080078 # These are command, output directory and log file
Srikrishna Iyer09a81e92019-12-30 10:47:57 -080079 self.mandatory_misc_attrs.update({
80 "name": False,
81 "build_mode": False,
82 "flow_makefile": False,
83 "exports": False,
84 "dry_run": False
85 })
86
Srikrishna Iyer7cf7cad2020-01-08 11:32:53 -080087 # Function to parse a dict and extract the mandatory cmd and misc attrs.
88 def parse_dict(self, ddict):
Srikrishna Iyer09a81e92019-12-30 10:47:57 -080089 if not hasattr(self, "target"):
90 log.error(
91 "Class %s does not have the mandatory attribute \"target\" defined",
92 self.__class__.__name__)
93 sys.exit(1)
94
95 ddict_keys = ddict.keys()
96 for key in self.mandatory_cmd_attrs.keys():
97 if self.mandatory_cmd_attrs[key] == False:
98 if key in ddict_keys:
99 setattr(self, key, ddict[key])
100 self.mandatory_cmd_attrs[key] = True
101
102 for key in self.mandatory_misc_attrs.keys():
103 if self.mandatory_misc_attrs[key] == False:
104 if key in ddict_keys:
105 setattr(self, key, ddict[key])
106 self.mandatory_misc_attrs[key] = True
107
108 def __post_init__(self):
109 # Ensure all mandatory attrs are set
110 for attr in self.mandatory_cmd_attrs.keys():
111 if self.mandatory_cmd_attrs[attr] is False:
112 log.error("Attribute \"%s\" not found for \"%s\".", attr,
113 self.name)
114 sys.exit(1)
115
116 for attr in self.mandatory_misc_attrs.keys():
117 if self.mandatory_misc_attrs[attr] is False:
118 log.error("Attribute \"%s\" not found for \"%s\".", attr,
119 self.name)
120 sys.exit(1)
121
122 # Recursively search and replace wildcards
123 self.__dict__ = find_and_substitute_wildcards(self.__dict__,
124 self.__dict__)
125
126 # Set the command, output dir and log
127 self.odir = getattr(self, self.target + "_dir")
128 # Set the output dir link name to the basename of odir (by default)
129 self.odir_ln = os.path.basename(os.path.normpath(self.odir))
130 self.log = self.odir + "/" + self.target + ".log"
131
132 # If using LSF, redirect stdout and err to the log file
133 self.cmd = self.construct_cmd()
134
135 def construct_cmd(self):
136 cmd = "make -f " + self.flow_makefile + " " + self.target
137 if self.dry_run is True:
138 cmd += " -n"
139 for attr in self.mandatory_cmd_attrs.keys():
140 value = getattr(self, attr)
141 if type(value) is list:
142 pretty_value = []
143 for item in value:
144 pretty_value.append(item.strip())
145 value = " ".join(pretty_value)
146 if type(value) is bool:
147 value = int(value)
148 if type(value) is str:
149 value = value.strip()
150 cmd += " " + attr + "=\"" + str(value) + "\""
151
152 # TODO: If not running locally, redirect stdout and err to the log file
153 # self.cmd += " > " + self.log + " 2>&1 &"
154 return cmd
155
156 def dispatch_cmd(self):
157 self.exports.update(os.environ)
158 args = shlex.split(self.cmd)
159 try:
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800160 # If renew_odir flag is True - then move it.
161 if self.renew_odir: self.odir_limiter(odir=self.odir)
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800162 os.system("mkdir -p " + self.odir)
Srikrishna Iyer544da8d2020-01-14 23:51:41 -0800163 os.system("ln -s " + self.odir + " " + self.sim_cfg.links['D'] +
164 '/' + self.odir_ln)
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800165 f = open(self.log, "w")
166 self.process = subprocess.Popen(args,
Srikrishna Iyer3d93afb2020-01-22 17:13:04 -0800167 bufsize=4096,
168 universal_newlines=True,
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800169 stdout=f,
170 stderr=f,
171 env=self.exports)
172 self.log_fd = f
173 self.status = "."
174 Deploy.dispatch_counter += 1
175 except IOError:
176 log.error('IO Error: See %s', self.log)
177 if self.log_fd: self.log_fd.close()
178 self.status = "K"
179
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800180 def odir_limiter(self, odir, max_odirs=-1):
181 '''Function to backup previously run output directory to maintain a
182 history of a limited number of output directories. It deletes the output
183 directory with the oldest timestamps, if the limit is reached. It returns
184 a list of directories that remain after deletion.
185 Arguments:
186 odir: The output directory to backup
187 max_odirs: Maximum output directories to maintain as history.
188
189 Returns:
190 dirs: Space-separated list of directories that remain after deletion.
191 '''
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800192 try:
193 # If output directory exists, back it up.
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800194 if os.path.exists(odir):
Srikrishna Iyer64009052020-01-13 11:27:39 -0800195 ts = run_cmd("date '+" + self.sim_cfg.ts_format + "' -d \"" +
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800196 "$(stat -c '%y' " + odir + ")\"")
197 os.system('mv ' + odir + " " + odir + "_" + ts)
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800198 except IOError:
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800199 log.error('Failed to back up existing output directory %s', odir)
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800200
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800201 dirs = ""
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800202 # Delete older directories.
203 try:
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800204 pdir = os.path.realpath(odir + "/..")
205 # Fatal out if pdir got set to root.
206 if pdir == "/":
207 log.fatal(
208 "Something went wrong while processing \"%s\": odir = \"%s\"",
209 self.name, odir)
210 sys.exit(1)
211
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800212 if os.path.exists(pdir):
213 find_cmd = "find " + pdir + " -mindepth 1 -maxdepth 1 -type d "
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800214 dirs = run_cmd(find_cmd)
215 dirs = dirs.replace('\n', ' ')
216 list_dirs = dirs.split()
217 num_dirs = len(list_dirs)
218 if max_odirs == -1: max_odirs = self.max_odirs
219 num_rm_dirs = num_dirs - max_odirs
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800220 if num_rm_dirs > -1:
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800221 rm_dirs = run_cmd(find_cmd +
222 "-printf '%T+ %p\n' | sort | head -n " +
223 str(num_rm_dirs + 1) +
224 " | awk '{print $2}'")
225 rm_dirs = rm_dirs.replace('\n', ' ')
226 dirs = dirs.replace(rm_dirs, "")
227 os.system("/usr/bin/rm -rf " + rm_dirs)
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800228 except IOError:
229 log.error("Failed to delete old run directories!")
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800230 return dirs
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800231
232 def set_status(self):
233 self.status = 'P'
234 if self.dry_run is False:
Srikrishna Iyer4f0b0902020-01-25 02:28:47 -0800235 for fail_pattern in self.fail_patterns:
236 grep_cmd = "grep -m 1 -E \'" + fail_pattern + "\' " + self.log
237 (status, rslt) = subprocess.getstatusoutput(grep_cmd + " -c")
238 if rslt != "0":
239 (status, rslt) = subprocess.getstatusoutput(grep_cmd)
240 msg = "```\n{}\n```\n".format(rslt)
241 self.fail_msg += msg
242 log.log(VERBOSE, msg)
243 self.status = 'F'
244 break
245
246 # Return if status is fail - no need to look for pass patterns.
247 if self.status == 'F': return
248
249 # If fail patterns were not found, ensure pass patterns indeed were.
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800250 for pass_pattern in self.pass_patterns:
251 grep_cmd = "grep -c -m 1 -E \'" + pass_pattern + "\' " + self.log
252 (status, rslt) = subprocess.getstatusoutput(grep_cmd)
253 if rslt == "0":
Srikrishna Iyer4f0b0902020-01-25 02:28:47 -0800254 msg = "Pass pattern \"{}\" not found.<br>\n".format(
255 pass_pattern)
256 self.fail_msg += msg
257 log.log(VERBOSE, msg)
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800258 self.status = 'F'
Srikrishna Iyer4f0b0902020-01-25 02:28:47 -0800259 break
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800260
261 # Recursively set sub-item's status if parent item fails
262 def set_sub_status(self, status):
263 if self.sub == []: return
264 for sub_item in self.sub:
265 sub_item.status = status
266 sub_item.set_sub_status(status)
267
268 def link_odir(self):
269 if self.status == '.':
270 log.error("Method unexpectedly called!")
271 else:
Srikrishna Iyer228f1c12020-01-17 10:54:48 -0800272 old_link = self.sim_cfg.links['D'] + "/" + self.odir_ln
273 new_link = self.sim_cfg.links[self.status] + "/" + self.odir_ln
274 cmd = "ln -s " + self.odir + " " + new_link + "; "
275 cmd += "rm " + old_link
276 try:
277 os.system(cmd)
278 except Exception as e:
279 log.error("Cmd \"%s\" could not be run", cmd)
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800280
281 def get_status(self):
282 if self.status != ".": return
283 if self.process.poll() is not None:
284 self.log_fd.close()
285 if self.process.returncode != 0:
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800286 msg = "Last 10 lines of the log:<br>\n"
Srikrishna Iyer4f0b0902020-01-25 02:28:47 -0800287 self.fail_msg += msg
288 log.log(VERBOSE, msg)
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800289 get_fail_msg_cmd = "tail -n 10 " + self.log
Srikrishna Iyer4f0b0902020-01-25 02:28:47 -0800290 msg = run_cmd(get_fail_msg_cmd)
291 msg = "```\n{}\n```\n".format(msg)
292 self.fail_msg += msg
293 log.log(VERBOSE, msg)
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800294 self.status = "F"
295 else:
296 self.set_status()
297
298 log.log(VERBOSE, "Item %s has completed execution: %s", self.name,
299 self.status)
300 Deploy.dispatch_counter -= 1
301 self.link_odir()
Srikrishna Iyer3d93afb2020-01-22 17:13:04 -0800302 del self.process
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800303
304 @staticmethod
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800305 def deploy(items):
Srikrishna Iyer3d93afb2020-01-22 17:13:04 -0800306 dispatched_items = []
Srikrishna Iyer4f0b0902020-01-25 02:28:47 -0800307
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800308 def dispatch_items(items):
309 item_names = {}
310 for item in items:
311 if item.target not in item_names.keys():
312 item_names[item.target] = "["
313 if item.status is None:
314 item_names[item.target] += " "
315 if log.getLogger().isEnabledFor(VERBOSE):
316 item_names[
317 item.target] += item.name + ":" + item.log + ",\n"
318 else:
319 item_names[item.target] += item.odir_ln + ", "
320 item.dispatch_cmd()
Srikrishna Iyer3d93afb2020-01-22 17:13:04 -0800321 dispatched_items.append(item)
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800322
323 for target in item_names.keys():
324 if item_names[target] != "[":
325 item_names[target] = " [" + item_names[target][3:]
326 item_names[target] = item_names[target][:-2] + "]"
327 log.info("[dvsim]: %s:\n%s", target, item_names[target])
328
329 # Dispatch the given items
330 dispatch_items_queue = []
331 if len(items) > Deploy.max_parallel:
Michael Schaffner8ac6c4c2020-03-03 15:00:20 -0800332 dispatch_items(items[0:Deploy.max_parallel])
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800333 dispatch_items_queue = items[Deploy.max_parallel:]
334 else:
335 dispatch_items(items)
336
Srikrishna Iyer3d93afb2020-01-22 17:13:04 -0800337 all_done = False
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800338 num_secs = 0
339 status = {}
340 status_str = {}
Srikrishna Iyer544da8d2020-01-14 23:51:41 -0800341 status_str_prev = {}
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800342
Srikrishna Iyer3d93afb2020-01-22 17:13:04 -0800343 while not all_done:
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800344 time.sleep(1)
345 num_secs += 1
Srikrishna Iyere19f42b2020-01-08 17:51:36 -0800346 trig_print = ((num_secs % Deploy.print_interval) == 0)
Srikrishna Iyer3d93afb2020-01-22 17:13:04 -0800347 for item in dispatched_items:
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800348 if item.target not in status.keys():
349 status[item.target] = {}
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800350 if item not in status[item.target].keys():
351 status[item.target][item] = ""
352
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800353 if item.status == ".": item.get_status()
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800354 if item.status != status[
355 item.target][item] and item.status != ".":
356 trig_print = True
357 if item.status != "P":
358 # Kill sub items
359 item.set_sub_status("K")
360 dispatch_items_queue.extend(item.sub)
361 status[item.target][item] = item.status
362
363 # Dispatch more from the queue
364 if len(dispatch_items_queue) == 0:
Srikrishna Iyer3d93afb2020-01-22 17:13:04 -0800365 all_done = True
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800366 else:
367 num_slots = Deploy.max_parallel - Deploy.dispatch_counter
Srikrishna Iyere19f42b2020-01-08 17:51:36 -0800368 if num_slots > 0:
Srikrishna Iyere19f42b2020-01-08 17:51:36 -0800369 if len(dispatch_items_queue) > num_slots:
370 dispatch_items(dispatch_items_queue[0:num_slots])
371 dispatch_items_queue = dispatch_items_queue[num_slots:]
372 else:
373 dispatch_items(dispatch_items_queue)
374 dispatch_items_queue = []
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800375
Srikrishna Iyer544da8d2020-01-14 23:51:41 -0800376 status_str_prev = status_str.copy()
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800377 status_str = {}
378 for target in status.keys():
379 if target not in status_str.keys(): status_str[target] = "["
380 for item in status[target].keys():
381 if status[target][item] is not None:
382 status_str[target] += status[target][item]
383 if status[target][item] == ".":
Srikrishna Iyer3d93afb2020-01-22 17:13:04 -0800384 all_done = False
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800385 status_str[target] += "]"
386
387 # Print the status string periodically
Srikrishna Iyere19f42b2020-01-08 17:51:36 -0800388 if trig_print:
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800389 for target in status_str.keys():
Srikrishna Iyer544da8d2020-01-14 23:51:41 -0800390 if (target in status_str_prev.keys()) and \
391 (status_str[target] == status_str_prev[target]) and \
392 (status_str[target].find(".") == -1):
393 continue
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800394 log.info("[dvsim]: [%06ds] [%s]: %s", num_secs, target,
395 status_str[target])
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800396
397
398class CompileSim(Deploy):
399 """
400 Abstraction for building the simulation executable.
401 """
402
403 # Register all builds with the class
404 items = []
405
406 def __init__(self, build_mode, sim_cfg):
407 self.target = "build"
408 self.pass_patterns = []
409 self.fail_patterns = []
410
Srikrishna Iyer8ce80d02020-02-05 10:53:19 -0800411 self.mandatory_cmd_attrs = {
412 # tool srcs
Weicai Yang680f7e22020-02-26 18:10:24 -0800413 "tool_srcs": False,
414 "tool_srcs_dir": False,
Srikrishna Iyer8ce80d02020-02-05 10:53:19 -0800415
416 # RAL gen
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800417 "skip_ral": False,
418 "gen_ral_pkg_cmd": False,
419 "gen_ral_pkg_dir": False,
420 "gen_ral_pkg_opts": False,
421
422 # Flist gen
423 "sv_flist_gen_cmd": False,
424 "sv_flist_gen_dir": False,
425 "sv_flist_gen_opts": False,
426
427 # Build
428 "build_dir": False,
429 "build_cmd": False,
430 "build_opts": False
431 }
432
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800433 self.mandatory_misc_attrs = {"cov_db_dir": False}
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800434
435 # Initialize
Srikrishna Iyer7cf7cad2020-01-08 11:32:53 -0800436 super().__init__(sim_cfg)
437 super().parse_dict(build_mode.__dict__)
438 # Call this method again with the sim_cfg dict passed as the object,
439 # since it may contain additional mandatory attrs.
440 super().parse_dict(sim_cfg.__dict__)
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800441 self.build_mode = self.name
442 self.__post_init__()
Srikrishna Iyer4f0b0902020-01-25 02:28:47 -0800443
444 # Start fail message construction
445 self.fail_msg = "\n**BUILD:** {}<br>\n".format(self.name)
Srikrishna Iyerf578e7c2020-01-29 13:11:58 -0800446 log_sub_path = self.log.replace(self.sim_cfg.scratch_path + '/', '')
447 self.fail_msg += "**LOG:** $scratch_path/{}<br>\n".format(log_sub_path)
Srikrishna Iyer4f0b0902020-01-25 02:28:47 -0800448
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800449 CompileSim.items.append(self)
450
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800451 def dispatch_cmd(self):
452 # Delete previous cov_db_dir if it exists before dispatching new build.
453 if os.path.exists(self.cov_db_dir):
454 os.system("rm -rf " + self.cov_db_dir)
455 super().dispatch_cmd()
456
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800457
Michael Schaffner8ac6c4c2020-03-03 15:00:20 -0800458class CompileOneShot(Deploy):
459 """
460 Abstraction for building the simulation executable.
461 """
462
463 # Register all builds with the class
464 items = []
465
466 def __init__(self, build_mode, sim_cfg):
467 self.target = "build"
468 self.pass_patterns = []
469 self.fail_patterns = []
470
471 self.mandatory_cmd_attrs = {
472 # tool srcs
473 "tool_srcs": False,
474 "tool_srcs_dir": False,
475
476 # Build
477 "build_dir": False,
478 "build_cmd": False,
479 "build_opts": False,
480
481 # Report processing
482 "report_cmd": False,
483 "report_opts": False
484 }
485
486 self.mandatory_misc_attrs = {}
487
488 # Initialize
489 super().__init__(sim_cfg)
490 super().parse_dict(build_mode.__dict__)
491 # Call this method again with the sim_cfg dict passed as the object,
492 # since it may contain additional mandatory attrs.
493 super().parse_dict(sim_cfg.__dict__)
494 self.build_mode = self.name
495 self.__post_init__()
496
497 # Start fail message construction
498 self.fail_msg = "\n**BUILD:** {}<br>\n".format(self.name)
499 log_sub_path = self.log.replace(self.sim_cfg.scratch_path + '/', '')
500 self.fail_msg += "**LOG:** $scratch_path/{}<br>\n".format(log_sub_path)
501
502 CompileOneShot.items.append(self)
503
504
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800505class RunTest(Deploy):
506 """
507 Abstraction for running tests. This is one per seed for each test.
508 """
509
Srikrishna Iyer7cf7cad2020-01-08 11:32:53 -0800510 # Initial seed values when running tests (if available).
511 seeds = []
512
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800513 # Register all runs with the class
514 items = []
515
516 def __init__(self, index, test, sim_cfg):
517 self.target = "run"
518 self.pass_patterns = []
519 self.fail_patterns = []
520
521 self.mandatory_cmd_attrs = {
Srikrishna Iyer42d032f2020-03-04 23:55:44 -0800522 "proj_root": False,
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800523 "uvm_test": False,
524 "uvm_test_seq": False,
525 "run_opts": False,
526 "sw_dir": False,
527 "sw_name": False,
Srikrishna Iyer42d032f2020-03-04 23:55:44 -0800528 "sw_build_device":False,
529 "sw_build_dir":False,
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800530 "run_dir": False,
531 "run_cmd": False,
532 "run_opts": False
533 }
534
535 self.mandatory_misc_attrs = {
536 "run_dir_name": False,
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800537 "cov_db_test_dir": False,
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800538 "pass_patterns": False,
539 "fail_patterns": False
540 }
541
542 self.index = index
Srikrishna Iyer7cf7cad2020-01-08 11:32:53 -0800543 self.seed = RunTest.get_seed()
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800544
545 # Initialize
Srikrishna Iyer7cf7cad2020-01-08 11:32:53 -0800546 super().__init__(sim_cfg)
547 super().parse_dict(test.__dict__)
548 # Call this method again with the sim_cfg dict passed as the object,
549 # since it may contain additional mandatory attrs.
550 super().parse_dict(sim_cfg.__dict__)
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800551 self.test = self.name
552 self.renew_odir = True
553 self.build_mode = test.build_mode.name
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800554 self.__post_init__()
Srikrishna Iyer4f0b0902020-01-25 02:28:47 -0800555 # For output dir link, use run_dir_name instead.
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800556 self.odir_ln = self.run_dir_name
Srikrishna Iyer4f0b0902020-01-25 02:28:47 -0800557
558 # Start fail message construction
559 self.fail_msg = "\n**TEST:** {}, ".format(self.name)
560 self.fail_msg += "**SEED:** {}<br>\n".format(self.seed)
561 log_sub_path = self.log.replace(self.sim_cfg.scratch_root + '/', '')
562 self.fail_msg += "**LOG:** {}<br>\n".format(log_sub_path)
563
Srikrishna Iyer09a81e92019-12-30 10:47:57 -0800564 RunTest.items.append(self)
Srikrishna Iyer7cf7cad2020-01-08 11:32:53 -0800565
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800566 def get_status(self):
567 '''Override base class get_status implementation for additional post-status
568 actions.'''
569 super().get_status()
570 if self.status not in [".", "P"]:
571 # Delete the coverage data if available.
572 if os.path.exists(self.cov_db_test_dir):
573 log.log(VERBOSE, "Deleting coverage data of failing test:\n%s",
574 self.cov_db_test_dir)
575 os.system("/usr/bin/rm -rf " + self.cov_db_test_dir)
576
Srikrishna Iyer7cf7cad2020-01-08 11:32:53 -0800577 @staticmethod
578 def get_seed():
Philipp Wagner9a521782020-02-25 11:49:53 +0000579 if not RunTest.seeds:
Srikrishna Iyera821bbc2020-01-31 00:28:02 -0800580 for i in range(1000):
Philipp Wagner75fd6c72020-02-25 11:47:41 +0000581 seed = random.getrandbits(32)
Srikrishna Iyera821bbc2020-01-31 00:28:02 -0800582 RunTest.seeds.append(seed)
Srikrishna Iyer7cf7cad2020-01-08 11:32:53 -0800583 return RunTest.seeds.pop(0)
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800584
585
586class CovMerge(Deploy):
587 """
588 Abstraction for merging coverage databases. An item of this class is created AFTER
589 the regression is completed.
590 """
591
592 # Register all builds with the class
593 items = []
594
595 def __init__(self, sim_cfg):
596 self.target = "cov_merge"
597 self.pass_patterns = []
598 self.fail_patterns = []
599
600 # Construct local 'special' variable from cov directories that need to
601 # be merged.
602 self.cov_db_dirs = ""
603
604 self.mandatory_cmd_attrs = {
605 "cov_merge_cmd": False,
606 "cov_merge_opts": False
607 }
608
609 self.mandatory_misc_attrs = {
610 "cov_merge_dir": False,
611 "cov_merge_db_dir": False
612 }
613
614 # Initialize
615 super().__init__(sim_cfg)
616 super().parse_dict(sim_cfg.__dict__)
617 self.__post_init__()
618
619 # Override standard output and log patterns.
620 self.odir = self.cov_merge_db_dir
621 self.odir_ln = os.path.basename(os.path.normpath(self.odir))
622
623 # Start fail message construction
624 self.fail_msg = "\n**COV_MERGE:** {}<br>\n".format(self.name)
625 log_sub_path = self.log.replace(self.sim_cfg.scratch_path + '/', '')
626 self.fail_msg += "**LOG:** $scratch_path/{}<br>\n".format(log_sub_path)
627
628 CovMerge.items.append(self)
629
630 def __post_init__(self):
631 # Add cov db dirs from all the builds that were kicked off.
632 for bld in self.sim_cfg.builds:
633 self.cov_db_dirs += bld.cov_db_dir + " "
634
635 # Recursively search and replace wildcards, ignoring cov_db_dirs for now.
636 # We need to resolve it later based on cov_db_dirs value set below.
637 self.__dict__ = find_and_substitute_wildcards(
638 self.__dict__, self.__dict__, ignored_wildcards=["cov_db_dirs"])
639
640 # Prune previous merged cov directories.
641 prev_cov_db_dirs = self.odir_limiter(odir=self.cov_merge_db_dir)
642
643 # If a merged cov data base exists from a previous run, then consider
644 # that as well for merging, if the --cov-merge-previous command line
645 # switch is passed.
646 if self.sim_cfg.cov_merge_previous:
647 self.cov_db_dirs += prev_cov_db_dirs
648
649 # Call base class __post_init__ to do checks and substitutions
650 super().__post_init__()
651
652
653class CovReport(Deploy):
654 """
655 Abstraction for coverage report generation. An item of this class is created AFTER
656 the regression is completed.
657 """
658
659 # Register all builds with the class
660 items = []
661
662 def __init__(self, sim_cfg):
663 self.target = "cov_report"
664 self.pass_patterns = []
665 self.fail_patterns = []
Weicai Yang680f7e22020-02-26 18:10:24 -0800666 self.cov_total = ""
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800667 self.cov_results = ""
668
669 self.mandatory_cmd_attrs = {
670 "cov_report_cmd": False,
671 "cov_report_opts": False
672 }
673
674 self.mandatory_misc_attrs = {
675 "cov_report_dir": False,
676 "cov_merge_db_dir": False,
677 "cov_report_dashboard": False
678 }
679
680 # Initialize
681 super().__init__(sim_cfg)
682 super().parse_dict(sim_cfg.__dict__)
683 self.__post_init__()
684
685 # Start fail message construction
686 self.fail_msg = "\n**COV_REPORT:** {}<br>\n".format(self.name)
687 log_sub_path = self.log.replace(self.sim_cfg.scratch_path + '/', '')
688 self.fail_msg += "**LOG:** $scratch_path/{}<br>\n".format(log_sub_path)
689
690 CovReport.items.append(self)
691
692 def get_status(self):
693 super().get_status()
694 # Once passed, extract the cov results summary from the dashboard.
695 if self.status == "P":
696 try:
697 with open(self.cov_report_dashboard, 'r') as f:
698 for line in f:
699 match = re.match("total coverage summary", line,
700 re.IGNORECASE)
701 if match:
702 results = []
703 # Metrics on the next line.
704 line = f.readline().strip()
705 results.append(line.split())
706 # Values on the next.
707 line = f.readline().strip()
708 # Pretty up the values - add % sign for ease of post
709 # processing.
710 values = []
711 for val in line.split():
712 val += " %"
713 values.append(val)
Weicai Yang680f7e22020-02-26 18:10:24 -0800714 # first row is coverage total
715 self.cov_total = values[0]
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800716 results.append(values)
717 colalign = (("center", ) * len(values))
718 self.cov_results = tabulate(results,
719 headers="firstrow",
720 tablefmt="pipe",
721 colalign=colalign)
722 break
723
724 except Exception as e:
725 ex_msg = "Failed to parse \"{}\":\n{}".format(
726 self.cov_report_dashboard, str(e))
Srikrishna Iyer86f6bce2020-02-27 19:02:04 -0800727 self.fail_msg += ex_msg
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800728 log.error(ex_msg)
729 self.status = "F"
730
731 if self.cov_results == "":
732 nf_msg = "Coverage summary not found in the reports dashboard!"
Srikrishna Iyer86f6bce2020-02-27 19:02:04 -0800733 self.fail_msg += nf_msg
Srikrishna Iyer2a710a42020-02-10 10:39:15 -0800734 log.error(nf_msg)
735 self.status = "F"
736
737 if self.status == "P":
738 # Delete the cov report - not needed.
739 os.system("rm -rf " + self.log)
740
741
742class CovAnalyze(Deploy):
743 """
744 Abstraction for coverage analysis tool.
745 """
746
747 # Register all builds with the class
748 items = []
749
750 def __init__(self, sim_cfg):
751 self.target = "cov_analyze"
752 self.pass_patterns = []
753 self.fail_patterns = []
754
755 self.mandatory_cmd_attrs = {
756 "cov_analyze_cmd": False,
757 "cov_analyze_opts": False
758 }
759
760 self.mandatory_misc_attrs = {
761 "cov_analyze_dir": False,
762 "cov_merge_db_dir": False
763 }
764
765 # Initialize
766 super().__init__(sim_cfg)
767 super().parse_dict(sim_cfg.__dict__)
768 self.__post_init__()
769
770 # Start fail message construction
771 self.fail_msg = "\n**COV_ANALYZE:** {}<br>\n".format(self.name)
772 log_sub_path = self.log.replace(self.sim_cfg.scratch_path + '/', '')
773 self.fail_msg += "**LOG:** $scratch_path/{}<br>\n".format(log_sub_path)
774
775 CovAnalyze.items.append(self)