Tod 0.9.1

dotnet tool install --global Tod --version 0.9.1
                    
This package contains a .NET tool you can call from the shell/command line.
dotnet new tool-manifest
                    
if you are setting up this repo
dotnet tool install --local Tod --version 0.9.1
                    
This package contains a .NET tool you can call from the shell/command line.
#tool dotnet:?package=Tod&version=0.9.1
                    
nuke :add-package Tod --version 0.9.1
                    

Tod

Build and Test Code Coverage NuGet

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

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 names
  • BranchName: Git branch this job builds
  • IsRoot: true for root/build jobs, false for 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 identifier
  • Pattern: Regex pattern to match test job names
  • Group: Logical grouping (use ChainTestGroup value 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 queue
  • MaxRequestDuration: 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-user is not specified, defaults to the current system user (Environment.UserName)
  • If --user is not specified (in new command), defaults to the current system user
  • If --domain is 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-bot credentials
  • 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:

  1. Detects your current Git commit
  2. Finds the matching baseline build on the specified branch
  3. Triggers on-demand builds with your changes
  4. 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:

  1. Validates the request ID format
  2. Looks up the request in the workspace
  3. Verifies you are the owner of the request
  4. Marks all chains in the request as aborted
  5. 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:

  1. Log in to Jenkins
  2. Go to UserConfigureAPI Token
  3. Click Add new Token
  4. Copy the generated token
  5. Use it with the -u or --user-token option

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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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
Loading failed