A test runner for Envoy ExtProc implementations
Features • Installation • Quick Start • Documentation • Examples • Contributing
Implementing and evolving Envoy External Processing (ExtProc) services is error-prone. Behaviours depend on correct protobuf message structures and a sequence of callbacks that are difficult to test manually.
Note
This project is heavily inspired by Google Service Extensions where they use the same approach to test their service extensions.
ExtProctor provides a dedicated test runner that enables:
- ✅ Automated regression testing for ExtProc implementations
- ✅ Fast feedback loops during local development
- ✅ CI/CD integration with machine-readable outputs
- ✅ Version-controlled test cases using human-readable prototext manifests
| Feature | Description |
|---|---|
| 📝 Prototext Manifests | Define test cases using human-readable prototext format |
| 🔄 Full ExtProc Support | Test all processing phases: headers, body, and trailers |
| 📸 Golden Files | Capture and compare responses using the golden file pattern |
| ⚡ Parallel Execution | Run tests concurrently for faster feedback |
| 🏷️ Flexible Filtering | Filter tests by name pattern or tags |
| 📊 Multiple Output Formats | Human-readable or JSON output for CI integration |
| 🔌 Unix Socket Support | Connect to ExtProc services via Unix domain sockets |
| 🔒 TLS Support | Secure gRPC connections with client certificates |
go install zntr.io/extproctor/cmd/extproctor@latestgit clone https://github.com/zntrio/extproctor.git
cd extproctor
go build -o extproctor ./cmd/extproctorextproctor --helpCreate a file tests/basic.textproto:
name: "basic-test"
description: "Basic ExtProc test"
test_cases: {
name: "add-header"
description: "Verify ExtProc adds a custom header"
tags: ["smoke"]
request: {
method: "GET"
path: "/api/v1/users"
scheme: "https"
authority: "api.example.com"
headers: {
key: "content-type"
value: "application/json"
}
}
expectations: {
phase: REQUEST_HEADERS
headers_response: {
set_headers: {
key: "x-custom-header"
value: "custom-value"
}
}
}
}
extproctor run ./tests/ --target localhost:50051Running tests from 1 manifest(s)...
✓ basic-test/add-header (12ms)
Results: 1 passed, 0 failed, 0 skipped
Execute tests against an ExtProc service.
# Run all tests in a directory
extproctor run ./tests/ --target localhost:50051
# Run with Unix domain socket
extproctor run ./tests/ --unix-socket /var/run/extproc.sock
# Run with parallel execution
extproctor run ./tests/ --target localhost:50051 --parallel 4
# Filter by test name pattern
extproctor run ./tests/ --target localhost:50051 --filter "auth*"
# Filter by tags
extproctor run ./tests/ --target localhost:50051 --tags "smoke,regression"
# JSON output for CI pipelines
extproctor run ./tests/ --target localhost:50051 --output json
# Verbose mode for debugging
extproctor run ./tests/ --target localhost:50051 -v
# Update golden files
extproctor run ./tests/ --target localhost:50051 --update-goldenValidate manifest syntax without running tests.
# Validate all manifests in a directory
extproctor validate ./tests/
# Validate specific files
extproctor validate test1.textproto test2.textproto| Flag | Description | Default |
|---|---|---|
--target |
ExtProc service address (host:port) | localhost:50051 |
--unix-socket |
Unix domain socket path | — |
--tls |
Enable TLS for gRPC connection | false |
--tls-cert |
TLS client certificate file | — |
--tls-key |
TLS client key file | — |
--tls-ca |
TLS CA certificate file | — |
-p, --parallel |
Number of parallel test executions | 1 |
-o, --output |
Output format (human, json) |
human |
-v, --verbose |
Enable verbose output | false |
--filter |
Filter tests by name pattern | — |
--tags |
Filter tests by tags (comma-separated) | — |
--update-golden |
Update golden files with actual responses | false |
Note:
--targetand--unix-socketare mutually exclusive.
Test manifests are written in Prototext format.
name: "manifest-name"
description: "Description of the test suite"
test_cases: {
name: "test-case-name"
description: "What this test validates"
tags: ["tag1", "tag2"]
request: {
method: "POST"
path: "/api/endpoint"
scheme: "https"
authority: "api.example.com"
headers: {
key: "content-type"
value: "application/json"
}
body: '{"key": "value"}'
process_request_body: true
process_response_headers: true
}
expectations: {
phase: REQUEST_HEADERS
headers_response: {
set_headers: {
key: "x-custom"
value: "value"
}
}
}
}
| Phase | Description |
|---|---|
REQUEST_HEADERS |
Processing request headers |
REQUEST_BODY |
Processing request body |
REQUEST_TRAILERS |
Processing request trailers |
RESPONSE_HEADERS |
Processing response headers |
RESPONSE_BODY |
Processing response body |
RESPONSE_TRAILERS |
Processing response trailers |
Headers Response
expectations: {
phase: REQUEST_HEADERS
headers_response: {
set_headers: {
key: "x-custom"
value: "value"
}
remove_headers: "x-internal"
append_headers: {
key: "x-multi"
value: "value"
}
}
}
Body Response
expectations: {
phase: REQUEST_BODY
body_response: {
body: '{"modified": true}'
common_response: {
status: CONTINUE_AND_REPLACE
}
}
}
Trailers Response
expectations: {
phase: REQUEST_TRAILERS
trailers_response: {
set_trailers: {
key: "x-checksum-validated"
value: "true"
}
}
}
Immediate Response (Short-circuit)
expectations: {
phase: REQUEST_HEADERS
immediate_response: {
status_code: 403
headers: {
key: "content-type"
value: "application/json"
}
body: '{"error": "forbidden"}'
}
}
Use golden files for snapshot testing:
test_cases: {
name: "golden-test"
request: { ... }
golden_file: "golden/test-response.textproto"
}
Update golden files when behavior changes intentionally:
extproctor run ./tests/ --target localhost:50051 --update-goldenThe testdata/examples/ directory contains complete example manifests:
| File | Description |
|---|---|
basic_headers.textproto |
Basic header processing (add/remove headers) |
auth_flow.textproto |
Authentication flow with immediate response rejection |
body_processing.textproto |
Request body inspection and transformation |
multi_phase_flow.textproto |
Multi-phase processing across request/response lifecycle |
A sample ExtProc server is included for testing and reference:
# Start the sample server
go run ./sample/extproc/ --addr :50051
# Run tests against it
extproctor run ./sample/extproc/test/ --target localhost:50051The sample server demonstrates:
- Request headers processing with custom header injection
- Request/response body handling
- Response headers modification
- gRPC health check endpoint
go build -o extproctor ./cmd/extproctorgo test ./...buf generateextproctor/
├── cmd/extproctor/ # CLI entry point
├── internal/
│ ├── cli/ # Command-line interface
│ ├── client/ # ExtProc gRPC client
│ ├── comparator/ # Response comparison logic
│ ├── golden/ # Golden file handling
│ ├── manifest/ # Manifest loading and validation
│ ├── reporter/ # Test result reporting
│ └── runner/ # Test execution engine
├── proto/ # Protobuf definitions
├── sample/extproc/ # Sample ExtProc server
└── testdata/examples/ # Example test manifests
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Please make sure to:
- Update tests as appropriate
- Follow the existing code style
- Update documentation for any new features
This project is licensed under the MIT License - see the LICENSE file for details.
Made with ❤️ for the Envoy community