tests: move out of the main package.

Compared to tests in the repository root, tests in the package have
many downsides:
  * Unless explicitly excluded in find_packages(), tests and their
    support code effectively become a part of public API.
    This, unfortunately, happened with FHDLTestCase, which was never
    intended for downstream use.
  * Even if explicitly excluded from the setuptools package, using
    an editable install, or setting PYTHONPATH still allows accessing
    the tests.
  * Having a sub-package that is present in the source tree but not
    exported (or, worse, exported only sometimes) is confusing.
  * The name `nmigen.test` cannot be used for anything else, such as
    testing utilities that *are* intended for downstream use.
This commit is contained in:
whitequark 2020-08-27 00:33:31 +00:00
parent ef7a3bcfb1
commit 67b957d4f4
32 changed files with 103 additions and 82 deletions

0
tests/compat/__init__.py Normal file
View file

16
tests/compat/support.py Normal file
View file

@ -0,0 +1,16 @@
from nmigen.compat import *
from nmigen.compat.fhdl import verilog
from nmigen._utils import _ignore_deprecated
class SimCase:
def setUp(self, *args, **kwargs):
with _ignore_deprecated():
self.tb = self.TestBench(*args, **kwargs)
def test_to_verilog(self):
verilog.convert(self.tb)
def run_with(self, generator):
with _ignore_deprecated():
run_simulation(self.tb, generator)

116
tests/compat/test_coding.py Normal file
View file

