gleedoc
A doc test library for Gleam, inspired by Rust and Elixirβs doctest tooling.
Doc tests let you write executable examples in your documentation comments (///). These examples are extracted, compiled, and run as part of your test suite, ensuring your documentation never goes out of date.
π© Disclaimer: This project contains substantial LLM-generated code, and I used LLMs for research and design. But I (as a Gleam amateur) have tried my best to review line by line, adjust, and refactor.
How it works
- Extract
///doc comments from your.gleamsource files. - Find fenced code blocks tagged with
gleaminside those comments. - Generate test modules in your
test/directory. - Run the generated tests with
gleam test.
How other languages do it
| Language | Approach | Key Difference from Gleam |
|---|---|---|
| Rust | cargo test compiles ```rust blocks from /// comments. No REPL needed. | Gleam follows this model closely. |
| Elixir | doctest Module parses iex> prompts from @doc strings. | Elixir has a REPL; Gleam does not. |
| Python | doctest parses >>> prompts from docstrings. | Python is interpreted; Gleam is compiled. |
Because Gleam is a compiled language with no built-in REPL, gleedoc adopts Rustβs approach: doc blocks are treated as standalone Gleam code that gets compiled and executed. If a block panics, the test fails.
Installation
gleam add gleedoc --dev
Usage
Write doc comments with gleam code blocks in your source files:
// src/math.gleam
/// Adds two numbers together.
///
/// ```gleam
/// let result = add(1, 2)
/// assert result == 3
/// ```
pub fn add(a: Int, b: Int) -> Int {
a + b
}
Then run gleedoc to generate tests:
gleam run -m gleedoc
This creates test/gleedoc/math_gleedoc_test.gleam containing:
// Generated by gleedoc - do not edit manually
import math.{add}
// From: src/math.gleam:4
pub fn add_1_test() {
let result = add(1, 2)
assert result == 3
}
Now run your tests as usual:
gleam test
Imports in generated tests
Each generated test file receives imports from three sources, merged and deduplicated automatically:
- The source module itself β
gleedocscans the moduleβs public names withglanceand generates a list of unqualified imports that include all public functions/types/constants, so you can call functions directly in your snippets. - The source moduleβs own top-level imports β any
importstatements at the top of the source file are carried over, so your snippets can use the same types and helpers the module itself uses without restating them. - Imports written inside the code block β you can always add an explicit
importline inside a snippet for anything extra.
For example, given this source file src/user.gleam:
import gleam/option.{type Option} // 1οΈβ£
/// Returns a greeting for the user.
///
/// ```gleam
/// import gleam/option.{Some} // 2οΈβ£
///
/// let name = Some("Alice")
/// assert greet(name) == "Hello, Alice!"
/// ```
pub fn greet(name: Option(String)) -> String { // 3οΈβ£
name
|> option.map(fn(n) { "Hello, " <> n <> "!" })
|> option.unwrap("")
}
The generated test file user_gleedoc_test.gleam will contain imports merged from all three sources:
// Generated by gleedoc - do not edit manually
import fixtures/user.{greet} // source module public definitions (3οΈβ£)
import gleam/option.{type Option, Some} // source module imports (1οΈβ£) + gleam code block imports (2οΈβ£)
// From: test/fixtures/user.gleam:5
pub fn greet_1_test() {
let name = Some("Alice")
assert greet(name) == "Hello, Alice!"
}
If the same module is imported in multiple places (e.g. gleam/option appears in both the source file and a code block), the unqualified names from all of them are merged into a single import line.
There are more examples in
test/fixturesandtest/integration/gleedoc.
API
You can also use gleedoc programmatically from your test suite:
// test/gleedoc_setup.gleam
import gleedoc
pub fn main() {
let config = gleedoc.GleedocConfig(
source_dir: "src",
output_dir: "test",
extra_imports: [],
)
case gleedoc.run(config) {
Ok(Nil) -> Nil
Error(snag) -> panic as snag.issue
}
}
The GleedocConfig
source_dir: The directory containing all the source files. Path resolution is relative to the project root. Default value:src.output_dir: The directory where all the doc tests will be generated. Path resolution is relative to the project root. Default value:test.extra_imports: A list of module names that will automatically be imported in every test. Unused imports will be removed in the final test. Example value:["gleam/int", "gleam/otp/actor"].- You can see it in action in
dev/fixture/store.gleam
- You can see it in action in
Architecture
src/
gleedoc.gleam # Main entry point and CLI
gleedoc/
extract.gleam # Line-based doc comment extraction
parse.gleam # Markdown code block parsing
generate.gleam # Test file generation
scan.gleam # Public names and imports extraction with glance
Key dependencies
| Package | Role |
|---|---|
glance | Gleam source parser |
simplifile | Cross-target file I/O |
snag | Lightweight error handling |
Development
gleam run -m prepare_tests && gleam test
You can also run the tests with the JavaScript target:
gleam run -m prepare_tests && gleam test -t javascript
Windows
On Windows, you probably want to make sure that autocrlf is false before checking out this repo:
git config --global core.autocrlf false
Contributing
Please kindly create an issue in your human voice, clearly describe the feature request or bug with reproduction steps, and ideally include a proposed solution before creating any PR.
Roadmap
Basics
-
gleeunit-compatible test generation from```gleamfenced code blocks in source filesβ doc comments - Smart imports handling: import merging and import generation from source fileβs public names
-
Single-command
gleam run -m gleedocCLI experience [x] Test file generation with OS-native line breaks:(reverted, as per this comment)\non Linux and Mac,\r\non Windows
Additional features before 1.0
-
Offer an
extra_importsoption to apply extra imports to all generated test files - Source-mapped error reporting
- Automatic formatting for generated tests
-
Single-command
gleam testCLI experience without needing to rungleam run -m gleedocbeforegleam test. - Module level doc tests
-
An
ignoreorskipattribute to exclude a code block from doc test generation
Missing Features (compared to Rust, Elixir, and Python)
π - Planned for 1.0 π - Not Planned for 1.0 β - Implemented
| Feature | Rust | Elixir | Python | gleedoc |
|---|---|---|---|---|
| Single-command CLI experience | β | β | β | π |
ignore / skip attribute | β | β | β | π |
no_run (compile only) | β | β | β | π |
should_panic | β | β | β | π |
Hidden setup lines (#) | β | β | β | π |
Output assertions (// ->) | β | β
(iex>) | β
(>>>) | π |
| Module-level doc tests | β
(//!) | β | β | π |
compile_fail | β | β | β | π |
Multi-target (erlang / javascript) | β
(cfg) | β | β | β |
| Incremental / cached generation | β | β | β | π |
| Source-mapped error reporting | β | β | β | π |
β Know Issues
-
Doesnβt work on Windows due to different path separators -
Generated tests will contain unused imports -
Test file generation is not OS-agnostic (some types of tests would fail on Windows)
The Name
gleeunit for unit tests, gleedoc for doc tests! πΈ