Files
magnus919_agent-skills/release-engineering/tests/test_dora_metrics.py
T
usernameandfactory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> 2b906565d4 fix(release-engineering): address review findings in DORA asset, cadence, link, metrics script
- Correct the DORA 2024 thresholds table (Elite/High/Medium/Low) and the
  2025 caveat (seven archetypes, report rename; DRR added in 2024) in
  assets/dora-metrics-reference.md to match references/metrics-and-dora.md
- Fix Firefox cadence to the 2-week train and align GitLab's release day
  with the third-Thursday fact in references/release-process-models.md and
  references/release-operations-and-triage.md
- Fix the dead versioning-decision-table.md link in templates/release-plan.md
  to point at ../assets/versioning-decision-table.md
- Scope the deployment-frequency observation window to the selected
  environment, clamp negative change lead time at 0, and require a recovery
  deployment to start at or after the failed deployment finished in
  scripts/dora_metrics.py
- Make the metrics-and-dora.md worked example internally consistent
  (21 total deploys, DRR = 3/21 = 14.3%)
- Reword the version_bump.py docstring to describe the implemented
  pre-release behavior instead of unimplemented graduation
- Add tests for environment-scoped window, CLT clamp, and recovery
  candidate ordering (114 tests pass under pytest and unittest)

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
2026-08-01 23:07:49 -04:00

539 lines
19 KiB
Python

