Go SDK tutorial, times10 function

You are a 10x programmer and will soon have your annual performance review, so you are writing a function that takes any number and multiplies it by ten. That way if anybody names some productivity, you can respond with your ten times greater productivity.

You write the function in Go and at first you use print debugging. However, this performance review is very important for your career so you decide not to take any chances. You ultimately decide to thoroughly test this function using the Antithesis Go SDK.

The rest of the document walks you through this process.

Recall that the basic SDK workflow is:

  1. Include Go SDK in dependencies.
  2. Use SDK functions in your code.
  3. Run the Go instrumentor. The instrumentor can be used for:
    1. Assertion cataloging only,
    2. Both assertion cataloging and coverage instrumentation.
  4. Build your Go project.
  5. Deploy your build.

Creating your project

Step 0: Write your project. You begin by writing your function in Go with print debugging.

Make a directory where you would like to create your Go project and cd to that directory. (Our directory is /home/me/Projects – this will be printed in the logs later.)

mkdir myapp
cd myapp

Create a file myapp.go with the following content:

package main

import (
    "fmt"
)

func times10(x int) int {
    result := x * 10
    return result
}

func main() {
    fmt.Println("Hello, world!")
    fmt.Printf("%v x 10 = %v\n", 3, times10(3));
    fmt.Printf("%v x 10 = %v\n", 8, times10(8));
}

You need to create a go.mod file. Do that by:

go mod init myorg.myapp
go mod tidy

Note that it’ll have the content similar to this:

module myorg.myapp

go 1.20

Now build and run:

go build
./myorg.myapp

and it prints

Hello, world!
3 x 10 = 30
8 x 10 = 80

Using our SDK functions

Step 1: Include Go SDK in dependencies

Run

go get github.com/antithesishq/antithesis-sdk-go@latest

Note that this updates your go.mod file to something like this

module myorg.myapp

go 1.20

require github.com/antithesishq/antithesis-sdk-go v0.3.3 // indirect

Step 2: Use SDK functions in your code

You wrote a function to multiply numbers by ten. What sorts of properties should it have? The output should always be even. You should also make sure you are testing a wide variety of inputs, such as both even and odd numbers.

Modify the code. The modifications are individually explained below.

package main

import (
    "fmt"
    "github.com/antithesishq/antithesis-sdk-go/assert"
    "github.com/antithesishq/antithesis-sdk-go/random"
)

type Details map[string]any

func times10(x int) int {
    assert.Sometimes(x % 2 == 1, "input is sometimes odd", Details{"input": x})
    result := x * 10
    assert.Always(result % 2 == 0, "result is always even", Details{"result": result})
    return result
}

func main() {
    fmt.Println("Hello, world!")
    for i := 0; i < 50; i++ {
        x := int(random.GetRandom() % 500);
        fmt.Printf("%v x 10 = %v\n", x, times10(x));
    }
}
  • Lines 5-6 You imported the assert and random libraries.

  • You added two assertions to times10.

    • Line 14 You assert that the result is even using an Always Assertion. This is a fairly conventional assertion.

    • Line 12 You insert a Sometimes Assertion, or an assertion that something will happen at least once across the entire testing session. You assert that sometimes during testing, the function will be called with an odd argument.

      The two types of assertions complement one another: Always Assertions assert that the behavior is correct, whereas Sometimes Assertions assert that you are testing under wide enough conditions to surface any potential incorrect behavior. In this case, the output would trivially be even if you only passed it even inputs – you must ensure your properties are not being trivially satisfied!

  • Lines 20-22 You use randomness to call the function with many random values (between 0 and 500). Previously, you called the function with hardcoded values but now you will pass the function random values and test that the output is always even. This approach is more powerful but makes Sometimes Assertions necessary – now you must test that you are passing the function odd values, whereas previously the tests were hardcoded so you were certain that you were passing it odd values.

    You call the random package’s GetRandom function to draw a random number, and then pass this random number to times10. You use a loop to do this fifty times in a row. Every time the times10 function is called in this loop, it triggers the assertions in lines 12 & 14. In summary: You will test times10 by passing it a random integer fifty times in a row. You have asserted that all fifty outputs must be even and that at least one random input must be odd.

You will now instrument your project using the Go instrumentor. The instrumentor has two user modes. You decide to use the default, which provides both coverage instrumentation and assertion cataloging.

Instrumenting and compiling

Step 3: Run the Go instrumentor with assertion cataloging and coverage instrumentation

When generating coverage instrumentation, the Go instrumentor will write the instrumented code to a target directory. (Nothing in your local directory will be modified.) The target directory must be an existing but empty directory. Create such a directory at the same level as myapp:

cd myapp
mkdir ../myapp_generated_instrumentation

Run the Go instrumentor. For this example, we’re generating both assertion cataloging and coverage instrumentation (which is choice 3b in the outline of steps above):

`go env GOPATH`/bin/antithesis-go-instrumentor . ../myapp_generated_instrumentation/

Step 4: Build

Build your project.

cd ../myapp_generated_instrumentation/customer
go build

Ensure that you run the Go instrumentor before you compile your Go code. You must rerun the Go instrumentor every time you add new assertions, so that the new assertions will be cataloged.

If you wanted to disable assertions, you could instead use the build tag no_antithesis_sdk.

E.g. go build -tags no_antithesis_sdk

Step 5: Deploy your project

You are now ready to build your project. If you are building for local deployment then you would be done here, but suppose instead you intend to send your project to Antithesis for testing. Antithesis will explore your software and search for violations of the properties you have defined.

You must send us containers as described in getting started.

  • The executable should be included in your container. In this example, myapp_generated_instrumentation/customer/myorg.myapp.
  • The instrumentation symbols () should be included in your configuration image. You will need to send Antithesis file with precisely the same name as the symbols file. In this example, myapp_generated_instrumentation/symbols/go-182b876ae1ad.sym.tsv.

This example is simplified compared to what the full Getting Started guide describes. Note that the function main is the workload and times10 is the software itself. There are no dependencies. The instrumentation symbols will go in the configuration image as stated.

Ultimately, you will receive a triage report that confirms the properties you defined are true: the output of the function is always even and the inputs to the function are sometimes odd. You now have a well-tested function that can multiply numbers by ten.