* * * * * Unit testing from inside an assembler > Plug plug: I've written an assembler[0] for the 6502 (with full LSP > (Language Server Protocol) and debugging support). It also supports the > concept of unit tests whereby your program gets assembled and every test > individually gets assembled and run, whereby you can add certain asserts to > check for CPU (Central Processing Unit) register states and things like > that. > > [0] See https://mos.datatra.sh/guide/unit-testing.html [1] > “Plug plug: I've written an assembler[0] for the 6502 (with full LSP and debuggin... | Hacker News [2]” This comment (from the Orange Site about a previous post [3]) grabbed my attention. I'm fascinated by the feature, and I think that's because the test is run in the assembler! (As a side note—I think they missed an opportunity by not using TRON to enable tracing) I'm thinking I might try to add a feature to my my assembler [4], as I've already written a 6809 emulator as a library [5]. If I already had this feature (and riffing off the sample [6]), how might this look? What are some of the issues that might come up? I marked up the random function as I might have done during testing: -----[ Assembly ]----- ;*********************************************************************** ; RANDOM Generate a random number ;Entry: none ;Exit: B - random number (1 - 255) ;*********************************************************************** random ldb lfsr andb #1 negb andb #$B4 stb ,-s ; lsb = -(lfsr & 1) & taps ldb lfsr lsrb ; lfsr >>= 1 eorb ,s+ ; lfsr ^= lsb stb lfsr rts ; -------------------- .test "random" .tron ldx #.result_array + 128 .troff lda #1 sta lfsr lda #255 .loop bsr random .assert cpu.B <> 0 , "degenerate LFSR" .tron tst b,x .troff .asert cpu.CC.z <> 1 inc b,x deca bne .loop rts .result_array rmb 256 .endtest -----[ END OF LINE ]----- First off, I would have the tracing always print results—that way I can follow the flow to help see the issue. One open question—would that be a command line option? Or as I have it here—a pseudo operation? Second, how would I return from the code? The sample I'm going off uses BRK (the 6502 software interrrupt instruction). I suppose I could use SWI but I would also want to fill unused memory with that instruction in case the code goes off into the weeds, so I would need a way to detect the difference. I don't want to juse use .endtest to end the code sequence, as I might also want to include variables, like I did here. Another example, this time the function that had the bug in it: -----[ Assembly ]----- ;************************************************************************* ; GETPIXEL Get the color of a given pixel ;Entry: A - x pos ; B - y pos ;Exit: X - video address ; A - 0 ; B - color ;************************************************************************* getpixel bsr point_addr ; get video address .tron comb ; reverse mask (since we're reading stb ,-s ; the screen, not writing it) ldb ,x ; get video data andb ,s+ ; mask off the pixel tsta ; any shift? beq .done .rotate lsrb ; shift color bits deca bne .rotate .troff .done rts ; return color in B .test "getpixel" ldd #.screen std ECB.beggrp lda #0 ; X lda #0 ; Y bsr getpixel .assert cpu.X = #.screen .assert cpu.B = 3 lda #1 ldb #0 bsr getpixel .assert cpu.X = #.screen .assert cpu.B = 3 lda #2 ldb #0 bsr getpixel .assert cpu.X = #.screen .assert cpu.B = 3 lda #3 ldb #0 bsr getpixel .assert cpu.X = #.screen .assert cpu.B = 3 rts .screen fcb %11_11_11_11 ; our four pixels .endtest -----[ END OF LINE ]----- More questions: should I be able to trace non-test code? Probably, as that could help with debugging issues. Also, the function being tested is calling another function which just happens to be a forward reference, which tells me that calling the tests should happen on pass two of the assembler. And that brings up further questions—what about code like this? -----[ Assembly ]----- INTCNV equ $B3ED GIVABF equ $B4F4 org $7000 checksum jsr INTCNV ; get parameter from BASIC tfr d,y ; it should point to a string variable ldx 2,y ; get address lda ,y ; get length clrb ; clear checksum and Carry bit .sum adcb ,x+ ; add deca bne .sum comb ; 1s compliment clra ; return 0-255 result jmp GIVABF ; return result to BASIC .test "checksum" ldd #.tmpstr ; our "string" jsr GIVABF ; give address to BASIC bsr checksum jsr INTCNV ; get our result from BASIC .assert cpu.D = 139 ; if I did my math right rts .tmpstr fcb 5 fcb 0 fdb .text fcb 0 .text fcc /HELLO/ .endtest -----[ END OF LINE ]----- The two routines INTCNV and GIVABF are ROM (Read Only Memory) routines (from the Color Computer BASIC (Beginners' All-purpose Symbolic Instruction Code) system) so we don't have the code for the emulator, and therefore, this code can't be tested as is. I suppose it could be rewritten such that it can be tested (and use more memory, which could be an issue) but this does show the limitation of this technique. I suppose one fix would be conditional assembly: -----[ Assembly ]----- .iftest .value fdb 0 INTCNV ldd .value rts GIVABF std INTCNV.value rts .else INVCNV equ $B3ED GIVABF equ $B4F4 .endif -----[ END OF LINE ]----- but personally, I'm not a fan of conditional code, but I shouldn't discount this as a solution. Another issue is labels. I've been using local labels for the testing code, thinking that there would be a unique non-local label for each test (generated by the assembler) to avoid naming conflicts (naming is hard). I need to think on how I want to handle this. It's an interesting idea though … [1] https://mos.datatra.sh/guide/unit-testing.html [2] https://mos.datatra.sh/guide/unit-testing.html#assertions [3] gopher://gopher.conman.org/0Phlog:2023/11/27.1 [4] https://github.com/spc476/a09 [5] https://github.com/spc476/mc6809 [6] https://mos.datatra.sh/guide/unit-testing.html Email Sean Conner at sean@conman.org .