#!/usr/bin/env python3 # DESCRIPTION: Verilator: Primitive C++ style checker # # Copyright 2024 by Wilson Snyder. This program is free software; you # can redistribute it and/or modify it under the terms of either the GNU # Lesser General Public License Version 3 or the Perl Artistic License # Version 2.0. # SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 import vltest_bootstrap test.scenarios('dist') Doc_Waivers = [ '+verilator+prof+threads+file+', # Deprecated '+verilator+prof+threads+start+', # Deprecated '+verilator+prof+threads+window+', # Deprecated '-clk', # Deprecated '-lineno', # Deprecated '-order-clock-delay', # Deprecated '-prof-threads', # Deprecated ] Test_Waivers = [ # Covered: '-G', # Covered; other text fallows option letter '-O', # Covered; other text fallows option letter '-U', # Covered; other text fallows option letter '-gdb', # Covered: no way to test, part of --gdbbt '-rr', # Not testing; not requiring rr installation # Need testing: '-fconst', # TODO breaks due to some needed V3Const steps ] Sums = {} Docs = {} Srcs = {} Tests = {} def read_sums(): for filename in test.glob_some(test.root + "/bin/*"): with open(filename, "r", encoding="latin-1") as fh: on = False lineno = 0 for line in fh: lineno += 1 line = line.rstrip() m1 = re.search(r'^\s+((-|\+)+[^ ]+)', line) m2 = re.search(r"parser.add_argument\('((-|\+)[^']+)'", line) if re.search(r'ARGUMENT SUMMARY', line): on = True elif re.search(r'=head1', line): on = False elif on and m1: opt = opt_clean(m1.group(1)) key = opt_key(opt) if test.verbose: print("A '" + opt + "' " + line) Sums[key] = filename + ":" + str(lineno) + ": " + opt elif m2: opt = opt_clean(m2.group(1)) key = opt_key(opt) if test.verbose: print("A '" + opt + "' " + line) Sums[key] = filename + ":" + str(lineno) + ": " + opt def read_docs(): for filename in test.glob_some(test.root + "/docs/guide/*.rst"): with open(filename, "r", encoding="latin-1") as fh: lineno = 0 for line in fh: lineno += 1 line = line.rstrip() m = re.search(r'option:: ((-|\+)+[^ `]+)', line) if not m: m = re.search(r':vlopt:`[^`]+ <([^>]+)>', line) if not m: m = re.search(r':vlopt:`((-|\+)+[^ `]+)', line) if m: opt = opt_clean(m.group(1)) key = opt_key(opt) if test.verbose: print("D '" + opt + "' " + line) Docs[key] = filename + ":" + str(lineno) + ": " + opt def read_srcs(): filename = "bin/verilator --debug-options" opts = test.run_capture("perl " + os.environ["VERILATOR_ROOT"] + "/bin/verilator" + " --debug-options 2>&1") lineno = 0 for line in opts.split("\n"): lineno += 1 m1 = re.search(r'OPTION: "([^"]+)"', line) if m1: opt = opt_clean(m1.group(1)) key = opt_key(opt) if re.match(r'-Werror-[A-Z ]', opt): continue if re.match(r'-Wno-[A-Z ]', opt): continue if re.match(r'-Wwarn-[A-Z ]', opt): continue if test.verbose: print("S '" + opt + "' " + line) Srcs[key] = filename + ":" + str(lineno) + ": " + opt if len(Srcs) < 5: test.error("Didn't parse any options") return Srcs def read_tests(): filename = "test_regress/t/*.py" for filename in (test.glob_some(test.root + "/test_regress/t/*.py")): if "t_dist_docs_options" in filename: # Avoid our own suppressions continue with open(filename, 'r', encoding="latin-1") as fh: lineno = 0 for line in fh: lineno += 1 line = line.lstrip().rstrip() for opt in re.findall(r'[-+]+[-+a-zA-Z0-9]+', line): opt = opt_clean(opt) key = opt_key(opt) if test.verbose: print("T '" + opt + "' " + line) Tests[key] = filename + ":" + str(lineno) + ": " + opt # For e.g. +verilator+seed+ and -dumpi-<#> pos = max(opt.rfind('+'), opt.rfind('-')) if pos > 1: subkey = opt[:pos + 1] if test.verbose: print("t '" + subkey + "' " + line) Tests[subkey] = filename + ":" + str(lineno) + ": " + opt def opt_clean(opt): opt = re.sub(r'--', '-', opt) opt = re.sub(r'<.*', '', opt) opt = re.sub(r'\\', '', opt) return opt def opt_key(opt): opt = opt_clean(opt) opt = re.sub(r'^-fno-', '-f', opt) opt = re.sub(r'^-no-', '-', opt) return opt if not os.path.exists(test.root + "/.git"): test.skip("Not in a git repository") read_sums() read_docs() read_srcs() read_tests() both = {} both.update(Sums) both.update(Docs) both.update(Srcs) doc_waiver = {k: 1 for k in Doc_Waivers} for opt in sorted(both.keys()): if opt in doc_waiver: continue is_in = [] not_in = [] summ = None if opt in Sums: summ = Sums[opt] is_in.append("summary " + summ) else: if not re.match(r'-f', opt): not_in.append("ARGUMENT SUMMARY in bin/verilator or bin/verilator_coverage") doc = None if opt in Docs: doc = Docs[opt] is_in.append("documentation " + doc) else: not_in.append("documentation in docs/guide/exe_*.rst") src = None if opt in Srcs: src = Srcs[opt] is_in.append("sources " + src) # Ok if not in sources if opt in Tests: if opt in Test_Waivers: print("Unnecessary Test_Waiver for option: '" + opt + "'") else: if opt in Test_Waivers: is_in.append("Test_Waiver for test_regress/t/*.py") else: not_in.append("uncovered in test_regress/t/*.py") if not_in: test.error_keep_going("Option '" + opt + "' has inconsistent references\n" + " Missing in " + "\n Missing in ".join(not_in) + "\n Found in " + "\n Found in ".join(is_in)) test.passes()