"""Tests for dora_metrics.py.
Covers: the five DORA metrics (deployment frequency, change lead time,
change failure rate, failed deployment recovery time, deployment rework
rate), environment scoping, unavailable-metric guards, --json output, and
exit codes.
Discoverable by both pytest and unittest (unittest.TestCase classes).
"""
import json
import os
import subprocess
import sys
import tempfile
import unittest
SCRIPTS_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "scripts")
DORA_METRICS = os.path.join(SCRIPTS_DIR, "dora_metrics.py")
HAPPY_EVENTS = {
"deployments": [
{
"started_at": "2026-01-01T00:00:00Z",
"finished_at": "2026-01-01T00:30:00Z",
"commit_sha": "aaa",
"environment": "prod",
"success": True,
"unplanned": False,
},
{
"started_at": "2026-01-02T00:00:00Z",
"finished_at": "2026-01-02T00:30:00Z",
"commit_sha": "bbb",
"environment": "prod",
"success": False,
"unplanned": True,
},
{
"started_at": "2026-01-02T01:00:00Z",
"finished_at": "2026-01-02T01:15:00Z",
"commit_sha": "ccc",
"environment": "prod",
"success": True,
"unplanned": True,
},
{
"started_at": "2026-01-03T00:00:00Z",
"finished_at": "2026-01-03T00:30:00Z",
"commit_sha": "ddd",
"environment": "prod",
"success": True,
"unplanned": False,
},
],
"commits": [
{"sha": "aaa", "created_at": "2025-12-31T12:00:00Z"},
{"sha": "bbb", "created_at": "2026-01-01T12:00:00Z"},
{"sha": "ccc", "created_at": "2026-01-02T00:45:00Z"},
{"sha": "ddd", "created_at": "2026-01-03T00:00:00Z"},
],
}
def run_dora(args):
"""Run dora_metrics.py with given args, return (returncode, stdout, stderr)."""
cmd = [sys.executable, DORA_METRICS] + args
proc = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
return proc.returncode, proc.stdout, proc.stderr
def write_events(events):
"""Write an events dict to a temp JSON file and return its path."""
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as tmp:
json.dump(events, tmp)
return tmp.name
class TestDoraMetricsHappyPath(unittest.TestCase):
"""All five metrics compute correctly on well-formed input."""
def setUp(self):
self.path = write_events(HAPPY_EVENTS)
def tearDown(self):
os.unlink(self.path)
def test_json_metrics_values(self):
"""Each metric matches the hand-computed expectation."""
rc, out, _ = run_dora(["--events", self.path, "--json"])
self.assertEqual(rc, 0)
data = json.loads(out)
# Deployment frequency: 3 successful over the observation window.
self.assertTrue(data["deployment_frequency"]["available"])
self.assertEqual(data["deployment_frequency"]["successful_deployments"], 3)
self.assertAlmostEqual(
data["deployment_frequency"]["deployments_per_day"],
3.0 / 2.020833,
places=4,
)
# Change lead time: median of finished_at - commit created_at over
# successful deploys with commit data: [12.5h, 30m, 30m] -> 30m.
self.assertTrue(data["change_lead_time"]["available"])
self.assertAlmostEqual(data["change_lead_time"]["seconds"], 1800.0, places=3)
self.assertEqual(data["change_lead_time"]["deployments_measured"], 3)
# Change failure rate: 1 failed of 4.
self.assertTrue(data["change_failure_rate"]["available"])
self.assertAlmostEqual(data["change_failure_rate"]["percent"], 25.0, places=4)
# Failed deployment recovery time: failed bbb -> next success ccc
# finished 01:15 Jan 2, bbb started 00:00 Jan 2 -> 75 minutes.
self.assertTrue(data["failed_deployment_recovery_time"]["available"])
self.assertAlmostEqual(
data["failed_deployment_recovery_time"]["seconds"], 4500.0, places=3
)
# Deployment rework rate: 2 unplanned of 4.
self.assertTrue(data["deployment_rework_rate"]["available"])
self.assertAlmostEqual(data["deployment_rework_rate"]["percent"], 50.0, places=4)
def test_counts(self):
"""Counts reflect the input deployments."""
rc, out, _ = run_dora(["--events", self.path, "--json"])
data = json.loads(out)
counts = data["deployments"]
self.assertEqual(
counts, {"total": 4, "successful": 3, "failed": 1, "unplanned": 2}
)
def test_human_output_default(self):
"""Without --json, output is a human-readable table."""
rc, out, _ = run_dora(["--events", self.path])
self.assertEqual(rc, 0)
self.assertIn("deployment frequency", out)
self.assertIn("change lead time", out)
self.assertIn("change failure rate", out)
self.assertIn("failed deployment recovery time", out)
self.assertIn("deployment rework rate", out)
class TestDoraMetricsGuards(unittest.TestCase):
"""Unavailable-metric guards."""
def test_unrecovered_failed_deploy_fdrt_unavailable(self):
"""FDRT is unavailable when a failed deploy has no later success."""
events = {
"deployments": [
{
"started_at": "2026-01-01T00:00:00Z",
"finished_at": "2026-01-01T00:10:00Z",
"commit_sha": None,
"environment": "prod",
"success": True,
"unplanned": False,
},
{
"started_at": "2026-01-02T00:00:00Z",
"finished_at": "2026-01-02T00:10:00Z",
"commit_sha": None,
"environment": "prod",
"success": False,
"unplanned": True,
},
],
"commits": [],
}
path = write_events(events)
try:
rc, out, _ = run_dora(["--events", path, "--json"])
self.assertEqual(rc, 0)
data = json.loads(out)
fdrt = data["failed_deployment_recovery_time"]
self.assertFalse(fdrt["available"])
self.assertIn("not yet recovered", fdrt["reason"])
finally:
os.unlink(path)
def test_no_failed_deployments_fdrt_unavailable(self):
"""FDRT is unavailable when there are no failed deployments."""
events = {
"deployments": [
{
"started_at": "2026-01-01T00:00:00Z",
"finished_at": "2026-01-01T00:10:00Z",
"commit_sha": None,
"environment": "prod",
"success": True,
"unplanned": False,
}
],
"commits": [],
}
path = write_events(events)
try:
rc, out, _ = run_dora(["--events", path, "--json"])
self.assertEqual(rc, 0)
data = json.loads(out)
fdrt = data["failed_deployment_recovery_time"]
self.assertFalse(fdrt["available"])
self.assertIn("no failed deployments", fdrt["reason"])
finally:
os.unlink(path)
def test_missing_commit_timestamps_clt_unavailable(self):
"""CLT is unavailable when no deployment has commit timestamp data."""
events = {
"deployments": [
{
"started_at": "2026-01-01T00:00:00Z",
"finished_at": "2026-01-01T00:10:00Z",
"commit_sha": "aaa",
"environment": "prod",
"success": True,
"unplanned": False,
}
],
"commits": [],
}
path = write_events(events)
try:
rc, out, _ = run_dora(["--events", path, "--json"])
self.assertEqual(rc, 0)
data = json.loads(out)
clt = data["change_lead_time"]
self.assertFalse(clt["available"])
self.assertEqual(clt["deployments_measured"], 0)
self.assertEqual(clt["deployments_without_commit_data"], 1)
finally:
os.unlink(path)
def test_no_deployments_all_unavailable(self):
"""An empty deployments array yields unavailable metrics, exit 0."""
path = write_events({"deployments": [], "commits": []})
try:
rc, out, _ = run_dora(["--events", path, "--json"])
self.assertEqual(rc, 0)
data = json.loads(out)
for key in (
"deployment_frequency",
"change_lead_time",
"change_failure_rate",
"failed_deployment_recovery_time",
"deployment_rework_rate",
):
self.assertFalse(data[key]["available"], key)
finally:
os.unlink(path)
def test_environment_scoping(self):
"""Deployments in other environments are excluded."""
events = {
"deployments": [
{
"started_at": "2026-01-01T00:00:00Z",
"finished_at": "2026-01-01T00:10:00Z",
"commit_sha": None,
"environment": "staging",
"success": False,
"unplanned": True,
}
],
"commits": [],
}
path = write_events(events)
try:
rc, out, _ = run_dora(["--events", path, "--json"])
self.assertEqual(rc, 0)
data = json.loads(out)
self.assertEqual(data["deployments"]["total"], 0)
self.assertFalse(data["deployment_frequency"]["available"])
finally:
os.unlink(path)
def test_custom_environment(self):
"""--environment selects a non-default environment."""
path = write_events(HAPPY_EVENTS)
try:
rc, out, _ = run_dora(
["--events", path, "--environment", "staging", "--json"]
)
self.assertEqual(rc, 0)
data = json.loads(out)
self.assertEqual(data["environment"], "staging")
self.assertEqual(data["deployments"]["total"], 0)
finally:
os.unlink(path)
class TestDoraMetricsEdgeBehavior(unittest.TestCase):
"""Edge behavior locked by review fixes: environment-scoped window,
change-lead-time clamping, and recovery-candidate ordering."""
def test_mixed_environment_window_scoped_to_environment(self):
"""DF window covers only the selected environment's deployments."""
events = {
"deployments": [
{
"started_at": "2026-01-01T00:00:00Z",
"finished_at": "2026-01-01T00:30:00Z",
"commit_sha": None,
"environment": "prod",
"success": True,
"unplanned": False,
},
{
"started_at": "2026-01-03T00:00:00Z",
"finished_at": "2026-01-03T00:30:00Z",
"commit_sha": None,
"environment": "prod",
"success": True,
"unplanned": False,
},
{
"started_at": "2026-01-01T00:00:00Z",
"finished_at": "2026-01-01T00:30:00Z",
"commit_sha": None,
"environment": "staging",
"success": True,
"unplanned": False,
},
{
"started_at": "2026-01-10T00:00:00Z",
"finished_at": "2026-01-10T00:30:00Z",
"commit_sha": None,
"environment": "staging",
"success": True,
"unplanned": False,
},
],
"commits": [],
}
path = write_events(events)
try:
rc, out, _ = run_dora(["--events", path, "--json"])
self.assertEqual(rc, 0)
data = json.loads(out)
df = data["deployment_frequency"]
self.assertTrue(df["available"])
# Prod window is Jan 1 00:00 -> Jan 3 00:30 (2 days + 30 min),
# NOT the all-environments window that extends to Jan 10.
self.assertAlmostEqual(df["window_days"], 2.020833, places=4)
self.assertAlmostEqual(
df["deployments_per_day"], 2.0 / 2.020833, places=4
)
self.assertEqual(df["successful_deployments"], 2)
finally:
os.unlink(path)
def test_negative_change_lead_time_clamped_to_zero(self):
"""A deploy finishing before its commit was created yields CLT 0."""
events = {
"deployments": [
{
"started_at": "2026-01-01T00:00:00Z",
"finished_at": "2026-01-01T00:30:00Z",
"commit_sha": "aaa",
"environment": "prod",
"success": True,
"unplanned": False,
}
],
"commits": [{"sha": "aaa", "created_at": "2026-01-02T12:00:00Z"}],
}
path = write_events(events)
try:
rc, out, _ = run_dora(["--events", path, "--json"])
self.assertEqual(rc, 0)
data = json.loads(out)
clt = data["change_lead_time"]
self.assertTrue(clt["available"])
self.assertEqual(clt["seconds"], 0.0)
finally:
os.unlink(path)
def test_recovery_must_start_after_failed_deploy_finished(self):
"""A success that began mid-failure is not counted as recovery."""
events = {
"deployments": [
{
"started_at": "2026-01-01T00:00:00Z",
"finished_at": "2026-01-01T01:00:00Z",
"commit_sha": None,
"environment": "prod",
"success": False,
"unplanned": True,
},
{
# Started mid-failure (before the failed deploy
# finished at 01:00) — must NOT count as recovery.
"started_at": "2026-01-01T00:30:00Z",
"finished_at": "2026-01-01T00:45:00Z",
"commit_sha": None,
"environment": "prod",
"success": True,
"unplanned": False,
},
{
"started_at": "2026-01-01T01:30:00Z",
"finished_at": "2026-01-01T02:00:00Z",
"commit_sha": None,
"environment": "prod",
"success": True,
"unplanned": False,
},
],
"commits": [],
}
path = write_events(events)
try:
rc, out, _ = run_dora(["--events", path, "--json"])
self.assertEqual(rc, 0)
data = json.loads(out)
fdrt = data["failed_deployment_recovery_time"]
self.assertTrue(fdrt["available"])
# Recovery = post-failure success finished (02:00) minus failed
# started (00:00) = 2 hours; the mid-failure success is excluded.
self.assertAlmostEqual(fdrt["seconds"], 7200.0, places=3)
self.assertEqual(fdrt["recovered"], 1)
self.assertEqual(fdrt["unrecovered"], 0)
finally:
os.unlink(path)
class TestDoraMetricsExitCodes(unittest.TestCase):
"""Exit codes for malformed input and usage errors."""
def test_missing_events_exit_2(self):
"""Missing --events is a usage error (exit 2)."""
rc, _, err = run_dora([])
self.assertEqual(rc, 2)
self.assertIn("usage", err.lower())
def test_malformed_json_exit_1(self):
"""Invalid JSON is an input error."""
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as tmp:
tmp.write("{not json")
path = tmp.name
try:
rc, _, err = run_dora(["--events", path])
self.assertEqual(rc, 1)
self.assertIn("invalid json", err.lower())
finally:
os.unlink(path)
def test_missing_file_exit_1(self):
"""A missing events file is an input error."""
rc, _, err = run_dora(["--events", "/nonexistent/events.json"])
self.assertEqual(rc, 1)
self.assertIn("error", err.lower())
def test_success_must_be_boolean_exit_1(self):
"""A non-boolean success field is an input error."""
events = {
"deployments": [
{
"started_at": "2026-01-01T00:00:00Z",
"finished_at": "2026-01-01T00:10:00Z",
"commit_sha": None,
"environment": "prod",
"success": "true",
"unplanned": False,
}
],
"commits": [],
}
path = write_events(events)
try:
rc, _, err = run_dora(["--events", path])
self.assertEqual(rc, 1)
self.assertIn("boolean", err.lower())
finally:
os.unlink(path)
def test_invalid_timestamp_exit_1(self):
"""An unparseable timestamp is an input error."""
events = {
"deployments": [
{
"started_at": "not-a-date",
"finished_at": "2026-01-01T00:10:00Z",
"commit_sha": None,
"environment": "prod",
"success": True,
"unplanned": False,
}
],
"commits": [],
}
path = write_events(events)
try:
rc, _, err = run_dora(["--events", path])
self.assertEqual(rc, 1)
self.assertIn("started_at", err.lower())
finally:
os.unlink(path)
def test_no_traceback_on_malformed(self):
"""Malformed input never produces a traceback."""
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as tmp:
tmp.write("{broken")
path = tmp.name
try:
rc, out, err = run_dora(["--events", path])
self.assertNotEqual(rc, 0)
self.assertNotIn("Traceback", err)
self.assertNotIn("Traceback", out)
finally:
os.unlink(path)
def test_help_exits_0(self):
"""--help exits 0 and describes usage."""
rc, out, _ = run_dora(["--help"])
self.assertEqual(rc, 0)
self.assertIn("usage", out.lower())
self.assertIn("dora_metrics", out.lower())
class TestDoraMetricsDeterminism(unittest.TestCase):
"""Output is deterministic across runs."""
def test_json_deterministic(self):
"""Two runs produce identical JSON."""
path = write_events(HAPPY_EVENTS)
try:
rc1, out1, _ = run_dora(["--events", path, "--json"])
rc2, out2, _ = run_dora(["--events", path, "--json"])
self.assertEqual(rc1, 0)
self.assertEqual(rc2, 0)
self.assertEqual(out1, out2)
finally:
os.unlink(path)
if __name__ == "__main__":
unittest.main()