Testing in Odin

Odin provides a good testing toolbox.

Let’s make a simple project to practice testing.

String count library

We will create a project that wraps around the procedures strings.rune_count and len.

This will be our project structure.

.
├── README.md
├── lib
│   └── String
│       └── count.odin
├── test
│   ├── lib
│   │   └── String
│   │       └── count_test.odin
│   └── test_helper.odin
└──test.bin

This the main sources will be inside the lib/String directory and the test directory will share the same structure of the lib directory.

lib/String/count.odin

The contents of our library will be as follow:

package String

import "core:strings"

length :: proc(binary: string) -> int {
    return strings.rune_count(binary)
}

byte_size :: proc(binary: string) -> int {
    return len(binary)
}

test.bin

This file is generated by Odin compiler to run the tests. You can add it to your .gitignore.

test/test_helper.odin

This file will import all the other tests, and configure general settings.

package Test

// Import the tests inside test/lib
import "lib/String"

// Mark the package as used to pass -vet
_ :: String

test/lib/String/count_test.odin

package Test_String

import "core:log"
import "core:testing"
import "core:os"

// We are using a custom collection path
import "project:lib/String"

// Test are marked with @(test) if this is not present then Odin will not run the test
@(test)
test_that_string_can_count_length :: proc(t: ^testing.T) {

    result := String.length("mate")

    testing.expect_value(t, result, 4)
    
    result = String.length("maté")

    // The last expect must return early so testing.cleanup
    // is not triggered if the test is sucessfull 
    if testing.expect_value(t, result, 4) {
        return
    }

    // Cleans up the test procedure. This is useful
    // when some tests fails and can clodge the testing process.
    // This is a way to ensure the testing process goes smoothly.
    testing.cleanup(t, proc (raw_handle: rawptr) {
        handle := cast(^os.Handle) raw_handle
        os.close(handle^)
        log.debug("Test cleanup")
    }, &result)
}

@(test)
test_that_string_can_count_byte_size :: proc(t: ^testing.T) {

    result := String.byte_size("mate")

    testing.expect_value(t, result, 4)
    
    result = String.byte_size("maté")

    if testing.expect_value(t, result, 5) {
        return
    }

    testing.cleanup(t, proc (raw_handle: rawptr) {
        handle := cast(^os.Handle) raw_handle
        os.close(handle^)
        log.debug("Test cleanup")
    }, &result)
}

Test Command

For running the commands we will be at the root directory of our project and run odin test command. We can pass params to this command, for example we are using a collection named project that holds the path to our root project directory.

$ odin test test/  -vet -all-packages -collection:project=.

If run correctly an output similar to this will appear.

[INFO ] --- [2024-11-18 16:12:34] Starting test runner with 4 threads. Set with -define:ODIN_TEST_THREADS=n.
[INFO ] --- [2024-11-18 16:12:34] The random seed sent to every test is: 32611758867092. Set with -define:ODIN_TEST_RANDOM_SEED=n.
[INFO ] --- [2024-11-18 16:12:34] Memory tracking is enabled. Tests will log their memory usage if there's an issue.
[INFO ] --- [2024-11-18 16:12:34] < Final Mem/ Total Mem> <  Peak Mem> (#Free/Alloc) :: [package.test_name]
Test_String  [||||||||||||||||||      ]       2 :: [package done]

Finished 2 tests in 1.124ms. All tests were successful.
5 Likes