@ -0,0 +1,116 @@
# nmigen: UnusedElaboratable=no
import unittest
from nmigen.compat import *
from nmigen.compat.genlib.coding import *
from .support import SimCase
class EncCase(SimCase, unittest.TestCase):
class TestBench(Module):
def __init__(self):
self.submodules.dut = Encoder(8)
def test_sizes(self):
self.assertEqual(len(self.tb.dut.i), 8)
self.assertEqual(len(self.tb.dut.o), 3)
self.assertEqual(len(self.tb.dut.n), 1)
def test_run_sequence(self):
seq = list(range(1<<8))
def gen():
for _ in range(256):
if seq:
yield self.tb.dut.i.eq(seq.pop(0))
yield
if (yield self.tb.dut.n):
self.assertNotIn((yield self.tb.dut.i), [1<<i for i in range(8)])
else:
self.assertEqual((yield self.tb.dut.i), 1<<(yield self.tb.dut.o))
self.run_with(gen())
class PrioEncCase(SimCase, unittest.TestCase):
class TestBench(Module):
def __init__(self):
self.submodules.dut = PriorityEncoder(8)
def test_sizes(self):
self.assertEqual(len(self.tb.dut.i), 8)
self.assertEqual(len(self.tb.dut.o), 3)
self.assertEqual(len(self.tb.dut.n), 1)
def test_run_sequence(self):
seq = list(range(1<<8))
def gen():
for _ in range(256):
if seq:
yield self.tb.dut.i.eq(seq.pop(0))
yield
i = yield self.tb.dut.i
if (yield self.tb.dut.n):
self.assertEqual(i, 0)
else:
o = yield self.tb.dut.o
if o > 0:
self.assertEqual(i & 1<<(o - 1), 0)
self.assertGreaterEqual(i, 1<<o)
self.run_with(gen())
class DecCase(SimCase, unittest.TestCase):
class TestBench(Module):
def __init__(self):
self.submodules.dut = Decoder(8)
def test_sizes(self):
self.assertEqual(len(self.tb.dut.i), 3)
self.assertEqual(len(self.tb.dut.o), 8)
self.assertEqual(len(self.tb.dut.n), 1)
def test_run_sequence(self):
seq = list(range(8*2))
def gen():
for _ in range(256):
if seq:
i = seq.pop()
yield self.tb.dut.i.eq(i//2)
yield self.tb.dut.n.eq(i%2)
yield
i = yield self.tb.dut.i
o = yield self.tb.dut.o
if (yield self.tb.dut.n):
self.assertEqual(o, 0)
else:
self.assertEqual(o, 1<<i)
self.run_with(gen())
class SmallPrioEncCase(SimCase, unittest.TestCase):
class TestBench(Module):
def __init__(self):
self.submodules.dut = PriorityEncoder(1)
def test_sizes(self):
self.assertEqual(len(self.tb.dut.i), 1)
self.assertEqual(len(self.tb.dut.o), 1)
self.assertEqual(len(self.tb.dut.n), 1)
def test_run_sequence(self):
seq = list(range(1))
def gen():
for _ in range(5):
if seq:
yield self.tb.dut.i.eq(seq.pop(0))
yield
i = yield self.tb.dut.i
if (yield self.tb.dut.n):
self.assertEqual(i, 0)
else:
o = yield self.tb.dut.o
if o > 0:
self.assertEqual(i & 1<<(o - 1), 0)
self.assertGreaterEqual(i, 1<<o)
self.run_with(gen())

View file

@ -0,0 +1,30 @@
import unittest
from nmigen.compat import *
from .support import SimCase
class ConstantCase(SimCase, unittest.TestCase):
class TestBench(Module):
def __init__(self):
self.sigs = [
(Signal(3), Constant(0), 0),
(Signal(3), Constant(5), 5),
(Signal(3), Constant(1, 2), 1),
(Signal(3), Constant(-1, 7), 7),
(Signal(3), Constant(0b10101)[:3], 0b101),
(Signal(3), Constant(0b10101)[1:4], 0b10),
(Signal(4), Constant(0b1100)[::-1], 0b0011),
]
self.comb += [a.eq(b) for a, b, c in self.sigs]
def test_comparisons(self):
def gen():
for s, l, v in self.tb.sigs:
s = yield s
self.assertEqual(
s, int(v),
"got {}, want {} from literal {}".format(
s, v, l))
self.run_with(gen())

38
tests/compat/test_fifo.py Normal file
View file

@ -0,0 +1,38 @@
import unittest
from itertools import count
from nmigen.compat import *
from nmigen.compat.genlib.fifo import SyncFIFO
from .support import SimCase
class SyncFIFOCase(SimCase, unittest.TestCase):
class TestBench(Module):
def __init__(self):
self.submodules.dut = SyncFIFO(64, 2)
self.sync += [
If(self.dut.we & self.dut.writable,
self.dut.din[:32].eq(self.dut.din[:32] + 1),
self.dut.din[32:].eq(self.dut.din[32:] + 2)
)
]
def test_run_sequence(self):
seq = list(range(20))
def gen():
for cycle in count():
# fire re and we at "random"
yield self.tb.dut.we.eq(cycle % 2 == 0)
yield self.tb.dut.re.eq(cycle % 3 == 0)
# the output if valid must be correct
if (yield self.tb.dut.readable) and (yield self.tb.dut.re):
try:
i = seq.pop(0)
except IndexError:
break
self.assertEqual((yield self.tb.dut.dout[:32]), i)
self.assertEqual((yield self.tb.dut.dout[32:]), i*2)
yield
self.run_with(gen())

87
tests/compat/test_fsm.py Normal file
View file

@ -0,0 +1,87 @@
import unittest
from itertools import count
from nmigen.compat import *
from nmigen.compat.genlib.fsm import FSM
from .support import SimCase
class FSMCase(SimCase, unittest.TestCase):
class TestBench(Module):
def __init__(self):
self.ctrl = Signal()
self.data = Signal()
self.status = Signal(8)
self.submodules.dut = FSM()
self.dut.act("IDLE",
If(self.ctrl,
NextState("START")
)
)
self.dut.act("START",
If(self.data,
NextState("SET-STATUS-LOW")
).Else(
NextState("SET-STATUS")
)
)
self.dut.act("SET-STATUS",
NextValue(self.status, 0xaa),
NextState("IDLE")
)
self.dut.act("SET-STATUS-LOW",
NextValue(self.status[:4], 0xb),
NextState("IDLE")
)
def assertState(self, fsm, state):
self.assertEqual(fsm.decoding[(yield fsm.state)], state)
def test_next_state(self):
def gen():
yield from self.assertState(self.tb.dut, "IDLE")
yield
yield from self.assertState(self.tb.dut, "IDLE")
yield self.tb.ctrl.eq(1)
yield
yield from self.assertState(self.tb.dut, "IDLE")
yield self.tb.ctrl.eq(0)
yield
yield from self.assertState(self.tb.dut, "START")
yield
yield from self.assertState(self.tb.dut, "SET-STATUS")
yield self.tb.ctrl.eq(1)
yield
yield from self.assertState(self.tb.dut, "IDLE")
yield self.tb.ctrl.eq(0)
yield self.tb.data.eq(1)
yield
yield from self.assertState(self.tb.dut, "START")
yield self.tb.data.eq(0)
yield
yield from self.assertState(self.tb.dut, "SET-STATUS-LOW")
self.run_with(gen())
def test_next_value(self):
def gen():
self.assertEqual((yield self.tb.status), 0x00)
yield self.tb.ctrl.eq(1)
yield
yield self.tb.ctrl.eq(0)
yield
yield
yield from self.assertState(self.tb.dut, "SET-STATUS")
yield self.tb.ctrl.eq(1)
yield
self.assertEqual((yield self.tb.status), 0xaa)
yield self.tb.ctrl.eq(0)
yield self.tb.data.eq(1)
yield
yield self.tb.data.eq(0)
yield
yield from self.assertState(self.tb.dut, "SET-STATUS-LOW")
yield
self.assertEqual((yield self.tb.status), 0xab)
self.run_with(gen())

View file

@ -0,0 +1,23 @@
import unittest
from nmigen.compat import *
class PassiveCase(unittest.TestCase):
def test_terminates_correctly(self):
n = 5
count = 0
@passive
def counter():
nonlocal count
while True:
yield
count += 1
def terminator():
for i in range(n):
yield
run_simulation(Module(), [counter(), terminator()])
self.assertEqual(count, n)

View file

@ -0,0 +1,28 @@
import unittest
from nmigen import Signal, Module, Elaboratable
from .support import SimCase
class RunSimulation(SimCase, unittest.TestCase):
""" test for https://github.com/nmigen/nmigen/issues/344 """
class TestBench(Elaboratable):
def __init__(self):
self.a = Signal()
def elaborate(self, platform):
m = Module()
m.d.sync += self.a.eq(~self.a)
return m
def test_run_simulation(self):
def gen():
yield
for i in range(10):
yield
a = (yield self.tb.a)
self.assertEqual(a, i % 2)
self.run_with(gen())

View file

@ -0,0 +1,43 @@
import unittest
from nmigen.compat import *
from .support import SimCase
class SignedCase(SimCase, unittest.TestCase):
class TestBench(Module):
def __init__(self):
self.a = Signal((3, True))
self.b = Signal((4, True))
comps = [
lambda p, q: p > q,
lambda p, q: p >= q,
lambda p, q: p < q,
lambda p, q: p <= q,
lambda p, q: p == q,
lambda p, q: p != q,
]
self.vals = []
for asign in 1, -1:
for bsign in 1, -1:
for f in comps:
r = Signal()
r0 = f(asign*self.a, bsign*self.b)
self.comb += r.eq(r0)
self.vals.append((asign, bsign, f, r, r0.op))
def test_comparisons(self):
def gen():
for i in range(-4, 4):
yield self.tb.a.eq(i)
yield self.tb.b.eq(i)
yield
a = yield self.tb.a
b = yield self.tb.b
for asign, bsign, f, r, op in self.tb.vals:
r, r0 = (yield r), f(asign*a, bsign*b)
self.assertEqual(r, int(r0),
"got {}, want {}*{} {} {}*{} = {}".format(
r, asign, a, op, bsign, b, r0))
self.run_with(gen())

21
tests/compat/test_size.py Normal file
View file

@ -0,0 +1,21 @@
import unittest
from nmigen._utils import _ignore_deprecated
from nmigen.compat import *
def _same_slices(a, b):
return a.value is b.value and a.start == b.start and a.stop == b.stop
class SignalSizeCase(unittest.TestCase):
def setUp(self):
self.i = C(0xaa)
self.j = C(-127)
with _ignore_deprecated():
self.s = Signal((13, True))
def test_len(self):
self.assertEqual(len(self.s), 13)
self.assertEqual(len(self.i), 8)
self.assertEqual(len(self.j), 8)