Fix random(), stabilize tests

During the development of rqlite 8.0, a surprising behavior revealed itself through an unexpected venue: AppVeyor, the Continuous Integration (CI) system I employ for Windows testing.

And even more unexpected was the resolution of previously unreliable unit tests.

It began with a simple testing requirement. I needed a function to generate a random string, 20 characters long. I coded the following function, which looks quite ordinary:

func randomString() string {
    var output strings.Builder
    now := time.Now().UnixNano()
    r :=     rand.New(rand.NewSource(now))
    chars := "abcdedfghijklmnopqrstABCDEFGHIJKLMNOP"

    for i := 0; i < 20; i++ {
        random := r.Intn(len(chars))
        randomChar := chars[random]
    return output.String()

Imagine my surprised when I discovered that this function, when called in quick succession, occasionally returned the same string twice when! The significance of this was not trivial; my tests use random strings for node IDs, and assigning the same ID to more than one node in a rqlite cluster would result in pretty confused system — and completely broken tests.

The culprit seemed to be AppVeyor, which may have returned the same value for multiple calls to UnixNano().  Though I wasn’t entirely certain the evidence pointed in that direction. After all, I highly doubt less than 1 nanosecond passed between successive invocations of the function.

In any case, the remedy was applied: I fixed my code, and now all testing uses the same, singly-seeded, random source. And even more delightful than dealing with this particular test failure, my tests have stabilized noticeably.

This was an unexpected benefit. Development of 8.0 has been quite involved, with tests failing very occasionally during CI for elusive reasons. Since making this change to random(), test stability has markedly improved. This faulty generation of random strings was affecting me more than I had realized — but it’s all looking green again.

Leave a Reply

Your email address will not be published. Required fields are marked *