97 lines
3.3 KiB
Markdown
97 lines
3.3 KiB
Markdown
---
|
|
title: "Synchronize goroutines in your tests"
|
|
created_at: 2014-08-29 13:40:36 +0200
|
|
kind: article
|
|
tags:
|
|
- golang
|
|
- go
|
|
- goroutines
|
|
- testing
|
|
- synchronization
|
|
summary: |
|
|
In most cases testing channels is not necessary. You can easily wrap the functionality
|
|
under test in a function and test that. Sometimes, however, you really need to validate
|
|
that those channels behave the way you want.
|
|
---
|
|
I have been working on an [emulator for the MOS 6502 Microprocessor, written in Go][1]. 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:
|
|
|
|
:::go
|
|
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.
|
|
|
|
:::go
|
|
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.
|
|
|
|
:::go
|
|
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][2]
|
|
|
|
[1]: https://github.com/ariejan/i6502
|
|
[2]: http://ariejan.github.io/i6502/
|