From 0fd3d078e3bc292fd689bffcf27d90392a61daff Mon Sep 17 00:00:00 2001 From: wgroeneveld Date: Wed, 28 Nov 2018 15:53:52 +0100 Subject: [PATCH] readme updaten --- README.md | 119 +++++++++++++++++++++++++++++++++++ opbtest/opbtestassertions.py | 23 +++++-- 2 files changed, 137 insertions(+), 5 deletions(-) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..b43c12e --- /dev/null +++ b/README.md @@ -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)`. \ No newline at end of file diff --git a/opbtest/opbtestassertions.py b/opbtest/opbtestassertions.py index 2cbd70e..c5961ef 100644 --- a/opbtest/opbtestassertions.py +++ b/opbtest/opbtestassertions.py @@ -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)