Skip to main content

Synchronize goroutines in your tests

·460 words·3 mins· ·
Golang Go Goroutines Testing Synchronization
Ariejan de Vroom
Author
Ariejan de Vroom
Jack of all Trades, Professional Software Craftsman

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:

type Acia6551 struct {
    // ... ommitted for brevity
    output chan []byte
}

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.

output := make(chan []byte)
acia := &Acia6551{output: output}

go func() {
    for {
        for _, b := range <-output {
            // Handle output byte
        }
    }
}()

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.

func TestAciaOutputChannel(t *testing.T) {
    var value []byte // Used to store the output value from the channel

    output := make(chan []byte) // The output channel
    done := make(chan bool)     // Channel signaling we're done waitign
    
    acia := &Acia6551{output: output}

    // Create a goroutine, waiting for data on the output channel,
    // and sending a signal when data arrived.
    go func() {
        value = <-output // Block, waiting for []byte
        done <- true     // We're done!
    }()

    acia.WriteByte(0x00, 0x42) // Writes a single byte of data to the 6551

    <-done // Wait for the output to be received and stored in `value`

    if value[0] != 0x42 {
        t.Errorf("Expected output not received through output channel")
    }
}

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

Related

A Call to all CI Service Providers
·472 words·3 mins
Testing Bdd Tdd Rant Ci Jenkins
A static file server in Go
·126 words·1 min
Go
Fast specs - Run your specs in less than 1 second
·1208 words·6 mins
Ruby Rails Rspec Testing Bdd Tdd Cucumber Fast_spec Fastspec Coreyhaines