Testing SSE in zig When EVERYTHING Blocks
I rely on TDD heavily and really don’t like manual testing. So when I started building SSE support into mantel , I wanted a proper e2e test — connect a client, read the events, assert on them.
Except, I ran into a problem. http.zig ’s test helpers don’t work with SSE - they expect a request/response cycle that completes. And zig 0.16’s new Io framework supports neither cancellation nor non-blocking reads .
So: everything blocks, and there’s no way to stop it.
If I wasn’t building this because it’s fun, I’d probably skip it. Actually, I’d just use go where testing this is easy.
I am not doing this for work, and this kind of “threading the needle” solution finding is something I enjoy. Plus, I am writing zig because I love it.
I’d actually decided to let it be and moved on, but in my sleep, a solution came to me, and it worked.
If We Could Cancel
The normal testing pattern for this, for example in Go, would be something like:
- Set up the server
- Connect the Client with a timeout
- Read from the client
Here is a version of it from henge .
| |
You could do something similar if it didn’t support timeout, but did support non-blocking reads. You just wait until your timeout and see if the data was there.
Unfortunately, on zig, std.Io.Evented — the non-blocking backend — is still a
work in progress."
(Un)Blocking
The idea that came to me was simply that we could close the server stream before we read as the client. In other words:
- Start the Server in a new thread
- Connect Client, but don't read
- Close the server
- Read all bytes from client
- Safe now because stream has been closed
| |
And in Feed.zig , we need to ensure the stream is closed on de-activating the feed:
| |
Feed.zig also contains details about the active flag and how it’s managed
| |
In this test, we are just verifying we get back “a message” which is just a placeholder. The intent is to prove the mechanism. The next step is to write an actual event and verify it.
Gotchas
There are many gotchas in this solution, particularly with ordering.
The server has to be closed before we join the thread, otherwise, it’ll just block.
We have to read all the content from the stream in one go with
appendRemaining, otherwise, it’ll block. We could read in parts if we know
there is a minimum of that much content left in the stream — but that’s tricky
at best.
We should wait until the Feed is active before de-activating it, or you’ll end
up with race conditions.
You must drain the stream fully, otherwise you’ll get errors.
Why Not Just Mock It?
I could mock it, and I will add some mocked tests later. I wanted some confidence of the end to end journey working well without having to run it and test with curl manually.
Once I replace the placeholder with actual events, I’ll not feel compelled to test it manually each time to ensure it still works.
