When a test starts failing on the production floor, the first question is always: "What changed?" If your test scripts aren't version controlled, you can't answer that. Git gives you a complete history of every change to every test. TofuPilot links each test run to the exact code version that produced it.
This guide covers Git workflow for test projects, embedding commit hashes in test metadata, and a practical .gitignore for hardware test repos.
Why Version Control Matters for Hardware Tests
Hardware test scripts aren't throwaway code. They're part of your quality system. Regulators, auditors, and your future self need to know:
- Which version of the test script produced a given result
- Who changed a measurement limit, and when
- Whether the test that ran in DVT is the same one running in PVT
Without Git, you get folders named test_v2_final_FINAL_john.py. With Git, you get a clean audit trail.
Git Workflow for Test Scripts
A simple branching strategy works well for test projects. You don't need GitFlow. You need something your team will actually use.
main ──●──●──●──●──●──●── (production-ready tests)
\ /
feature/add- ──●──●──●──● (new test or change)
thermal-testmain is always deployable to production stations. Every commit on main has been reviewed and tested.
Feature branches are where you develop new tests or modify existing ones. Name them descriptively: feature/add-thermal-cycling, fix/dmm-timeout-handling, update/voltage-limits-rev-c.
Basic workflow:
# Start a new test change
git checkout -b feature/add-current-measurement
# Make your changes, test locally
git add tests/power_rail_fct.py
git commit -m "Add idle current measurement to power rail FCT"
# Push and create a pull request for review
git push -u origin feature/add-current-measurement
# After review, merge to main
git checkout main
git pull
git merge feature/add-current-measurement
git pushTag releases when you deploy to production stations:
# Tag a release before deploying to production
git tag -a v1.2.0 -m "Add thermal cycling test, update voltage limits for Rev C"
git push origin v1.2.0Embed Git Commit Hash in Test Metadata
Link every test run to the exact code that produced it. This is the key traceability link between your test results and your test code.
import subprocess
def get_git_info() -> dict:
"""Get current Git commit hash and tag."""
info = {}
try:
info["commit"] = subprocess.check_output(
["git", "rev-parse", "HEAD"],
stderr=subprocess.DEVNULL,
).decode().strip()
info["commit_short"] = subprocess.check_output(
["git", "rev-parse", "--short", "HEAD"],
stderr=subprocess.DEVNULL,
).decode().strip()
# Check for uncommitted changes
status = subprocess.check_output(
["git", "status", "--porcelain"],
stderr=subprocess.DEVNULL,
).decode().strip()
info["dirty"] = len(status) > 0
# Get latest tag if available
try:
info["tag"] = subprocess.check_output(
["git", "describe", "--tags", "--abbrev=0"],
stderr=subprocess.DEVNULL,
).decode().strip()
except subprocess.CalledProcessError:
info["tag"] = None
except subprocess.CalledProcessError:
info["commit"] = "unknown"
info["commit_short"] = "unknown"
info["dirty"] = True
info["tag"] = None
return infoTag Test Runs with Code Version in TofuPilot
Pass the Git info as metadata when you create a test run. Every run in TofuPilot will show which commit produced it.
import openhtf as htf
from openhtf.util import units
from tofupilot.openhtf import TofuPilot
from git_version import get_git_info
git_info = get_git_info()
# Warn if running with uncommitted changes in production
if git_info["dirty"]:
print("WARNING: Running tests with uncommitted changes")
@htf.measures(
htf.Measurement("voltage_3v3").in_range(3.2, 3.4).with_units(units.VOLT),
)
def test_power_rail(test):
test.measurements.voltage_3v3 = 3.29
def main():
test = htf.Test(test_power_rail)
# Pass git info as run metadata
with TofuPilot(
test,
script_version=git_info.get("tag") or git_info["commit_short"],
):
test.execute(test_start=lambda: "DUT-001")
if __name__ == "__main__":
main()Now when you look at a test run in TofuPilot, you can see which code version produced it. If a test starts failing after a deployment, compare the script_version of passing runs against failing ones to find the change.
.gitignore Template for Test Projects
Hardware test repos have specific files you don't want to track. Here's a practical starting point.
# Python
__pycache__/
*.py[cod]
*.so
*.egg-info/
dist/
build/
venv/
.venv/
# Environment and secrets
.env
.env.*
!.env.example
# IDE
.vscode/
.idea/
*.swp
*.swo
# Test artifacts (logs, reports, captures)
logs/
test_output/
*.log
*.csv
captures/
screenshots/
# Instrument calibration data (station-specific)
cal_data/
fixture_cal/
# OS files
.DS_Store
Thumbs.db
# PyInstaller (if you package tests)
*.spec
# Local config overrides
config/local.yamlA few notes on what's excluded:
.envfiles contain API keys. Never commit these. Include a.env.examplewith placeholder values so new team members know what to configure.logs/andtest_output/are generated per run. They belong in TofuPilot, not Git.cal_data/is station-specific calibration data. It doesn't belong in the test script repo.config/local.yamlis for per-developer overrides. The shared configs (config/dev.yaml,config/production.yaml) stay tracked.