Tod 0.9.1
dotnet tool install --global Tod --version 0.9.1
dotnet new tool-manifest
dotnet tool install --local Tod --version 0.9.1
#tool dotnet:?package=Tod&version=0.9.1
nuke :add-package Tod --version 0.9.1
Tod
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.
Features
- 🚀 On-Demand Test Execution - Trigger Jenkins builds for specific commits
- 🔍 Smart Branch Detection - Automatically identifies the correct baseline 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
Installation
As a .NET Tool (Recommended)
dotnet tool install --global Tod
From Source
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
Quick Start
1. Create a Configuration File
Create a jenkins_config.json file with your Jenkins settings:
{
"Url": "https://jenkins.example.com",
"MultiBranchFolders": ["MyProject"],
"BaselineJobs": [
{
"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": "jenkins@example.com"
},
"KeptDays": 30,
"BaselineReportConfig": {
"Enabled": true,
"HideFlakyTests": true
}
}
2. Initialize Workspace
tod sync --config jenkins_config.json --workspace ./workspace --jenkins-token YOUR_JENKINS_TOKEN --jobs
3. Create a Test Request
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
Configuration File Reference
The Jenkins configuration file (jenkins_config.json) defines how Tod interacts with your Jenkins instance.
Core Settings
| 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) |
MaxErrorDetailsLength |
int | Maximum length of error details in reports (default: 1000) |
GerritReviewServer |
string? | Gerrit review server URL (optional). When set, verifies commits exist in Gerrit before triggering Jenkins builds |
BaselineReportConfig |
BaselineReportConfig? | Configuration for automatic baseline build reports (optional). See Baseline Build Reports |
Job Patterns
Baseline Jobs
Define patterns for baseline 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
On-Demand Jobs
Define patterns for custom/on-demand jobs:
{
"Pattern": "CUSTOM-(?<root>build)$",
"IsRoot": true
}
Filters
Root Filters
Define which root jobs to run:
{
"Name": "build",
"Pattern": "^build$"
}
Supports chain patterns with named groups:
{
"Name": "frontend-build",
"Pattern": "^(?<chain>frontend)-build$"
}
Test Filters
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.
Mail Configuration
{
"SmtpHost": "smtp.example.com",
"From": "jenkins@example.com"
}
Load Thresholds
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.
User Request Limits
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.
Gerrit Integration
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.
Baseline Build Reports
When BaselineReportConfig is configured, Tod automatically sends email reports to commit authors when baseline builds complete with new test failures:
{
"BaselineReportConfig": {
"Enabled": true
}
}
Properties:
Enabled: Enable or disable automatic baseline reports (required)
How it works:
- Monitors baseline branch builds (e.g., main, develop) for completion
- When all test builds for a root build are complete, analyzes test failures
- Compares current test results with the last successful build to identify new failures
- Sends email report to all commit authors if new failures are detected
- Includes failed builds in the report (accumulates authors from consecutive failed builds)
Example scenarios:
| Scenario | Result |
|---|---|
| All tests pass | No report sent |
| 5 new test failures | Report sent to commit authors |
| Failed build #100, #101, then successful #102 with failures | Report includes authors from all 3 builds |
| No commit author info (old builds) | No report sent |
Email report includes:
- Chain name and root build numbers
- List of commits and their authors
- Summary of new failures vs total failures
- Details of each new failed test (not recurring failures)
- Flaky test indicators
Note: Baseline reports use the same SMTP configuration as on-demand reports (MailConfig).
Service Mode
Tod can be run as a service or daemon to automate testing workflows. In service mode, you typically need to separate:
- Request ownership: The developer who created the request (receives email reports)
- API authentication: The service account credentials used to access Jenkins and Gerrit
Service User Authentication
Use the --service-user option to specify the service account username for API access:
# Sync builds using service account
tod sync \
--config jenkins_config.json \
--workspace ./workspace \
--jenkins-token $SERVICE_TOKEN \
--service-user jenkins-bot
# Create request on behalf of a developer
tod new \
--config jenkins_config.json \
--workspace ./workspace \
--root-filters build \
--test-filters unit integration \
--user john.doe \
--domain CORP \
--service-user jenkins-bot \
--jenkins-token $SERVICE_TOKEN \
--gerrit-token $SERVICE_TOKEN
Authentication Flow
| Parameter | Purpose | Example | Used For |
|---|---|---|---|
--service-user |
API authentication | jenkins-bot |
Jenkins/Gerrit API calls |
--user |
Request ownership | john.doe |
Email notifications, request tracking |
--domain |
User domain | CORP |
Email address resolution |
When to use each parameter:
--service-user: Always use when Tod runs as a service with a dedicated service account--user: Use when creating requests on behalf of other users (e.g., via web API)--domain: Use when the service runs in a different domain than the users
Defaults:
- If
--service-useris not specified, defaults to the current system user (Environment.UserName) - If
--useris not specified (innewcommand), defaults to the current system user - If
--domainis not specified, defaults to the current system domain (Environment.UserDomainName)
Example: Automated Service
Here's a typical setup for running Tod as a scheduled service:
#!/bin/bash
# Service configuration
SERVICE_USER="jenkins-bot"
SERVICE_TOKEN="$JENKINS_SERVICE_TOKEN"
CONFIG="/etc/tod/jenkins_config.json"
WORKSPACE="/var/tod/workspace"
# Sync builds every 5 minutes
tod sync \
--config $CONFIG \
--workspace $WORKSPACE \
--jenkins-token $SERVICE_TOKEN \
--service-user $SERVICE_USER
Example: Web API Integration
When integrating Tod with a web API to create requests on behalf of developers:
# User john.doe@corp.com submits a request via web UI
# Web service calls Tod with proper user attribution
tod new \
--config jenkins_config.json \
--workspace ./workspace \
--branch main \
--root-filters build \
--test-filters unit integration \
--user john.doe \
--domain CORP \
--service-user jenkins-bot \
--jenkins-token $SERVICE_TOKEN \
--gerrit-token $SERVICE_TOKEN
Result:
- Request is created by
john.doe(receives email report) - Jenkins/Gerrit API calls use
jenkins-botcredentials - Email sent to
john.doe@corp.com
Commands
sync
Synchronize build history from Jenkins.
Sync builds
tod sync --config jenkins_config.json --workspace ./workspace --jenkins-token TOKEN
Sync job list (run this first or when jobs change)
tod sync --config jenkins_config.json --workspace ./workspace --jenkins-token TOKEN --jobs
Options:
-c, --config(required): Path to Jenkins config file-w, --workspace(required): Path to workspace directory-j, --jenkins-token(required): Jenkins API token--service-user: Service account username for API access (defaults to current user)-s, --jobs: Sync job definitions instead of builds
Service Mode:
When running Tod as a service (e.g., in a scheduled task or daemon), use the --service-user option to specify the service account credentials:
tod sync --config jenkins_config.json --workspace ./workspace --jenkins-token SERVICE_TOKEN --service-user jenkins-bot
This separates the API authentication user from the system user running the command.
new
Create a new on-demand test request.
tod new --config jenkins_config.json --workspace ./workspace --branch main --root-filters build --test-filters unit integration --jenkins-token TOKEN --gerrit-token TOKEN
Options:
-c, --config(required): Path to Jenkins config file-w, --workspace(required): Path to workspace directory-b, --branch: Baseline branch (auto-detected if not specified)-r, --root-filters(required): Root filter names to run-t, --test-filters(required): Test filter names to run-j, --jenkins-token(required): Jenkins API token-g, --gerrit-token(required): Gerrit API token (if Gerrit integration is enabled)-u, --user: User name for request ownership (defaults to current user)--domain: User domain for request ownership (defaults to current domain)--service-user: Service account username for API access (defaults to current user)
Service Mode:
When running Tod as a service, use both --user and --service-user to distinguish between the request owner and the API authentication:
tod new \
--config jenkins_config.json \
--workspace ./workspace \
--branch main \
--root-filters build \
--test-filters unit integration \
--user john.doe \
--domain CORP \
--service-user jenkins-bot \
--jenkins-token SERVICE_TOKEN \
--gerrit-token SERVICE_TOKEN
--user john.doe: The request is owned by john.doe (who receives the email report)--service-user jenkins-bot: API calls use jenkins-bot credentials--domain CORP: Used to resolve john.doe's email address
How it works:
- Detects your current Git commit
- Finds the matching baseline build on the specified branch
- Triggers on-demand builds with your changes
- Tracks build progress and collects results
jobs
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: Baseline 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
report
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
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
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 user@example.com:
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
filters
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)
Workspace Structure
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
Examples
Example 1: Run Tests for Current Commit
First time: sync jobs
tod sync -c jenkins.json -w ./workspace -u $JENKINS_TOKEN --jobs
Sync latest builds
tod sync -c jenkins.json -w ./workspace -u $JENKINS_TOKEN
Create test request
tod new -c jenkins.json -w ./workspace -r build -t unit integration -u $JENKINS_TOKEN
Example 2: Target Specific Branch
tod new -c jenkins.json -w ./workspace -b develop -r build -t unit -u $JENKINS_TOKEN
Example 3: Check What Would Run
tod jobs -c jenkins.json -w ./workspace -r build -t unit integration
Example 4: Validate Filter Configuration
tod filters -c jenkins.json -w ./workspace
Example 5: Send Report for a Request
tod report -c jenkins.json -w ./workspace -i 12345678-1234-1234-1234-123456789abc
Example 6: Abort a Request
tod abort -c jenkins.json -w ./workspace -i 12345678-1234-1234-1234-123456789abc
Example 7: List Your Active Requests
# List only active requests
tod list -c jenkins.json -w ./workspace
# List all requests (including completed)
tod list -c jenkins.json -w ./workspace --all
Authentication
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%
Development
Prerequisites
- .NET 10 SDK or later
- Git
- Visual Studio 2025 or later (recommended)
Build
dotnet restore dotnet build
Run Tests
dotnet test
Run with Coverage
dotnet test --collect:"XPlat Code Coverage"
Contributing
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
License
This project is licensed under the MIT License - see the LICENSE file for details.
TODO
Core
- Timeout support for FileLock
Jenkins
- 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
Workspace
- Serialization UT: ensure that json converters are needed
- Auto save baseline branches when adding new ones
Requests
- 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
git
- Sha1 validation in ctor
- Check local commit has been pushed (or push it automatically?)
- Hardcoded git commit count in history
Agent
- Agent mode to automatically synchronize workspace and trigger requests periodically
Tod Tests
- Remove NextBuildNumber limit and improve UTs that fail with the same build number
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
This package has no dependencies.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.9.1 | 97 | 3/14/2026 |
| 0.9.0 | 87 | 3/4/2026 |
| 0.8.3 | 89 | 2/28/2026 |
| 0.8.2 | 87 | 2/24/2026 |
| 0.8.1 | 89 | 2/23/2026 |
| 0.8.0 | 86 | 2/23/2026 |
| 0.7.0 | 95 | 2/2/2026 |
| 0.6.1 | 104 | 1/31/2026 |
| 0.6.0 | 102 | 1/31/2026 |
| 0.5.2 | 98 | 1/28/2026 |
| 0.5.1 | 102 | 1/28/2026 |
| 0.5.0 | 97 | 1/27/2026 |
| 0.4.1 | 103 | 1/25/2026 |
| 0.4.0 | 102 | 1/25/2026 |
| 0.3.2 | 107 | 1/24/2026 |
| 0.3.1 | 98 | 1/24/2026 |
| 0.3.0 | 101 | 1/24/2026 |
| 0.2.1 | 102 | 1/20/2026 |
| 0.2.0 | 104 | 1/20/2026 |
| 0.1.6 | 105 | 1/20/2026 |