diff --git a/content/posts/2014-08-29-synchronize-goroutines-in-your-tests.md b/content/posts/2014-08-29-synchronize-goroutines-in-your-tests.md new file mode 100644 index 0000000..ac45c55 --- /dev/null +++ b/content/posts/2014-08-29-synchronize-goroutines-in-your-tests.md @@ -0,0 +1,96 @@ +--- +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/