Last updated

Synchronize goroutines in your tests

I have been working on an emulator for the MOS 6502 Microprocessor, written in Go. As part of this package I have also implemented a minimal 6551 Asynchronous Communication Interface Adapter. The 6551 provides serial IO and is easy to use in combination with the 6502.

When the microprocessor writes a byte to the 6551 it is stored in the tx (transmit) register where it’s available for other hardware components to read.

In order to make my emulator available using websockets (more on that in a later post), I had to know when a new byte became available. In a real hardware design I’d probably have to use the \CTS (Clear to Send) input pin to signal the 6551 I’m ready to read the next byte of data.

But, my goal is to emulate the Microprocessor not a complete computer hardware design.o

The logical option in Go would be to use a channel that receives new bytes when they are written by the Microprocessor.

I quickly devised the following:

1type Acia6551 struct {
2    // ... ommitted for brevity
3    output chan []byte
4}

This would allow me to create an instance of type Acia6551 and supply it with an output channel.

The calling code would start a goroutine waiting for output on this channel.

 1output := make(chan []byte)
 2acia := &Acia6551{output: output}
 3
 4go func() {
 5    for {
 6        for _, b := range <-output {
 7            // Handle output byte
 8        }
 9    }
10}()

Awesome. This worked very well in my websockets prototype.

The problem is: how do you test this? How do you test that bytes written by the microcontroller to the Acia are actually sent out to the output channel?

The solution was to create a goroutine in the test and use another channel to synchronize the writing of the byte with the output appearing on the output channel.

 1func TestAciaOutputChannel(t *testing.T) {
 2    var value []byte // Used to store the output value from the channel
 3
 4    output := make(chan []byte) // The output channel
 5    done := make(chan bool)     // Channel signaling we're done waitign
 6    
 7    acia := &Acia6551{output: output}
 8
 9    // Create a goroutine, waiting for data on the output channel,
10    // and sending a signal when data arrived.
11    go func() {
12        value = <-output // Block, waiting for []byte
13        done <- true     // We're done!
14    }()
15
16    acia.WriteByte(0x00, 0x42) // Writes a single byte of data to the 6551
17
18    <-done // Wait for the output to be received and stored in `value`
19
20    if value[0] != 0x42 {
21        t.Errorf("Expected output not received through output channel")
22    }
23}

This is an example of simple synchronization using a seperate channel, in this case chan bool. It worked great in my tests to verify data was actually written to the output channel.

Read more about my i6502 project