hansontechsolutions.com

Enhancing Code Quality with Fuzz Testing in Go

Written on

Chapter 1: Introduction to Fuzz Testing

Fuzz testing is an innovative approach that automatically generates input values for functions in order to uncover bugs that may not be immediately apparent.

Fuzz Testing in Go: An Overview

Fuzzing was incorporated into the Go standard library starting from version 1.18. This feature is a valuable addition for detecting elusive bugs in your code. Many developers have discovered bugs in the Go standard library itself by utilizing third-party fuzzing tools. Since fuzzing is a form of testing, it integrates seamlessly with existing testing utilities.

In this article, we will explore how to utilize the new fuzzing capabilities by testing an HTTP handler we’ve created. Before you begin, ensure you have at least Go 1.18 installed. For installation guidance, refer to my overview of the changes introduced in version 1.18.

Chapter 2: Setting Up Fuzz Testing

To initiate fuzz testing, I established a new project and initialized a Go module. This requires a test file, which I named main_test.go. Here’s how you can set it up:

mkdir fuzzy

go mod init programmingpercy.tech/fuzzy

touch main_test.go

Before we can start fuzzing, we need a function to test. We’ll create a handler that determines the maximum value from a slice of integers. To demonstrate fuzzing, we'll intentionally introduce a bug into the handler: if the result is equal to 50, it will fail.

Understanding Fuzzing Mechanics

Now, let’s dive into using the fuzzer. If you're already familiar with Go testing, you’ll find this straightforward. A Go test is typically defined by a function that begins with "Test" and accepts a parameter of type *testing.T. Fuzz tests follow a similar convention but are prefixed with "Fuzz" and take a *testing.F parameter.

To kick things off, we need to supply the testing.F with a Seed Corpus, which should consist of example data reflecting the expected input to your function. The fuzzer will utilize this data to create new inputs.

You can add seeds using f.Add(), which can accept various data types, including:

  • string
  • []byte
  • int
  • float64
  • bool

Keep in mind that you cannot mix data types. If you attempt to add a string followed by an integer, it will result in an error.

Handling Multiple Input Parameters

If your function requires multiple parameters, f.Add() can handle a variable number of inputs. Just ensure that the order of your example data matches the parameter order of the function being tested.

Now, let’s create example data for our handler and add it. Since we’re fuzzing an HTTP handler, we will use JSON data encoded as []byte.

We will set up an HTTP server that hosts the handler we want to fuzz and create example slices to feed into the Seed Corpus. After marshaling the slices into JSON format, we will add them to the fuzzer.

Implementing the Fuzz Target

To start the fuzzing process, we call f.Fuzz(), which accepts a function that acts as the fuzz target. This function should manage error checking, data preparation, and invocation of the function being fuzzed.

The input function for Fuzz should accept testing.T as the first parameter, followed by the data types corresponding to the seed inputs. In our case, we only need to pass testing.T and []byte, as those are the types we added.

Let’s define the Fuzz target to:

  1. Post the data to our handler.
  2. Verify the response status.
  3. Ensure the response is of type int.

While it might seem logical to check the returned value for correctness, it’s often unpredictable since the fuzzer generates random inputs.

Running the Fuzzer

To execute the fuzzer, use the standard Go test command with the --fuzz=Fuzz flag. The value after --fuzz= serves as a prefix for all methods starting with "Fuzz". For example, to fuzz only FuzzTestHTTPHandler, run:

go test --fuzz=FuzzTestHTTPHandler

Fuzzing behaves differently than standard tests; by default, it continues running until an error occurs. You can either cancel it manually or specify a duration limit using the -fuzztime flag. For a 10-second duration, you would execute:

go test --fuzz=Fuzz -fuzztime=10s

Wait for the fuzzer to fail, and you should see output indicating the nature of the failure.

Fuzzing In Go - YouTube: This video discusses fuzz testing in Go, detailing its implementation and benefits.

Analyzing Fuzz Test Failures

When an error occurs, the fuzzer will save the input parameters that caused the failure in a designated file. For example, if the JSON marshaling fails, you can inspect the contents of the failure file.

This experience is common when initially writing fuzz tests. To enhance the quality of the generated data, we should only allow valid JSON to be processed. We can skip invalid payloads using t.Skip("reason for skipping"). To ensure that we only pass valid JSON to the handler, we can use the json.Valid function for preliminary checks.

To further validate the JSON, we can attempt to unmarshal the data into a predefined struct and skip any payloads that fail. After making these adjustments, you can rerun the fuzzer:

go test -v --fuzz=Fuzz

Eventually, you should see a new failure file indicating the issue detected by the fuzzer.

packagemain #23: Fuzz Testing in Go - YouTube: This video explores practical fuzz testing examples in Go, demonstrating its effectiveness.

Conclusion

You are now equipped to write your own fuzz tests. Although the example we examined may seem trivial, fuzz testing for HTTP handlers and other methods can uncover hard-to-detect bugs. Fuzzers often reveal issues that traditional unit tests might overlook, as unit tests usually involve predictable input values.

As demonstrated, crafting a fuzzer for an HTTP handler is relatively straightforward, and with practice, you can quickly implement new fuzzers, enhancing the robustness of your codebase.

What are you waiting for? Dive in and start fuzz testing today!

For further insights into the changes in Go 1.18, feel free to check out my other article on using the new generics.

Share the page:

Twitter Facebook Reddit LinkIn

-----------------------

Recent Post:

Celebrating Self-Acceptance: My Journey to Embrace Life

A heartfelt reflection on self-celebration, embracing identity, and the journey of self-acceptance.

Efficiently Solving Differential Equations with FFT Techniques

Explore how Fourier techniques simplify solving differential equations numerically, with practical Python examples.

Revitalize Your Life: Eliminate Negativity and Thrive

Discover effective strategies to rid yourself of negative influences and embrace a more fulfilling life.

Discovering Your Purpose: A Simple Approach to Fulfillment

Unveil the straightforward path to finding your purpose in life through commitment, responsibility, and action.

Innovative Electric Mini-Pickups: Meet Telo Trucks Today

Discover Telo Trucks, the startup redefining electric pickups with compact designs and eco-friendly features.

# Timeless Wisdom: 5 Transformative Habits from Great Philosophers

Explore five impactful habits derived from esteemed philosophers that can enhance your life today.

Mastering ViewState in SwiftUI: A Comprehensive Overview

Explore effective strategies for managing ViewState in SwiftUI, enhancing your app's performance and maintainability.

Mastering the Basics: How Basketball Fundamentals Shape Life

Discover how mastering the fundamentals in basketball translates to success in life and writing.