191 lines
6.3 KiB
Python
191 lines
6.3 KiB
Python
#!/usr/bin/env python3
|
|
"""Tests for scripts/reset_pipeline_state.py — 10 tests."""
|
|
|
|
import json
|
|
import os
|
|
import sys
|
|
import tempfile
|
|
from datetime import datetime, timezone, timedelta
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
from reset_pipeline_state import reset_pipeline_state, classify_stale, parse_timestamp
|
|
|
|
|
|
def test_no_state_file():
|
|
"""Reset on missing file returns empty."""
|
|
state, removed = reset_pipeline_state("/nonexistent/pipeline_state.json")
|
|
assert state == {}
|
|
assert removed == []
|
|
print("PASS: test_no_state_file")
|
|
|
|
|
|
def test_empty_state():
|
|
"""Empty JSON object is untouched."""
|
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
|
|
json.dump({}, f)
|
|
path = f.name
|
|
try:
|
|
state, removed = reset_pipeline_state(path)
|
|
assert state == {}
|
|
assert removed == []
|
|
finally:
|
|
os.unlink(path)
|
|
print("PASS: test_empty_state")
|
|
|
|
|
|
def test_fresh_complete_kept():
|
|
"""Recent complete entry is kept."""
|
|
now = datetime.now(timezone.utc)
|
|
entry = {"state": "complete", "updated": now.isoformat()}
|
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
|
|
json.dump({"my-pipeline": entry}, f)
|
|
path = f.name
|
|
try:
|
|
state, removed = reset_pipeline_state(path)
|
|
assert "my-pipeline" in state
|
|
assert removed == []
|
|
finally:
|
|
os.unlink(path)
|
|
print("PASS: test_fresh_complete_kept")
|
|
|
|
|
|
def test_old_complete_removed():
|
|
"""Complete entry older than 24h is removed."""
|
|
old = (datetime.now(timezone.utc) - timedelta(hours=30)).isoformat()
|
|
entry = {"state": "complete", "updated": old}
|
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
|
|
json.dump({"old-pipeline": entry}, f)
|
|
path = f.name
|
|
try:
|
|
state, removed = reset_pipeline_state(path)
|
|
assert "old-pipeline" not in state
|
|
assert len(removed) == 1
|
|
assert "old-pipeline" in removed[0]
|
|
finally:
|
|
os.unlink(path)
|
|
print("PASS: test_old_complete_removed")
|
|
|
|
|
|
def test_stuck_running_removed():
|
|
"""Running entry older than 6h is treated as stuck and removed."""
|
|
old = (datetime.now(timezone.utc) - timedelta(hours=10)).isoformat()
|
|
entry = {"state": "running", "updated": old}
|
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
|
|
json.dump({"stuck-pipeline": entry}, f)
|
|
path = f.name
|
|
try:
|
|
state, removed = reset_pipeline_state(path)
|
|
assert "stuck-pipeline" not in state
|
|
assert len(removed) == 1
|
|
finally:
|
|
os.unlink(path)
|
|
print("PASS: test_stuck_running_removed")
|
|
|
|
|
|
def test_old_failed_removed():
|
|
"""Failed entry older than 24h is removed."""
|
|
old = (datetime.now(timezone.utc) - timedelta(hours=48)).isoformat()
|
|
entry = {"state": "failed", "updated": old}
|
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
|
|
json.dump({"failed-pipeline": entry}, f)
|
|
path = f.name
|
|
try:
|
|
state, removed = reset_pipeline_state(path)
|
|
assert "failed-pipeline" not in state
|
|
finally:
|
|
os.unlink(path)
|
|
print("PASS: test_old_failed_removed")
|
|
|
|
|
|
def test_running_kept_if_fresh():
|
|
"""Fresh running entry is kept."""
|
|
now = datetime.now(timezone.utc)
|
|
entry = {"state": "running", "updated": now.isoformat()}
|
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
|
|
json.dump({"active-pipeline": entry}, f)
|
|
path = f.name
|
|
try:
|
|
state, removed = reset_pipeline_state(path)
|
|
assert "active-pipeline" in state
|
|
assert removed == []
|
|
finally:
|
|
os.unlink(path)
|
|
print("PASS: test_running_kept_if_fresh")
|
|
|
|
|
|
def test_dry_run_does_not_modify():
|
|
"""Dry run reports removals but doesn't change the file."""
|
|
old = (datetime.now(timezone.utc) - timedelta(hours=30)).isoformat()
|
|
content = json.dumps({"old-pipeline": {"state": "complete", "updated": old}})
|
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
|
|
f.write(content)
|
|
path = f.name
|
|
try:
|
|
state, removed = reset_pipeline_state(path, dry_run=True)
|
|
assert "old-pipeline" not in state
|
|
assert len(removed) == 1
|
|
# File should be unchanged
|
|
with open(path) as f:
|
|
file_state = json.load(f)
|
|
assert "old-pipeline" in file_state
|
|
finally:
|
|
os.unlink(path)
|
|
print("PASS: test_dry_run_does_not_modify")
|
|
|
|
|
|
def test_mixed_entries():
|
|
"""Mix of fresh and stale entries — only stale removed."""
|
|
now = datetime.now(timezone.utc)
|
|
old = (now - timedelta(hours=30)).isoformat()
|
|
state_data = {
|
|
"fresh-complete": {"state": "complete", "updated": now.isoformat()},
|
|
"stale-complete": {"state": "complete", "updated": old},
|
|
"fresh-running": {"state": "running", "updated": now.isoformat()},
|
|
"stuck-running": {"state": "running", "updated": (now - timedelta(hours=10)).isoformat()},
|
|
}
|
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
|
|
json.dump(state_data, f)
|
|
path = f.name
|
|
try:
|
|
state, removed = reset_pipeline_state(path)
|
|
assert "fresh-complete" in state
|
|
assert "fresh-running" in state
|
|
assert "stale-complete" not in state
|
|
assert "stuck-running" not in state
|
|
assert len(removed) == 2
|
|
finally:
|
|
os.unlink(path)
|
|
print("PASS: test_mixed_entries")
|
|
|
|
|
|
def test_corrupted_entry_removed():
|
|
"""Non-dict entries are removed."""
|
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
|
|
json.dump({"broken": "not_a_dict", "also-bad": 42}, f)
|
|
path = f.name
|
|
try:
|
|
state, removed = reset_pipeline_state(path)
|
|
assert "broken" not in state
|
|
assert "also-bad" not in state
|
|
finally:
|
|
os.unlink(path)
|
|
print("PASS: test_corrupted_entry_removed")
|
|
|
|
|
|
def run_all():
|
|
test_no_state_file()
|
|
test_empty_state()
|
|
test_fresh_complete_kept()
|
|
test_old_complete_removed()
|
|
test_stuck_running_removed()
|
|
test_old_failed_removed()
|
|
test_running_kept_if_fresh()
|
|
test_dry_run_does_not_modify()
|
|
test_mixed_entries()
|
|
test_corrupted_entry_removed()
|
|
print("\nAll 10 tests passed!")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
run_all()
|