devroom.io/content/posts/2014-08-29-synchronize-goroutines-in-your-tests.md

94 lines
3.2 KiB
Markdown
Raw Normal View History

2015-03-26 11:28:08 +00:00
+++
date = "2014-08-29"
title = "Synchronize goroutines in your tests"
tags = ["golang", "go", "goroutines", "testing", "synchronization"]
description = "In most cases testing channels is not necessary. You can easily wrap the functionalityunder test in a function and test that. Sometimes, however, you really need to validatethat those channels behave the way you want."
slug = "synchronize-goroutines-in-your-tests"
+++
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:
2017-03-20 15:35:19 +00:00
``` go
type Acia6551 struct {
// ... ommitted for brevity
output chan []byte
}
```
2015-03-26 11:28:08 +00:00
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.
2017-03-20 15:35:19 +00:00
``` go
output := make(chan []byte)
acia := &Acia6551{output: output}
2015-03-26 11:28:08 +00:00
2017-03-20 15:35:19 +00:00
go func() {
for {
for _, b := range <-output {
// Handle output byte
2015-03-26 11:28:08 +00:00
}
2017-03-20 15:35:19 +00:00
}
}()
```
2015-03-26 11:28:08 +00:00
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.
2017-03-20 15:35:19 +00:00
``` go
func TestAciaOutputChannel(t *testing.T) {
var value []byte // Used to store the output value from the channel
2015-03-26 11:28:08 +00:00
2017-03-20 15:35:19 +00:00
output := make(chan []byte) // The output channel
done := make(chan bool) // Channel signaling we're done waitign
acia := &Acia6551{output: output}
2015-03-26 11:28:08 +00:00
2017-03-20 15:35:19 +00:00
// 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!
}()
2015-03-26 11:28:08 +00:00
2017-03-20 15:35:19 +00:00
acia.WriteByte(0x00, 0x42) // Writes a single byte of data to the 6551
2015-03-26 11:28:08 +00:00
2017-03-20 15:35:19 +00:00
<-done // Wait for the output to be received and stored in `value`
2015-03-26 11:28:08 +00:00
2017-03-20 15:35:19 +00:00
if value[0] != 0x42 {
t.Errorf("Expected output not received through output channel")
2015-03-26 11:28:08 +00:00
}
2017-03-20 15:35:19 +00:00
}
```
2015-03-26 11:28:08 +00:00
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/