73 lines
3.5 KiB
Markdown
73 lines
3.5 KiB
Markdown
---
|
|
title: "Adding probability to testing: Golang"
|
|
date: 2019-05-23T17:17:17-04:00
|
|
draft: false
|
|
tags: ["go", "testing"]
|
|
---
|
|
|
|
In my work as a Software Engineer at Kickback Rewards Systems, I recently wrote an application that essentially reads packets from a TCP stream.
|
|
It's amazing to me that writing a concurrent application that reads bytes from a TCP stream is SO EASY with Go, in less than 150 LOC!
|
|
We've had similar applications in C and Python that are much larger than that at my company!
|
|
Anyway, this application merely needed to mock the functionality of the production system, so that we could test the transmission of a new packet
|
|
type from our client code. The client expected some entity acting as the server in a socket connection that would first read bytes from the stream
|
|
to determine the length of the payload, then the payload itself. The server has to respond to the client in a similar manner, length preamble, and
|
|
payload of response.
|
|
|
|
An interesting requirement arose amid development of this mock server. The requirement was that the server should simulate an actual internet connection,
|
|
sometimes terminating the client's connection
|
|
too early, sometimes delaying a response to the client, and sometimes just not responding at all. The trick to this though is that this wacky behavior
|
|
needed to be inconsistent, something that happened seemingly randomly. To accomplish this, I developed a neat pattern using closures and function
|
|
pointers.
|
|
|
|
Enter our goroutine, which is obviously not my real production code:
|
|
|
|
```go
|
|
// Main and rest of program not included, it's not important to this example.
|
|
|
|
// Our goroutine.. called in the main socket listening loop
|
|
func handleConn(conn net.Conn) {
|
|
// Do some work and respond.
|
|
}
|
|
```
|
|
|
|
This is a pretty standard pattern, develop a function intended to be used as a goroutine, and call it in some loop passing a connection from a successful
|
|
`net.Listener.Accept()` call. What I did to add a little chaos to this function, was utilize a little bit of closure trickery >:). To introduce any
|
|
amount of chaos to your application, a function like this will do:
|
|
|
|
```go
|
|
// Flip a coin, and do the action on tails...
|
|
func chaos(action func()) {
|
|
if rand.Intn(10) > 5 {
|
|
// Executing our function pointer.
|
|
action()
|
|
}
|
|
}
|
|
```
|
|
|
|
On its own, this function may seem harmless. One may ask why you would want to flip a coin then do something if we achieve a certain result. The answer
|
|
to that is simple, and that's where closures come in. Let's modify my goroutine from before to do something nasty.
|
|
|
|
```go
|
|
func handleConn(conn net.Conn) {
|
|
// Now let's introduce some... chaos
|
|
chaos(func() {
|
|
fmt.Println("Oh no, something bad happened!")
|
|
conn.Close()
|
|
|
|
// don't worry about this.
|
|
runtime.Goexit()
|
|
})
|
|
|
|
fmt.Println("Phew, nothing's gonna happen, let's continue.")
|
|
// Do the work.
|
|
}
|
|
```
|
|
|
|
The way this works is simple, we pass an anonymous function as the argument to chaos, noted above as type `func()`. This anonymous function that we
|
|
create has access to any variables defined in its enclosing scope. `conn`, a function parameter, is defined in the same scope as the anonymous function,
|
|
thus it is perfectly valid to reference it from within the function. Now, when the function is executed in `chaos` like this: `action()`, it is still
|
|
holding a valid reference to the conn object, resulting in `conn.Close()` being called terminating the client's connection with the server. Use this
|
|
pattern any time you would like to introduce a little bit of chaos to your testing.
|
|
|
|
|