readme updaten

This commit is contained in:
wgroeneveld 2018-11-28 15:53:52 +01:00
parent a7a9c9286c
commit 0fd3d078e3
2 changed files with 137 additions and 5 deletions

119
README.md Normal file
View File

@ -0,0 +1,119 @@
## 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:
````python
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](https://kevinpt.github.io/opbasm) 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 call `execute()`, which returns an `OpbTestAssertions` instance.
* use `execute_file(filename)`. This immediately evaluates by reading the file, returning an `OpbTestAssertions` instance.
* use `execute_psm(asmstring)`. This immediately evaluates the given string, returning an `OpbTestAssertions` instance.
For example, inline Assembly:
````python
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()` and `ports()`. Output Ports. `.port(0).contains(1)`. Max 16x16
* `scratchpad()` and `scratchpads()`. Scratchpad. `.scratchpad(0).contains(1)` Max 4x16
Everything can be chained together, or be asserted in one line:
```python
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)`:
````python
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)`.

View File

@ -39,9 +39,10 @@ class OpbTestBaseAssertions():
class OpbTestRegAssertions(OpbTestBaseAssertions):
def __init__(self, json, case):
def __init__(self, json, case, bank):
super().__init__(json, case)
self.registers = None
self.bank = bank
def torawregistername(self, register):
return int(register[1], 16)
@ -66,14 +67,14 @@ class OpbTestRegAssertions(OpbTestBaseAssertions):
def _tocontain(self, expected):
for register in self.registers:
result = self.case.checkReg(self.jsondata, "a", register, expected)
result = self.case.checkReg(self.jsondata, self.bank, register, expected)
if result != "":
self.case.fail(result)
def _contains(self, expected):
results = []
for i in range(0, len(expected)):
result = self.case.checkReg(self.jsondata, "a", self.registers[i], expected[i])
result = self.case.checkReg(self.jsondata, self.bank, self.registers[i], expected[i])
if result != "":
results.append(result)
return results
@ -164,7 +165,19 @@ class OpbTestAssertions():
return OpbTestScratchAssertions(self.jsondata, self.case).scratchpads(indices)
def regs(self, registers):
return OpbTestRegAssertions(self.jsondata, self.case).regs(registers)
return OpbTestRegAssertions(self.jsondata, self.case, "a").regs(registers)
def reg(self, register):
return OpbTestRegAssertions(self.jsondata, self.case).reg(register)
return OpbTestRegAssertions(self.jsondata, self.case, "a").reg(register)
def regsa(self, registers):
return OpbTestRegAssertions(self.jsondata, self.case, "a").regs(registers)
def rega(self, register):
return OpbTestRegAssertions(self.jsondata, self.case, "a").reg(register)
def regsb(self, registers):
return OpbTestRegAssertions(self.jsondata, self.case, "b").regs(registers)
def regb(self, register):
return OpbTestRegAssertions(self.jsondata, self.case, "b").reg(register)