For SwiftNIO, we have an integration testing tool that we call the "allocation counter tests". The way they work is the following. You write a small test in the form of
import Module1YouNeedAvailableInTheTests
import Module2YouNeedAvailableInTheTests
func run(identifier: String) {
// setup
measure(identifier: identifier) {
// what you want tested
}
// tear down
}
in a .swift
file, for example our test_read_10000_chunks_from_file.swift
test.
The main goal of the allocation counter tests is to count the number of allocations made during a test
. But as a side-effect, we also know the number of unfreed allocations, and those are the memory leaks. It measures everything that goes on in the whole program (all threads, all modules, C code, Swift code, ...) whilst the measure { ... }
block is running.
The framework is pretty crude and there's not that much documentation (some is available in the debugging allocations and the general allocation counter docs.
Our CI then runs a terribly ugly script that runs all the allocation counter tests and then parses their output to verify a few things:
- compare the number of allocations against the configured limits
- check that we have no unfreed allocations (memory leaks)
The output of each of the allocation counter tests looks like:
test_future_lots_of_callbacks.remaining_allocations: 0
test_future_lots_of_callbacks.total_allocations: 75001
test_future_lots_of_callbacks.total_allocated_bytes: 4138056
So for each test we log
-
total_allocations
(total number of allocations, usually to be divided by 1000 because we usually do 1000 iterations per test)
-
remaining_allocations
: number of mallocs - number of frees = number of memory leaks
-
total_allocated_bytes
: total amount of bytes allocated during this test
The framework is implemented in a mix of bash (glue and SwiftPM project generation), C (hooking malloc, free, and friends), and Swift. It's pretty crude, there's not too much documentation but it's written in a way that it can support pretty much any SwiftPM project (I'd hope). If this sounds interesting, please reach out to us, we're very happy to help with more info.
The basic usage (sorry, it's ugly) is:
"/path/to/checkout/of/swift-nio/IntegrationTests/allocation-counter-tests-framework/run-allocation-counter.sh" \
-p "/path/to/your/package" \
-m Module1YouNeedAvailableInTheTests \
-m Module2YouNeedAvailableInTheTests \
-d <( echo '.package(url: "https://some/dependency/that/you/have.git", from: "1.0.0"),' ) \
test_my_first_alloc_counter_test.swift test_my_second_alloc_counter_test.swift
This is how NIO's HTTP/2 module runs its alloc counter tests: swift-nio-http2/run-nio-http2-alloc-counter-tests.sh at main · apple/swift-nio-http2 · GitHub .