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 changegit checkout -b feature/add-current-measurement# Make your changes, test locallygit add tests/power_rail_fct.pygit commit -m "Add idle current measurement to power rail FCT"# Push and create a pull request for reviewgit push -u origin feature/add-current-measurement# After review, merge to maingit checkout maingit pullgit merge feature/add-current-measurementgit pushTag releases when you deploy to production stations:
# Tag a release before deploying to productiongit 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 subprocessdef 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 htffrom openhtf.util import unitsfrom tofupilot.openhtf import TofuPilotfrom git_version import get_git_infogit_info = get_git_info()# Warn if running with uncommitted changes in productionif 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.29def 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*.csvcaptures/screenshots/# Instrument calibration data (station-specific)cal_data/fixture_cal/# OS files.DS_StoreThumbs.db# PyInstaller (if you package tests)*.spec# Local config overridesconfig/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.