| Crates.io | clnrm |
| lib.rs | clnrm |
| version | 1.3.0 |
| created_at | 2025-10-14 01:05:39.219787+00 |
| updated_at | 2025-11-01 05:25:55.719062+00 |
| description | Cleanroom Testing Framework - CLI tool |
| homepage | |
| repository | https://github.com/seanchatmangpt/clnrm |
| max_upload_size | |
| id | 1881394 |
| size | 228,305 |
A hermetic integration testing framework that executes tests in isolated Docker containers with OpenTelemetry validation. Define tests declaratively using TOML configuration files and validate runtime behavior with Weaver schema validation.
brew tap seanchatmangpt/clnrm
brew install clnrm
cargo install clnrm
Test an API service with database integration. Weaver automatically validates that your telemetry follows OpenTelemetry semantic conventions, ensuring correct instrumentation without manual trace inspection.
# Run the test
clnrm run tests/api_with_database.clnrm.toml
[meta]
name = "api_with_database"
version = "1.0.0"
description = "API service with database - Weaver validates telemetry structure"
# Enable Weaver schema validation
[weaver]
enabled = true
registry_path = "registry"
otlp_port = 0 # Auto-discover available port
admin_port = 0 # Auto-discover available port
# Configure OpenTelemetry export to Weaver
[otel]
exporter = "otlp-http"
resources = {
"service.name" = "api_service",
"deployment.environment" = "test"
}
# Multiple services working together
[service.api]
plugin = "generic_container"
image = "my-api:latest"
[service.database]
plugin = "generic_container"
image = "postgres:15-alpine"
# Scenario that emits rich telemetry
[[scenario]]
name = "api_handles_user_request"
service = "api"
run = "my-api --endpoint /api/v1/users"
artifacts.collect = ["spans:default"]
# Validate HTTP server span
[[expect.span]]
name = "http.server.request"
kind = "server"
attrs.all = {
"http.method" = "GET",
"http.route" = "/api/v1/users"
}
# Validate database query span (must be child of HTTP span)
[[expect.span]]
name = "db.query"
kind = "client"
parent = "http.server.request"
attrs.all = {
"db.system" = "postgresql",
"db.operation" = "SELECT"
}
# Validate trace graph structure - proves services actually communicated
[expect.graph]
must_include = [
["http.server.request", "db.query"] # HTTP span must have DB child
]
acyclic = true # No cycles allowed (proves correct trace structure)
# Validate temporal ordering - proves operations happened in correct sequence
[expect.order]
must_precede = [
["http.server.request", "db.query"], # Request must come before query
["db.query", "http.server.response"] # Query must come before response
]
# Ensure no external service leaks - catch accidental production calls
[expect.hermeticity]
no_external_services = true
span_attrs.forbid_keys = ["net.peer.name"] # Forbid external hostnames
This test validates behavior, not just exit codes. Consider what traditional testing misses:
Traditional Testing Problem:
#!/bin/bash
# Fake-green test - passes but does nothing
echo "✅ Test passed"
exit 0
# ❌ Database never queried
# ❌ API never handled request
# ❌ Services never interacted
# ✅ Traditional testing: PASS (exit code 0)
OTEL-First Validation Solution: Your test fails if:
OTEL-first validation requires proof of execution through telemetry:
Weaver automatically validates all of this against OpenTelemetry schemas—no manual trace inspection needed. The test fails if your code doesn't actually execute correctly, even if it returns exit code 0.
Core Testing
OpenTelemetry Integration
Behavior Validation (Not Just Exit Codes) Unlike traditional testing that only checks return codes, clnrm validates actual execution through telemetry:
CLI Commands
clnrm init - Initialize new test projectclnrm run - Execute test files with Weaver validationclnrm validate - Validate TOML configurationclnrm plugins - List available service pluginsclnrm self-test - Run framework self-validationCleanroom supports comprehensive OpenTelemetry configuration directly in TOML test files:
Enable automatic schema validation:
[weaver]
enabled = true # Enable Weaver validation
registry_path = "registry" # Path to schema registry
otlp_port = 0 # Auto-discover (0) or fixed port
admin_port = 0 # Auto-discover (0) or fixed port
output_dir = "./validation_output" # Validation report directory
stream = false # Streaming output (real-time)
fail_fast = false # Stop on first violation
[otel]
exporter = "otlp-http" # Export format: stdout, otlp-http, otlp-grpc
endpoint = "http://localhost:4318" # OTLP endpoint URL
protocol = "http/protobuf" # Protocol: http/protobuf, grpc, http/json
sample_ratio = 1.0 # Sampling rate (0.0-1.0)
# Resource attributes
resources = {
"service.name" = "my_service",
"service.version" = "1.0.0",
"deployment.environment" = "test"
}
# Custom headers
headers = {
"Authorization" = "Bearer token"
}
# Context propagators
propagators.use = ["tracecontext", "baggage"]
Validate span structure and attributes:
[[expect.span]]
name = "http.request" # Span name (supports globs)
kind = "server" # Span kind: internal, client, server, producer, consumer
parent = "http.server.request" # Parent span name
# Attribute validation
attrs.all = { # All attributes must match
"http.method" = "GET",
"http.route" = "/api/users"
}
attrs.any = { # Any attribute must match
"http.status_code" = "200"
}
# Event validation
events.all = ["http.request.received", "http.response.sent"]
events.any = ["exception"]
# Duration bounds
duration_ms = { min = 10.0, max = 1000.0 }
Validate trace topology:
[expect.graph]
# Required edges
must_include = [
["http.server.request", "db.query"],
["db.query", "cache.get"]
]
# Forbidden edges
must_not_cross = [
["external.service", "internal.service"]
]
acyclic = true # Ensure no cycles
[expect.counts]
spans_total = { gte = 1, lte = 100 } # Total span count bounds
events_total = { gte = 5 } # Total event count
errors_total = { eq = 0 } # Must have zero errors
# Per-span-name counts
by_name = {
"http.request" = { eq = 10 }, # Exactly 10 http.request spans
"db.query" = { gte = 1 } # At least 1 db.query span
}
[expect.order]
# First must precede second
must_precede = [
["auth.check", "db.query"],
["db.query", "cache.set"]
]
# First must follow second
must_follow = [
["response.sent", "request.received"]
]
[[expect.window]]
outer = "http.server.request" # Outer span defining time window
contains = [ # Spans that must be within window
"db.query",
"cache.get",
"auth.check"
]
[expect.status]
all = "OK" # All spans must have OK status
# Or per-span-name
by_name = {
"http.request" = "OK",
"error.*" = "ERROR" # Supports glob patterns
}
Ensure tests don't leak to external services:
[expect.hermeticity]
no_external_services = true # Forbid external service calls
# Resource attributes must match exactly
resource_attrs.must_match = {
"service.name" = "my_service",
"deployment.environment" = "test"
}
# Forbid certain span attributes (e.g., external network calls)
span_attrs.forbid_keys = [
"net.peer.name", # No external hosts
"http.url" # No external URLs
]
Contributions are welcome. See CONTRIBUTING.md for guidelines.
Licensed under the MIT License. See LICENSE for details.
Repository: github.com/seanchatmangpt/clnrm