chanmix51 wrote:
I am fully with you, Soft65C02 is heavily unit tested (more than 250 unit tests and 1k assertions). I discovered that Rust encouraged the implementation of unit tests directly as submodules of the tested modules....
Sorry, I wasn't clear. While of course my framework has unit tests for the framework itself as well, the purpose of the framework is to
unit test the 6502 code you write. So, for example, I have a 6502 routine called
bi_readhex which reads an ASCII string representing an arbitrary-precision integer, and converts it to native binary representation. One set of tests for it is:
Code:
# Buffers used for testing deliberately cross page boundaries.
INBUF = 0x6FFE
OUTBUF = 0x71FE
@pytest.mark.parametrize('input, bytes', [
(b"5", [0x05]),
(b'67', [0x067]),
(b'89A', [0x08, 0x9A]),
(b'fedc', [0xFE, 0xDC]),
(b'fedcb', [0x0F, 0xED, 0xCB]),
(b"80000", [0x08, 0x00, 0x00]),
(b"0", [0x00]),
(b"00000000", [0x00]),
(b"087", [0x87]),
(b"00000087", [0x87]),
])
def test_bi_readhex(M, input, bytes):
print('bi_readhex:', input, type(input), bytes)
S = M.symtab
M.deposit(INBUF, input)
M.depword(S.buf0ptr, INBUF)
M.depword(S.buf1ptr, OUTBUF)
size = len(bytes) + 2 # length byte + value + guard byte
M.deposit(OUTBUF, [222] * size) # 222 ensures any 0s really were written
M.call(S.bi_readhex, R(a=len(input)))
bvalue = M.bytes(OUTBUF+1, len(bytes))
assert (len(bytes), bytes, 222,) \
== (M.byte(OUTBUF), bvalue, M.byte(OUTBUF+size-1))
Here
M is the "machine," the emulated system on which I'm running the 6502 code,
S.xxxx are symbol lookups (from the symbol table output of the assembler), and
R is the constructor for an object representing some or all of the 6502 registers.
M.call can run for a given number of instructions, to a given instruction, and throws an exception if any instruction from a given list is executed (the default list is just
BRK). If an assertion fails, it prints out the expected and actual values, for register sets printing them more or less the way you do except with a hyphen for registers or flags that you're ignoring in the comparison.
So basically I'm doing the same kind of thing you're doing in your command interpreter, but specified as unit tests that can easily be re-run. The reason I find little need for an interactive command interpreter is that I can just take what would be typed as commands, write a unit test with those commands instead, and then run the test. (Or, more often, just tweak an existing test.) So long as the test fails (easily achieved by adding an
assert 0 at the end) the stdout/stderr output will be printed, too, so you can just add
print() statements to dump bits of memory at any point you like if you need to see what's going on. (In the test above this is the purpose of the
print() at the start; you won't see its output if the test passes, but if the test fails it gives you some useful debugging information.)
(And I just noticed I'm forgetting to check the guard byte before the buffer above; oops! I really need to add something to automatically add guard bytes to buffers and check them.)
Quote:
...but the purpose of soft65C02 is to « see to understand » what's going on under the hood.
Right. My way is a bit slower than than the interactive method if you're at a specific point in a program and you want to do a lot of exploring, but it's a lot faster if you need to just see "what happened at this point in the run," and "what happened at that point"; having the test system re-run the code and print at the appropriate points is a lot easier than setting up data and running your 6502 routine it over and over again by hand. And of course you come out at the end with a set of unit tests, too.
That said, I've considered adding a "drop into an interactive monitor" feature to the system so that at any point in a test you could stop execution and then interactively explore. I've just not felt the need for that yet.