add missing import Statement for assertRepr
[nmutil.git] / src / nmutil / formaltest.py
1 import re
2 import shutil
3 import subprocess
4 import textwrap
5 import unittest
6 import warnings
7 from contextlib import contextmanager
8 from nmigen.hdl.ast import Statement
9 from nmigen.hdl.ir import Fragment
10 from nmigen.back import rtlil
11 from nmigen._toolchain import require_tool
12 from nmutil.get_test_path import get_test_path
13
14
15 __all__ = ["FHDLTestCase"]
16
17
18 class FHDLTestCase(unittest.TestCase):
19 def assertRepr(self, obj, repr_str):
20 if isinstance(obj, list):
21 obj = Statement.cast(obj)
22
23 def prepare_repr(repr_str):
24 repr_str = re.sub(r"\s+", " ", repr_str)
25 repr_str = re.sub(r"\( (?=\()", "(", repr_str)
26 repr_str = re.sub(r"\) (?=\))", ")", repr_str)
27 return repr_str.strip()
28 self.assertEqual(prepare_repr(repr(obj)), prepare_repr(repr_str))
29
30 @contextmanager
31 def assertRaises(self, exception, msg=None):
32 with super().assertRaises(exception) as cm:
33 yield
34 if msg is not None:
35 # WTF? unittest.assertRaises is completely broken.
36 self.assertEqual(str(cm.exception), msg)
37
38 @contextmanager
39 def assertRaisesRegex(self, exception, regex=None):
40 with super().assertRaises(exception) as cm:
41 yield
42 if regex is not None:
43 # unittest.assertRaisesRegex also seems broken...
44 self.assertRegex(str(cm.exception), regex)
45
46 @contextmanager
47 def assertWarns(self, category, msg=None):
48 with warnings.catch_warnings(record=True) as warns:
49 yield
50 self.assertEqual(len(warns), 1)
51 self.assertEqual(warns[0].category, category)
52 if msg is not None:
53 self.assertEqual(str(warns[0].message), msg)
54
55 def assertFormal(self, spec, mode="bmc", depth=1, solver="",
56 base_path="formal_test_temp"):
57 path = get_test_path(self, base_path)
58
59 # The sby -f switch seems not fully functional when sby is
60 # reading from stdin.
61 shutil.rmtree(path, ignore_errors=True)
62 path.mkdir(parents=True)
63
64 if mode == "hybrid":
65 # A mix of BMC and k-induction, as per personal
66 # communication with Clifford Wolf.
67 script = "setattr -unset init w:* a:nmigen.sample_reg %d"
68 mode = "bmc"
69 else:
70 script = ""
71
72 config = textwrap.dedent("""\
73 [options]
74 mode {mode}
75 depth {depth}
76 wait on
77
78 [engines]
79 smtbmc {solver}
80
81 [script]
82 read_ilang top.il
83 prep
84 {script}
85
86 [file top.il]
87 {rtlil}
88 """).format(
89 mode=mode,
90 depth=depth,
91 solver=solver,
92 script=script,
93 rtlil=rtlil.convert(Fragment.get(spec, platform="formal"))
94 )
95 with subprocess.Popen([require_tool("sby"), "-d", "job"],
96 cwd=path,
97 universal_newlines=True,
98 stdin=subprocess.PIPE,
99 stdout=subprocess.PIPE) as proc:
100 stdout, stderr = proc.communicate(config)
101 if proc.returncode != 0:
102 self.fail("Formal verification failed:\n" + stdout)