Tod is a command-line tool for running tests on-demand on Jenkins. It helps you trigger and manage Jenkins builds based on your Git commits and custom test filters, making CI/CD workflows more efficient.
- 🚀 On-Demand Test Execution - Trigger Jenkins builds for specific commits
- 🔍 Smart Branch Detection - Automatically identifies the correct reference branch
- 📊 Build Tracking - Monitors and synchronizes build status
- 📧 Email Reports - Sends build results via email
- 🎯 Filter-Based Job Selection - Use regex patterns to select which jobs to run
- 💾 Local Workspace - Caches build history for faster operations
dotnet tool install --global Tod
git clone https://github.com/dedale/tod.git
cd tod
dotnet build
dotnet pack src/Tod/Tod.csproj --configuration Release
dotnet tool install --global --add-source ./src/Tod/bin/packages Tod
Create a jenkins_config.json file with your Jenkins settings:
{
"Url": "https://jenkins.example.com",
"MultiBranchFolders": ["MyProject"],
"ReferenceJobs": [
{
"Pattern": "^MAIN-(?<root>build)$",
"BranchName": "main",
"IsRoot": true
},
{
"Pattern": "^MAIN-(?<test>.)$",
"BranchName": "main",
"IsRoot": false
}
],
"OnDemandJobs": [
{
"Pattern": "CUSTOM-(?<root>build)$",
"IsRoot": true
},
{
"Pattern": "CUSTOM-(?<test>.)$",
"IsRoot": false
}
],
"RootFilters": [
{
"Name": "build",
"Pattern": "^build$"
}
],
"ChainTestGroup": "chains",
"TestFilters": [
{
"Name": "unit",
"Pattern": "^unit-tests$",
"Group": "tests"
},
{
"Name": "integration",
"Pattern": "^integration-tests$",
"Group": "tests"
}
],
"MailConfig":
{
"SmtpHost": "smtp.example.com",
"From": "[email protected]"
},
"KeptDays": 30
}tod sync --config jenkins_config.json --workspace ./workspace --jenkins-token YOUR_JENKINS_TOKEN --jobs
tod new --config jenkins_config.json --workspace ./workspace --branch main --root-filters build --test-filters unit integration --jenkins-token YOUR_JENKINS_TOKEN --gerrit-token YOUR_GERRIT_TOKEN
The Jenkins configuration file (jenkins_config.json) defines how Tod interacts with your Jenkins instance.
| Property | Type | Description |
|---|---|---|
Url |
string | Jenkins server URL |
MultiBranchFolders |
string[] | Folders containing multi-branch pipeline jobs |
KeptDays |
int? | Number of days to keep build history (optional) |
MaxUserActiveRequests |
int? | Maximum number of active requests per user (optional) |
Define patterns for reference branch jobs (e.g., main, develop):
{
"Pattern": "^MAIN-(?<root>build)$",
"BranchName": "main",
"IsRoot": true
}Pattern: Regex pattern to match job namesBranchName: Git branch this job buildsIsRoot:truefor root/build jobs,falsefor test jobs- Named groups:
(?<root>...)for root jobs,(?<test>...)for test jobs
Define patterns for custom/on-demand jobs:
{
"Pattern": "CUSTOM-(?<root>build)$",
"IsRoot": true
}Define which root jobs to run:
{
"Name": "build",
"Pattern": "^build$"
}Supports chain patterns with named groups:
{
"Name": "frontend-build",
"Pattern": "^(?<chain>frontend)-build$"
}Define which test jobs to run:
{
"Name": "unit",
"Pattern": "^unit-tests$",
"Group": "tests"
}Name: Filter identifierPattern: Regex pattern to match test job namesGroup: Logical grouping (useChainTestGroupvalue for chain-linked tests)
The ChainTestGroup property links test filters to root filters via chain patterns.
{
"SmtpHost": "smtp.example.com",
"From": "[email protected]"
}Load thresholds protect Jenkins from being overloaded by preventing requests when the server is under high load. Configure thresholds based on queue size and estimated request duration:
{
"LoadThresholds": [
{
"QueueSize": 50,
"MaxRequestDuration": "01:00:00"
},
{
"QueueSize": 100,
"MaxRequestDuration": "00:30:00"
}
]
}Properties:
QueueSize: Maximum number of builds in Jenkins queueMaxRequestDuration: Maximum total duration for the request (format: "HH:MM:SS")
How it works:
- Before registering a new request, Tod checks the current Jenkins queue size
- Tod estimates the total duration of all builds in the request
- If both the queue size and duration exceed any configured threshold, the request is rejected
- Multiple thresholds allow different limits based on load (e.g., stricter limits when queue is larger)
Example scenarios:
| Queue Size | Request Duration | Threshold 1 (50, 1h) | Threshold 2 (100, 30min) | Result |
|---|---|---|---|---|
| 30 | 45 min | 👍 Both OK | 👍 Queue OK | ✅ Accepted |
| 60 | 75 min | 🔥 Both exceeded | 👍 Queue OK | ❌ Rejected |
| 60 | 20 min | 👍 Duration OK | 👍 Both OK | ✅ Accepted |
| 110 | 35 min | 👍 Duration OK | 🔥 Both exceeded | ❌ Rejected |
Note: If no thresholds are configured, all requests are accepted regardless of Jenkins load.
The MaxUserActiveRequests setting limits how many active requests a single user can have running simultaneously. This prevents individual users from overwhelming the Jenkins server with too many concurrent requests.
{
"MaxUserActiveRequests": 3
}How it works:
- Before registering a new request, Tod counts the user's currently active requests
- An active request is one that has at least one chain not yet completed
- If the user already has the maximum number of active requests, the new request is rejected
- Completed requests do not count toward the limit
- Each user's limit is tracked independently
Example scenarios:
| User Active Requests | Max Limit | Result |
|---|---|---|
| 0 | 3 | ✅ Accepted |
| 2 | 3 | ✅ Accepted |
| 3 | 3 | ❌ Rejected |
| 5 | 3 | ❌ Rejected |
Note: If MaxUserActiveRequests is not configured, users can create unlimited requests.
When GerritReviewServer is configured, Tod verifies that the commit exists in Gerrit before creating a request:
{ "GerritReviewServer": "https://gerrit.example.com" }How it works:
- Before triggering builds, Tod queries Gerrit to verify the commit exists as a patchset
- If the commit is not found, the request is rejected with an error
- This prevents Jenkins from failing to checkout code that hasn't been pushed to Gerrit
- Uses the same authentication token as Jenkins by default, or a dedicated Gerrit token if provided via
--gerrit-token
Note: If GerritReviewServer is not configured, this check is skipped.
Synchronize build history from Jenkins.
tod sync --config jenkins_config.json --workspace ./workspace --user-token TOKEN
tod sync --config jenkins_config.json --workspace ./workspace --user-token TOKEN --jobs
Options:
-c, --config(required): Path to Jenkins config file-w, --workspace(required): Path to workspace directory-u, --user-token(required): Jenkins API token-j, --jobs: Sync job definitions instead of builds
Create a new on-demand test request.
tod new --config jenkins_config.json --workspace ./workspace --branch main --root-filters build --test-filters unit integration --user-token TOKEN
Options:
-c, --config(required): Path to Jenkins config file-w, --workspace(required): Path to workspace directory-b, --branch: Reference branch (auto-detected if not specified)-r, --root-filters(required): Root filter names to run-t, --test-filters(required): Test filter names to run-u, --user-token(required): Jenkins API token
How it works:
- Detects your current Git commit
- Finds the matching reference build on the specified branch
- Triggers on-demand builds with your changes
- Tracks build progress and collects results
Preview which jobs would be triggered without actually running them.
tod jobs --config jenkins_config.json --workspace ./workspace --branch main --root-filters build --test-filters unit integration
Options:
-c, --config(required): Path to Jenkins config file-w, --workspace(required): Path to workspace directory-b, --branch: Reference branch-r, --root-filters(required): Root filter names-t, --test-filters(required): Test filter names
Output:
- Lists root and test jobs that would be triggered
- Shows estimated duration based on historical data
Send an email report for a request (completed or not).
tod report --config jenkins_config.json --workspace ./workspace --request-id 12345678-1234-1234-1234-123456789abc
Options:
-c, --config(required): Path to Jenkins config file-w, --workspace(required): Path to workspace directory-i, --request-id(required): Request UUID to report on
Abort a running or queued request.
tod abort --config jenkins_config.json --workspace ./workspace --request-id 12345678-1234-1234-1234-123456789abc
Options:
-c, --config(required): Path to Jenkins config file-w, --workspace(required): Path to workspace directory-i, --request-id(required): Request UUID to abort
Authorization:
- Users can only abort their own requests
- Returns an error if you try to abort someone else's request
How it works:
- Validates the request ID format
- Looks up the request in the workspace
- Verifies you are the owner of the request
- Marks all chains in the request as aborted
- Saves the updated request state
Note: Aborting a request marks it as complete but does not stop Jenkins builds that are already running. It prevents Tod from tracking those builds further.
List your test requests and their status.
tod list --config jenkins_config.json --workspace ./workspace
Options:
-c, --config(required): Path to Jenkins config file-w, --workspace(required): Path to workspace directory-a, --all: List all requests (including completed ones). By default, only active requests are shown.
Output: For each request belonging to the current user, displays:
- Request ID (UUID)
- Creation timestamp
- Branch and commit
- Test filters used
- Overall status (Active/Done)
- Chain status for each job chain:
- Root Triggered: Root build has been triggered
- Tests Triggered: Root build completed, test builds triggered
- Done: All builds in the chain are complete
Example output:
Found 2 active requests for user [email protected]:
Request ID: 12345678-1234-1234-1234-123456789abc
Created: 2024-01-15 10:30:00
Branch: main
Commit: abc123def456
Filters: unit;integration
Status: Active
Chain CUSTOM-build: Tests Triggered
Chain CUSTOM-deploy: Root Triggered
Request ID: 87654321-4321-4321-4321-210987654321
Created: 2024-01-15 09:15:00
Branch: develop
Commit: def456abc123
Filters: unit
Status: Done
Chain CUSTOM-build: Done
List all jobs grouped by filters.
tod filters --config jenkins_config.json --workspace ./workspace
Options:
-c, --config(required): Path to Jenkins config file-w, --workspace(required): Path to workspace directory
Output:
- Shows all chains and their associated jobs
- Lists test groups and their jobs
- Reports any configuration errors (unmatched filters, missing jobs)
Tod creates a local workspace to cache build information:
workspace/
├── Branches/
│ ├── main/
│ │ ├── Roots/
│ │ │ └── build.json
│ │ └── Tests/
│ │ ├── unit-tests.json
│ │ └── integration-tests.json
│ └── develop/
├── OnDemand/
│ ├── Roots/
│ └── Tests/
├── Requests/
│ └── {request-id}.json
└── Flaky/
└── flaky-tests.json
tod sync -c jenkins.json -w ./workspace -u $JENKINS_TOKEN --jobs
tod sync -c jenkins.json -w ./workspace -u $JENKINS_TOKEN
tod new -c jenkins.json -w ./workspace -r build -t unit integration -u $JENKINS_TOKEN
tod new -c jenkins.json -w ./workspace -b develop -r build -t unit -u $JENKINS_TOKEN
tod jobs -c jenkins.json -w ./workspace -r build -t unit integration
tod filters -c jenkins.json -w ./workspace
tod report -c jenkins.json -w ./workspace -i 12345678-1234-1234-1234-123456789abc
tod abort -c jenkins.json -w ./workspace -i 12345678-1234-1234-1234-123456789abc
# List only active requests
tod list -c jenkins.json -w ./workspace
# List all requests (including completed)
tod list -c jenkins.json -w ./workspace --all
Tod requires a Jenkins API token for authentication:
- Log in to Jenkins
- Go to User → Configure → API Token
- Click Add new Token
- Copy the generated token
- Use it with the
-uor--user-tokenoption
Security tip: Store your token in an environment variable:
# bash
export JENKINS_TOKEN="your-token-here" tod sync -c jenkins.json -w ./workspace -u $JENKINS_TOKEN
# PowerShell
$env:JENKINS_TOKEN = "your-token-here"
tod sync -c jenkins.json -w ./workspace -u $env:JENKINS_TOKEN
# cmd
set JENKINS_TOKEN=your-token-here
tod sync -c jenkins.json -w .\workspace -u %JENKINS_TOKEN%
- .NET 10 SDK or later
- Git
- Visual Studio 2025 or later (recommended)
dotnet restore dotnet build
dotnet test
dotnet test --collect:"XPlat Code Coverage"
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
This project is licensed under the MIT License - see the LICENSE file for details.
- Timeout support for FileLock
- Support complex job dependency graphs
- Support job renaming
- Multiple changesets support (identify the right one containing the files to test) (needed?)
- Hardcoded build count in JenkinsClient
- Serialization UT: ensure that json converters are needed
- Support lost commits? (not in any root builds)
- Better support for test builds timeouts (missing UT) (use next build?)
- Ignore test builds without tests?
- Handle JobGroup generation failure
- Handle trailing slash in Jenkins URL
- Serialization UT: ensure that json converters are needed
- Auto save branch references when adding new ones
- Transactional triggering of requests, safe resuming without double triggering
- ChainStatus is wrong (TestTriggered when tests are done but ref still pending)
- GANTT diagram in report
- Archive or purge done requests
- Improve performance (if needed) when looking for requests to update
- Force new root build for a request (retrigger all its builds)
- Storage abstraction for on-demand requests
- Sha1 validation in ctor
- Check local commit has been pushed (or push it automatically?)
- Hardcoded git commit count in history
- Agent mode to automatically synchronize workspace and trigger requests periodically
- Remove NextBuildNumber limit and improve UTs that fail with the same build number