4.2 KiB
Open PicoBlaze Assembler Unit Test package
This python 3 package makes it easy for you to write PSM(4) Assembly - even test-first!
Simply write a unit test for it in python, extending from OpbTestCase
. It's easily integratable into your CI environment by leveraging the output of python -m unittest
.
Example test case:
from opbtest import OpbTestCase
class MyTestCase(OpbTestCase):
def test_my_cool_procedure_should_set_some_register(self):
assert_that = self.load_file("functions.psm4").testproc("my_cool_procedure").setregs({"s5": 2}).execute()
assert_that.reg("s5").contains(3)
That's it! Load your Assembly file, apply setup (mocks, inputs), and verify expectations.
opbtest relies on opbasm and opbsim to compile and evaluate your code.
Both executables should be in your $PATH
.
The simulator will behave different compared to the actual hardware, so be aware that a set of green tests will not guarantee your code to work when deployed.
API Documentation
Loading your Assembly
Base class: OpbTestCase
The following methods are possible to bootstrap your Assembly:
- use
load_file(filename)
. This does not evaluate yet, until you callexecute()
, which returns anOpbTestAssertions
instance. - use
execute_file(filename)
. This immediately evaluates by reading the file, returning anOpbTestAssertions
instance. - use
execute_psm(asmstring)
. This immediately evaluates the given string, returning anOpbTestAssertions
instance.
For example, inline Assembly:
def test_basic_output_assertion(self):
psm = """load s2, 5
output s2, 1
output sD, FF"""
assert_that = self.execute_psm(psm)
assert_that.port(1).contains(5)
assert_that.ports(["1", "FF"]).contains([5, 0])
Assertions
Base class: OpbTestAssertions
The following can be asserted:
reg()
,regs()
,rega()
,regsa()
,regb()
,regsb()
. Registers..reg("s0").contains(1)
. Max 2x16. No bank specified defaults to bank "a".port()
andports()
. Output Ports..port(0).contains(1)
. Max 16x16scratchpad()
andscratchpads()
. Scratchpad..scratchpad(0).contains(1)
Max 4x16
Everything can be chained together, or be asserted in one line:
def test_basic_scratchpad_assertion(self):
psm = """load s0, 1
store s0, (s0)
load s0, 2
store s0, (s0)
load s0, 3
store s0, (s0)
output sD, FF"""
assert_that = self.execute_psm(psm)
assert_that.scratchpad(1).contains(1)
assert_that.scratchpads(["0", "1", "2", "3"]).contains([0, 1, 2, 3])
Notice the difference: scratchpads()
will accept an array, and contains()
will accept the same length in values.
You can set the scratchpad/output port/... index as an int
(0, 1, ...) or as a str
in hex values ("0", "1F", "FF", ...)
Testing only one procedure
If you don't want the whole thing to be executed, you can still use opbtest, and call testproc(name)
:
def test_proc2_testproc_does_not_execute_rest_of_psm(self):
assert_that = self.load_file("functions.psm4").testproc("proc2").execute()
assert_that.regs(["s2", "s4"]).contains([0, 42])
given the following contents of functions.psm4:
use_stack(sF, 0x3F)
jump main
proc proc1(s0 is result := 1) {
load result, 42
call proc2
}
proc proc2(s4 is test) {
load test, 42
}
main:
load s2, 11
add s2, 1
output sD, FF
Only testing proc1, and nothing more, is usually tricky in Assembly because of the jump statement. opbtest will inject code into your Assembly to jump to the procedure to test, execute that, and jump to an exit label. That ensures no other state will be modified.
Mocking
If you explicitly do not want a certain procedure to be called, you can do so by calling mockproc(procname)
.
This will replace all call procname
statements with dummy statements, hence never actually executing the procedure.
You can replace your own statements with replace(statement_to_replace, statement_to_replace_with)
.