Load Forge: A Custom DSL for API Load Testing
Hey everyone!
Let’s talk about Load Forge, a project I built together with my friends as a university team project to make API load testing a lot more declarative and developer-friendly.
If you’ve ever set up load tests, you know that the logic often ends up scattered across multiple scripts, helper functions, and configuration files. This makes tests slower to author, harder to read, and difficult to review. I wanted a way to define load testing scenarios, authentication, and SLO thresholds using a simple, custom Domain-Specific Language (DSL) instead of writing repetitive, boilerplate code.
So, we built Load Forge: a compiler, runtime, and language designed to do exactly that.
The .lf Language#
The core idea behind Load Forge is that you declare what you want to test in a single .lf file, and the runtime handles the how.
Here is what a standard test looks like:
test "Catalog search - steady load" {
environment {
baseUrl = env("BASE_URL")
}
target #baseUrl
scenario "search" {
request GET "/catalog/search?q=phone"
expect status 200
expect json $.results isArray
}
load {
users 50
rampUp 30s
duration 5m
}
metrics {
p95 < 250ms
errorRate < 1%
}
}
Everything from the target environment to the expected JSON payload and the p95 latency threshold is explicitly stated and easy to read.
Under the Hood (Python & TextX)#
To make this work, we split the project into two main parts: the parser and the execution engine.
- The Parser: We designed the grammar for the custom DSL and used TextX to parse it. The parser reads the
.lffile, normalizes the inputs, and maps everything into typed Python models. - The Execution Engine: Once the models are built, the Python runtime takes over. We leveraged asyncio and httpx to build a high-performance execution engine capable of simulating heavy, concurrent loads. It gathers latency, status codes, and throughput in real-time.
Key Features#
- Flexible Authentication: It supports standard shared-token auth, but also features a per-user authentication mode. You can pass a
.ulf(User List File) via the CLI, and the engine will seamlessly authenticate each virtual user with their own unique credentials. - Rich Assertions: The
expect jsonblock uses JSONPath under the hood, allowing you to easily validate deep nested structures, types, regex matches, and null values. - Live CLI Output: While the test runs, the CLI provides a live-updating progress line showing active users, throughput, and error rates. Once finished, it prints a clean final report.
- VS Code Integration: A new language isn’t very fun to write without good editor support. To fix that, a companion VS Code extension was built to provide syntax highlighting and an integrated test runner right in the editor.
Building our own language and execution engine was a fantastic way to learn about parsing, ASTs, and high-concurrency programming. It was a really fun team challenge that resulted in a genuinely useful tool!