From 9edc65874c3054b6b08404f87fa5d916e2794ee6 Mon Sep 17 00:00:00 2001 From: Clifford Wolf Date: Tue, 27 Mar 2018 16:11:43 +0200 Subject: [PATCH] Drastically improve sby error handling --- sbysrc/sby.py | 32 +++++-- sbysrc/sby_core.py | 213 +++++++++++++++++++++++++-------------------- 2 files changed, 144 insertions(+), 101 deletions(-) diff --git a/sbysrc/sby.py b/sbysrc/sby.py index e77c571..28f259a 100644 --- a/sbysrc/sby.py +++ b/sbysrc/sby.py @@ -19,7 +19,7 @@ import os, sys, getopt, shutil, tempfile ##yosys-sys-path## -from sby_core import SbyJob +from sby_core import SbyJob, SbyAbort from time import localtime sbyfile = None @@ -29,6 +29,7 @@ opt_force = False opt_backup = False opt_tmpdir = False exe_paths = dict() +throw_err = False def usage(): print(""" @@ -49,6 +50,9 @@ sby [options] [.sby [tasknames]] -T taskname add taskname (useful when sby file is read from stdin) + -E + throw an exception (incl stack trace) for most errors + --yosys --abc --smtbmc @@ -60,7 +64,7 @@ sby [options] [.sby [tasknames]] sys.exit(1) try: - opts, args = getopt.getopt(sys.argv[1:], "d:btfT:", ["yosys=", + opts, args = getopt.getopt(sys.argv[1:], "d:btfT:E", ["yosys=", "abc=", "smtbmc=", "suprove=", "aigbmc=", "avy="]) except: usage() @@ -76,6 +80,8 @@ for o, a in opts: opt_tmpdir = True elif o == "-T": tasknames.append(a) + elif o == "-E": + throw_err = True elif o == "--yosys": exe_paths["yosys"] = a elif o == "--abc": @@ -93,7 +99,9 @@ for o, a in opts: if len(args) > 0: sbyfile = args[0] - assert sbyfile.endswith(".sby") + if not sbyfile.endswith(".sby"): + print("ERROR: Sby file does not have .sby file extension.", file=sys.stderr) + sys.exit(1) if len(args) > 1: tasknames = args[1:] @@ -195,8 +203,9 @@ if len(tasknames) == 0: if len(tasknames) == 0: tasknames = [None] -assert (workdir is None) or (len(tasknames) == 1) - +if (workdir is not None) and (len(tasknames) != 1): + print("ERROR: Exactly one task is required when workdir is specified.", file=sys.stderr) + sys.exit(1) def run_job(taskname): my_workdir = workdir @@ -227,12 +236,18 @@ def run_job(taskname): my_workdir = tempfile.mkdtemp() sbyconfig, _ = read_sbyconfig(sbydata, taskname) - job = SbyJob(sbyconfig, taskname, my_workdir, early_logmsgs) + job = SbyJob(sbyconfig, my_workdir, early_logmsgs) for k, v in exe_paths.items(): job.exe_paths[k] = v - job.run() + if throw_err: + job.run() + else: + try: + job.run() + except SbyAbort: + pass if my_opt_tmpdir: job.log("Removing direcory '%s'." % (my_workdir)) @@ -244,8 +259,7 @@ def run_job(taskname): retcode = 0 for t in tasknames: - assert (t is None) or (t in tasknames) - retcode += run_job(t) + retcode |= run_job(t) sys.exit(retcode) diff --git a/sbysrc/sby_core.py b/sbysrc/sby_core.py index 8806912..e67b1c1 100644 --- a/sbysrc/sby_core.py +++ b/sbysrc/sby_core.py @@ -127,8 +127,12 @@ class SbyTask: return +class SbyAbort(BaseException): + pass + + class SbyJob: - def __init__(self, sbyconfig, taskname, workdir, early_logs): + def __init__(self, sbyconfig, workdir, early_logs): self.options = dict() self.used_options = set() self.engines = list() @@ -138,6 +142,7 @@ class SbyJob: self.models = dict() self.workdir = workdir self.status = "UNKNOWN" + self.expect = [] self.exe_paths = { "yosys": "yosys", @@ -164,94 +169,10 @@ class SbyJob: print(line, file=self.logfile) self.logfile.flush() - mode = None - key = None - with open("%s/config.sby" % workdir, "w") as f: for line in sbyconfig: print(line, file=f) - with open("%s/config.sby" % workdir, "r") as f: - for line in f: - raw_line = line - if mode in ["options", "engines", "files"]: - line = re.sub(r"\s*(\s#.*)?$", "", line) - if line == "" or line[0] == "#": - continue - else: - line = line.rstrip() - # print(line) - if mode is None and (len(line) == 0 or line[0] == "#"): - continue - match = re.match(r"^\s*\[(.*)\]\s*$", line) - if match: - entries = match.group(1).split() - assert len(entries) > 0 - - if entries[0] == "options": - mode = "options" - assert len(self.options) == 0 - assert len(entries) == 1 - continue - - if entries[0] == "engines": - mode = "engines" - assert len(self.engines) == 0 - assert len(entries) == 1 - continue - - if entries[0] == "script": - mode = "script" - assert len(self.script) == 0 - assert len(entries) == 1 - continue - - if entries[0] == "file": - mode = "file" - assert len(entries) == 2 - current_verbatim_file = entries[1] - assert current_verbatim_file not in self.verbatim_files - self.verbatim_files[current_verbatim_file] = list() - continue - - if entries[0] == "files": - mode = "files" - assert len(entries) == 1 - continue - - assert False - - if mode == "options": - entries = line.split() - assert len(entries) == 2 - self.options[entries[0]] = entries[1] - continue - - if mode == "engines": - entries = line.split() - self.engines.append(entries) - continue - - if mode == "script": - self.script.append(line) - continue - - if mode == "files": - entries = line.split() - if len(entries) == 1: - self.files[os.path.basename(entries[0])] = entries[0] - elif len(entries) == 2: - self.files[entries[0]] = entries[1] - else: - assert False - continue - - if mode == "file": - self.verbatim_files[current_verbatim_file].append(raw_line) - continue - - assert False - def taskloop(self): for task in self.tasks_all: task.poll() @@ -283,6 +204,19 @@ class SbyJob: print("SBY %2d:%02d:%02d [%s] %s" % (tm.tm_hour, tm.tm_min, tm.tm_sec, self.workdir, logmessage), file=self.logfile) self.logfile.flush() + def error(self, logmessage): + tm = localtime() + print("SBY %2d:%02d:%02d [%s] ERROR: %s" % (tm.tm_hour, tm.tm_min, tm.tm_sec, self.workdir, logmessage)) + print("SBY %2d:%02d:%02d [%s] ERROR: %s" % (tm.tm_hour, tm.tm_min, tm.tm_sec, self.workdir, logmessage), file=self.logfile) + self.logfile.flush() + self.status = "ERROR" + if "ERROR" not in self.expect: + self.retcode = 16 + self.terminate() + with open("%s/%s" % (self.workdir, self.status), "w") as f: + print("ERROR: %s" % logmessage, file=f) + raise SbyAbort(logmessage) + def copy_src(self): os.makedirs(self.workdir + "/src") @@ -323,7 +257,8 @@ class SbyJob: def handle_bool_option(self, option_name, default_value): if option_name in self.options: - assert self.options[option_name] in ["on", "off"] + if self.options[option_name] not in ["on", "off"]: + self.error("Invalid value '%s' for boolean option %s." % (self.options[option_name], option_name)) self.__dict__["opt_" + option_name] = self.options[option_name] == "on" self.used_options.add(option_name) else: @@ -446,8 +381,99 @@ class SbyJob: assert 0 def run(self): + mode = None + key = None + + with open("%s/config.sby" % self.workdir, "r") as f: + for line in f: + raw_line = line + if mode in ["options", "engines", "files"]: + line = re.sub(r"\s*(\s#.*)?$", "", line) + if line == "" or line[0] == "#": + continue + else: + line = line.rstrip() + # print(line) + if mode is None and (len(line) == 0 or line[0] == "#"): + continue + match = re.match(r"^\s*\[(.*)\]\s*$", line) + if match: + entries = match.group(1).split() + if len(entries) == 0: + self.error("sby file syntax error: %s" % line) + + if entries[0] == "options": + mode = "options" + if len(self.options) != 0 or len(entries) != 1: + self.error("sby file syntax error: %s" % line) + continue + + if entries[0] == "engines": + mode = "engines" + if len(self.engines) != 0 or len(entries) != 1: + self.error("sby file syntax error: %s" % line) + continue + + if entries[0] == "script": + mode = "script" + if len(self.script) != 0 or len(entries) != 1: + self.error("sby file syntax error: %s" % line) + continue + + if entries[0] == "file": + mode = "file" + if len(entries) != 2: + self.error("sby file syntax error: %s" % line) + current_verbatim_file = entries[1] + if current_verbatim_file in self.verbatim_files: + self.error("duplicate file: %s" % entries[1]) + self.verbatim_files[current_verbatim_file] = list() + continue + + if entries[0] == "files": + mode = "files" + if len(entries) != 1: + self.error("sby file syntax error: %s" % line) + continue + + self.error("sby file syntax error: %s" % line) + + if mode == "options": + entries = line.split() + if len(entries) != 2: + self.error("sby file syntax error: %s" % line) + self.options[entries[0]] = entries[1] + continue + + if mode == "engines": + entries = line.split() + self.engines.append(entries) + continue + + if mode == "script": + self.script.append(line) + continue + + if mode == "files": + entries = line.split() + if len(entries) == 1: + self.files[os.path.basename(entries[0])] = entries[0] + elif len(entries) == 2: + self.files[entries[0]] = entries[1] + else: + self.error("sby file syntax error: %s" % line) + continue + + if mode == "file": + self.verbatim_files[current_verbatim_file].append(raw_line) + continue + + self.error("sby file syntax error: %s" % line) + self.handle_str_option("mode", None) - assert self.opt_mode in ["bmc", "prove", "cover", "live"] + + if self.opt_mode not in ["bmc", "prove", "cover", "live"]: + self.error("Invalid mode: %s" % self.opt_mode) self.expect = ["PASS"] if "expect" in self.options: @@ -455,7 +481,8 @@ class SbyJob: self.used_options.add("expect") for s in self.expect: - assert s in ["PASS", "FAIL", "UNKNOWN", "ERROR", "TIMEOUT"] + if s not in ["PASS", "FAIL", "UNKNOWN", "ERROR", "TIMEOUT"]: + self.error("Invalid expect value: %s" % s) self.handle_bool_option("multiclock", False) self.handle_bool_option("wait", False) @@ -466,7 +493,8 @@ class SbyJob: if self.opt_smtc is not None: for engine in self.engines: - assert engine[0] == "smtbmc" + if engine[0] != "smtbmc": + self.error("Option smtc is only valid for smtbmc engine.") self.copy_src() @@ -490,7 +518,8 @@ class SbyJob: assert False for opt in self.options.keys(): - assert opt in self.used_options + if opt not in self.used_options: + self.error("Unused option: %s" % opt) self.taskloop() @@ -516,9 +545,9 @@ class SbyJob: else: if self.status == "PASS": self.retcode = 1 if self.status == "FAIL": self.retcode = 2 - if self.status == "ERROR": self.retcode = 3 if self.status == "UNKNOWN": self.retcode = 4 - if self.status == "TIMEOUT": self.retcode = 5 + if self.status == "TIMEOUT": self.retcode = 8 + if self.status == "ERROR": self.retcode = 16 with open("%s/%s" % (self.workdir, self.status), "w") as f: for line in self.summary: -- 2.30.2