proto.Unmarshal test fails inconsistently

Issue

I had some Redis code that relied on the Proto standard for marshalling/unmarshalling of data. I wrote the following test using gomega to test around proto.Unmarshal:

b64Decoded, err := base64.StdEncoding.DecodeString("derp")
Expect(err).ShouldNot(HaveOccurred())

var item testRecord
err = proto.Unmarshal(b64Decoded, &item)
Expect(err).Should(HaveOccurred())
Expect(err.Error()).Should(Equal("proto:\u00a0cannot parse invalid wire-format data"))

However, the final assertion fails because the expected error string was proto: cannot parse invalid wire-format data. The obvious solution here is to change it, and when I do, the error goes away. Until I modify the test and rerun it, in which case the test again fails telling me that the string should’ve been proto:\u00a0cannot parse invalid wire-format data. This cycle continues infinitely. So, what am I doing wrong here and how do I fix this?

Solution

This issue is not quite as you think; the protobuf package randomly selects a space or \u00a0 when outputting an error (I believe it’s based upon a hash of the binary). You can see this here:

// Deliberately introduce instability into the error message string to
// discourage users from performing error string comparisons.
if detrand.Bool() {
   return "proto: " // use non-breaking spaces (U+00a0)
} else {
   return "proto: " // use regular spaces (U+0020)
}

So the issue you are encountering is deliberate and aims to prevent users from doing what you are attempting (relying upon errors remaining the same). The reason that you only see this when you change a test (and, I would guess, not every time you change it) is that Go will cache the test results (by default tests are only run when something has changed).

In terms of what you can do about this I’d first suggest considering if this test is really necessary. The package authors are specifically signalling that the errors are not stable so comparing in this way may break when a new version of google.golang.org/protobuf/proto is released.

The protobuf package tests work around this by calling detrand.Disable() (e.g. here). You cannot do this because google.golang.org/protobuf/internal/detrand is under internal and, as such, not accessible.

If you really want to work around this the simplest approach is probably strings.Contains.

Answered By – Brits

Answer Checked By – Willingham (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.