#!/usr/bin/env python3
"""Offline runtime improvements: no installed /p4, services, or network use."""
import json
import hashlib
import importlib.util
import os
from pathlib import Path
import shutil
import subprocess
import sys
import tempfile
import time
import unittest
import test_maintenance_logging as maintenance_fixtures
BIN = Path(__file__).resolve().parents[1] / "Server/Unix/p4/common/bin"
BASH = os.environ.get("SDP_TEST_BASH", shutil.which("bash"))
class RuntimeImprovements(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory(prefix="sdp-improvements-")
self.addCleanup(self.tmp.cleanup)
self.root = Path(self.tmp.name).resolve()
self.logs = self.root / "logs"
self.checkpoints = self.root / "checkpoints"
self.old = self.root / "original database"
self.new = self.root / "replacement database"
self.mockbin = self.root / "mockbin"
for p in [self.logs, self.checkpoints, self.old, self.new, self.mockbin]:
p.mkdir()
(self.root / "root").symlink_to(self.old)
(self.root / "offline_db").symlink_to(self.new)
self.env = dict(os.environ, TEST_ROOT=str(self.root), LOGS=str(self.logs),
LOGFILE=str(self.logs / "checkpoint.log"),
CHECKPOINTS=str(self.checkpoints), P4CKP_PREFIX="p4_test",
P4CBIN=str(self.root), P4ROOT=str(self.root / "root"),
BACKUP_FUNCTIONS=str(BIN / "backup_functions.sh"),
PATH=str(self.mockbin) + ":" + os.environ["PATH"],
AWK="awk", KEEPCKPS="2", KEEPJNLS="1")
self.prefix = '''source "$BACKUP_FUNCTIONS"
OFFLINE_DB="$TEST_ROOT/offline_db"
log () { printf '%s\\n' "$*" >> "$LOGFILE"; }
die () { log "$*"; printf '%s\\n' "$*" >&2; exit 97; }
mail_log_file () { :; }
start_p4d () { echo started >> "$TEST_ROOT/services"; }
compare_journal_numbers () { :; }
copy_readonly_clients_dir () { return 0; }
'''
# Native macOS mv lacks GNU -T. Emulate its rename semantics only for
# fixture symlinks; all other moves are real moves inside this temp tree.
self.write_executable(self.mockbin / "mv", "#!" + sys.executable + r'''
import os, subprocess, sys
from pathlib import Path
args = sys.argv[1:]
root = Path(os.environ["TEST_ROOT"])
needle = os.environ.get("FAIL_MOVE", "")
matched = needle and needle in args[-1]
if os.environ.get("FAIL_ROLLBACK") and (root / "move-failed").exists() and args[-1] == str(root / "root"):
print("injected rollback failure", file=sys.stderr)
sys.exit(20)
if matched and (os.environ.get("FAIL_MOVE_ALWAYS") or not (root / "move-failed").exists()):
(root / "move-failed").touch()
print("injected move failure", file=sys.stderr)
sys.exit(19)
if "-Tf" in args:
os.replace(args[-2], args[-1])
else:
sys.exit(subprocess.call(["/bin/mv"] + args))
''')
@staticmethod
def write_executable(path, text):
path.write_text(text)
path.chmod(0o755)
def command(self, script, extra=None):
return [BASH, "-c", self.prefix + script], dict(self.env, **(extra or {}))
def run_bash(self, script, extra=None):
args, env = self.command(script, extra)
return subprocess.run(args, env=env, cwd=self.root, text=True,
capture_output=True, timeout=15)
def checkpoint(self, n, kind="serial", complete=True):
path = self.checkpoints / ("p4_test.ckp." + str(n))
if kind == "parallel":
path.mkdir()
(path / "db.counters.ckp.gz").write_text("contents")
elif kind == "plain":
path.write_text("contents")
else:
path = Path(str(path) + ".gz")
path.write_text("contents")
if complete:
Path(str(path) + ".md5").write_text("digest")
return path
def journal(self, n):
path = self.checkpoints / ("p4_test.jnl." + str(n) + ".gz")
path.write_text("journal")
return path
def test_checkpoint_lock_has_one_owner_under_contention(self):
args, env = self.command('''ckp_running quiet
echo acquired >> "$TEST_ROOT/owners"
while [[ ! -f "$TEST_ROOT/release" ]]; do sleep 0.01; done
ckp_complete''')
owner = subprocess.Popen(args, env=env, cwd=self.root, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
self.addCleanup(lambda: owner.poll() is None and owner.kill())
for _ in range(200):
if (self.root / "owners").exists():
break
time.sleep(0.01)
contenders = [subprocess.Popen(args, env=env, cwd=self.root,
stdout=subprocess.PIPE, stderr=subprocess.PIPE) for _ in range(12)]
for proc in contenders:
stdout, stderr = proc.communicate(timeout=10)
self.assertEqual(proc.returncode, 0, stderr)
self.assertEqual(stdout + stderr, b"")
self.assertEqual((self.root / "owners").read_text(), "acquired\n")
(self.root / "release").touch()
owner.communicate(timeout=10)
self.assertEqual(owner.returncode, 0)
self.assertFalse((self.logs / "ckp_running.txt").exists())
def test_unhandled_exit_without_die_keeps_its_lock(self):
result = self.run_bash('ckp_running; exit 4')
self.assertEqual(result.returncode, 4)
self.assertTrue((self.logs / "ckp_running.txt").exists())
def test_nonowner_cannot_clear_replaced_or_inherited_lock(self):
for script in ['ckp_complete', 'ckp_running; ( ckp_complete )',
'ckp_running; echo replacement > "$LOGS/ckp_running.txt"; ckp_complete']:
with self.subTest(script=script):
lock = self.logs / "ckp_running.txt"
if lock.exists():
lock.unlink()
if script == 'ckp_complete':
lock.write_text("legacy lock\n")
result = self.run_bash(script)
self.assertEqual(result.returncode, 97, result.stderr)
self.assertTrue(lock.exists())
def test_all_existing_checkpoint_callers_skip_before_shared_logs(self):
common = self.root / "common"
common.mkdir()
(common / "backup_functions.sh").write_text((BIN / "backup_functions.sh").read_text())
(common / "sdp_checkpoint_lock.sh").write_text((BIN / "sdp_checkpoint_lock.sh").read_text())
(common / "p4_vars").write_text('export LOGS="$TEST_ROOT/logs" P4CBIN="$TEST_ROOT/common"\n')
(self.logs / "ckp_running.txt").write_text("another owner\n")
active = self.logs / "checkpoint.log"
active.write_text("owner log\n")
scripts = ["daily_checkpoint.sh", "rotate_journal.sh", "live_checkpoint.sh",
"recreate_db_checkpoint.sh", "recreate_db_sync_replica.sh", "sync_replica.sh",
"recreate_offline_db.sh", "recover_edge.sh", "journal_watch.sh",
"convert_standby_to_master.sh", "convert_old_master_to_standby.sh", "replica_cleanup.sh", "edge_dump.sh"]
for name in scripts:
with self.subTest(script=name):
target = common / name
target.write_text((BIN / name).read_text().replace("/p4/common/bin", str(common)))
result = subprocess.run([BASH, str(target), "1", "standby-id-or-checkpoint", "/fixture/checkpoint"], env=self.env,
capture_output=True, text=True, timeout=10)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(active.read_text(), "owner log\n")
self.assertEqual((self.logs / "ckp_running.txt").read_text(), "another owner\n")
def test_retention_counts_complete_generations_across_formats(self):
old = self.checkpoint(1)
retained = [self.checkpoint(4, "parallel"), self.checkpoint(7, "plain")]
partial = self.checkpoint(99, complete=False)
journals = {n: self.journal(n) for n in range(1, 10)}
result = self.run_bash('OFFLINEJNLNUM=9; remove_old_checkpoints_and_journals')
self.assertEqual(result.returncode, 0, result.stderr)
self.assertFalse(old.exists())
self.assertTrue(all(p.exists() for p in retained + [partial]))
self.assertTrue(all(journals[n].exists() for n in range(4, 10)))
self.assertFalse(any(journals[n].exists() for n in range(1, 4)))
def test_retention_protects_offline_replay_and_minimum_count(self):
self.checkpoint(7)
journals = {n: self.journal(n) for n in range(1, 10)}
result = self.run_bash('KEEPJNLS=3; OFFLINEJNLNUM=5; remove_old_checkpoints_and_journals')
self.assertEqual(result.returncode, 0, result.stderr)
self.assertTrue(all(journals[n].exists() for n in range(5, 10)))
self.assertFalse(any(journals[n].exists() for n in range(1, 5)))
def test_retention_preserves_everything_on_chain_gap(self):
checkpoints = [self.checkpoint(n) for n in [1, 4, 7]]
journals = [self.journal(n) for n in [1, 4, 6, 7, 8]]
result = self.run_bash('OFFLINEJNLNUM=8; remove_old_checkpoints_and_journals')
self.assertEqual(result.returncode, 0, result.stderr)
self.assertTrue(all(p.exists() for p in checkpoints + journals))
self.assertIn("missing journal 5", (self.logs / "checkpoint.log").read_text())
def test_retention_without_complete_checkpoint_preserves_journals(self):
self.checkpoint(8, complete=False)
journals = [self.journal(n) for n in range(5)]
result = self.run_bash('remove_old_checkpoints_and_journals')
self.assertEqual(result.returncode, 0, result.stderr)
self.assertTrue(all(p.exists() for p in journals))
def test_keepckps_zero_preserves_no_cleanup_policy(self):
artifacts = [self.checkpoint(1), self.checkpoint(2), self.journal(0)]
result = self.run_bash('KEEPCKPS=0; KEEPJNLS=0; remove_old_checkpoints_and_journals')
self.assertEqual(result.returncode, 0, result.stderr)
self.assertTrue(all(p.exists() for p in artifacts))
def test_missing_completion_marker_preserves_existing_offline_database(self):
self.checkpoint(5, complete=False)
(self.new / "db.counters").write_text("preserve")
result = self.run_bash('''DoParallelCheckpoints=0
P4DBIN=unexpected_restore
unexpected_restore () { echo 'restore must not run without completion marker' >&2; return 9; }
recreate_offline_db_files''')
self.assertEqual(result.returncode, 97, result.stderr)
self.assertEqual((self.new / "db.counters").read_text(), "preserve")
self.assertNotIn("restore must not run", (self.logs / "checkpoint.log").read_text())
def prepare_switch(self):
(self.old / "db.counters").write_text("old counters")
(self.old / "db.rev").write_text("old revisions")
(self.new / "db.counters").write_text("new counters")
(self.new / "offline_db_usable.txt").touch()
(self.old / "server.id").write_text("original server")
(self.new / "server.id").write_text("offline server")
(self.old / "server.locks").mkdir()
(self.old / "server.locks/lock").touch()
(self.old / "save").mkdir()
(self.old / "save/db.counters").write_text("prior saved counters")
def test_database_switch_retains_old_db_until_successful_completion(self):
self.prepare_switch()
result = self.run_bash('switch_db_files')
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual((self.root / "root").resolve(), self.new)
self.assertEqual((self.root / "offline_db").resolve(), self.old)
self.assertEqual((self.old / "save/db.counters").read_text(), "old counters")
self.assertEqual((self.new / "server.id").read_text(), "original server")
self.assertFalse((self.old / "server.locks").exists())
self.assertFalse((self.new / "offline_db_usable.txt").exists())
def test_database_switch_rolls_back_failed_second_link_and_metadata(self):
self.prepare_switch()
result = self.run_bash('switch_db_files', {"FAIL_MOVE": str(self.root / "offline_db")})
self.assertEqual(result.returncode, 97, result.stderr)
self.assertEqual((self.root / "root").resolve(), self.old)
self.assertEqual((self.root / "offline_db").resolve(), self.new)
self.assertEqual((self.old / "db.counters").read_text(), "old counters")
self.assertEqual((self.new / "server.id").read_text(), "offline server")
self.assertEqual((self.old / "save/db.counters").read_text(), "prior saved counters")
self.assertEqual((self.root / "services").read_text(), "started\n")
def test_database_switch_rolls_back_partial_database_move(self):
self.prepare_switch()
result = self.run_bash('switch_db_files', {"FAIL_MOVE": "/db/"})
self.assertEqual(result.returncode, 97, result.stderr)
self.assertEqual((self.old / "db.counters").read_text(), "old counters")
self.assertEqual((self.new / "server.id").read_text(), "offline server")
self.assertEqual((self.root / "services").read_text(), "started\n")
def test_database_switch_rolls_back_first_link_failure(self):
self.prepare_switch()
result = self.run_bash('switch_db_files', {"FAIL_MOVE": str(self.root / "root")})
self.assertEqual(result.returncode, 97, result.stderr)
self.assertEqual((self.root / "root").resolve(), self.old)
self.assertEqual((self.old / "db.counters").read_text(), "old counters")
self.assertEqual((self.new / "server.id").read_text(), "offline server")
self.assertEqual((self.root / "services").read_text(), "started\n")
def test_database_switch_rolls_back_metadata_replacement(self):
self.prepare_switch()
result = self.run_bash('switch_db_files', {"FAIL_MOVE": str(self.new) + "/"})
self.assertEqual(result.returncode, 97, result.stderr)
self.assertEqual((self.old / "db.counters").read_text(), "old counters")
self.assertEqual((self.new / "server.id").read_text(), "offline server")
self.assertEqual((self.root / "services").read_text(), "started\n")
def test_database_switch_rolls_back_previous_save_backup_failure(self):
self.prepare_switch()
result = self.run_bash('switch_db_files', {"FAIL_MOVE": "previous-save"})
self.assertEqual(result.returncode, 97, result.stderr)
self.assertEqual((self.old / "db.counters").read_text(), "old counters")
self.assertEqual((self.old / "save/db.counters").read_text(), "prior saved counters")
self.assertEqual((self.root / "root").resolve(), self.old)
def test_database_switch_failed_rollback_stays_stopped_with_recovery_files(self):
self.prepare_switch()
result = self.run_bash('switch_db_files', {"FAIL_MOVE": str(self.root / "offline_db"), "FAIL_ROLLBACK": "1"})
self.assertEqual(result.returncode, 97, result.stderr)
self.assertFalse((self.root / "services").exists())
self.assertTrue((self.old / "P4ROOT_not_usable.txt").exists())
self.assertTrue(list(self.old.glob(".sdp-switch.*")))
self.assertIn("manual recovery", result.stderr)
def test_database_switch_restores_previous_save_after_late_failure(self):
self.prepare_switch()
result = self.run_bash('switch_db_files', {"FAIL_MOVE": str(self.old / "save")})
self.assertEqual(result.returncode, 97, result.stderr)
self.assertEqual((self.old / "db.counters").read_text(), "old counters")
self.assertEqual((self.old / "save/db.counters").read_text(), "prior saved counters")
self.assertEqual((self.new / "server.id").read_text(), "offline server")
self.assertEqual((self.root / "root").resolve(), self.old)
def test_parallel_verify_rejects_missing_capability_before_restore(self):
checkpoint = self.checkpoint(5, "parallel")
result = self.run_bash('''P4DBIN=old_p4d
old_p4d () { echo '-jv file'; }
verify_checkpoint "$CHECKPOINTS/p4_test.ckp.5"''')
self.assertEqual(result.returncode, 1, result.stderr)
self.assertIn("does not advertise -jvp", (self.logs / "checkpoint.log").read_text())
def test_database_switch_completion_uses_saved_physical_path(self):
self.prepare_switch()
result = self.run_bash('switch_db_files; start_p4d; complete_db_switch')
self.assertEqual(result.returncode, 0, result.stderr)
self.assertFalse((self.old / "save/db.counters").exists())
self.assertEqual((self.new / "db.counters").read_text(), "new counters")
self.assertFalse(list(self.old.glob(".sdp-switch.*")))
class EdgeRecovery(unittest.TestCase):
def setUp(self):
self.f = RuntimeImprovements(methodName="runTest")
self.f.setUp()
self.addCleanup(self.f.doCleanups)
f = self.f
f.prepare_switch()
common = f.root / "common"
common.mkdir()
for name in ["sdp_checkpoint_lock.sh", "backup_functions.sh"]:
(common / name).write_text((BIN / name).read_text())
with (common / "backup_functions.sh").open("a") as out:
out.write('''
check_vars () { :; }; set_vars () { OFFLINE_DB="$TEST_ROOT/offline_db"; }
check_uid () { :; }; check_dirs () { :; }; log_init () { :; }
rotate_log_file () { :; }; sdp_log_init () { :; }; mail_log_file () { :; }
copy_readonly_clients_dir () { return 0; }
stop_p4d () { echo stopped >> "$TEST_ROOT/services"; }
start_p4d () { echo started >> "$TEST_ROOT/services"; }
DoParallelCheckpoints=1
NumCheckPointThreads=2
''')
(common / "p4_vars").write_text('''export P4CBIN="$TEST_ROOT/common"
export P4DBIN="$TEST_ROOT/p4d_mock" P4JOURNAL="$TEST_ROOT/logs/journal"
export P4SERVER=p4_test P4HOME="$TEST_ROOT"
''')
self.script = common / "recover_edge.sh"
self.script.write_text((BIN / "recover_edge.sh").read_text().replace("/p4/common/bin", str(common)))
f.write_executable(f.root / "p4d_mock", "#!" + sys.executable + r'''
import json, os, sys
from pathlib import Path
args = sys.argv[1:]
root = Path(os.environ["TEST_ROOT"])
if args == ["-h"]:
print("-jvp verify parallel checkpoint")
sys.exit(0)
with (root / "p4d_calls.jsonl").open("a") as out:
out.write(json.dumps(args) + "\n")
source = args[-1]
mode = ""
if "-jr" in args or "-jrp" in args:
mode = "master_restore" if "master.ckp" in source else "edge_replay"
elif "-jd" in args or "-jdpm" in args:
mode = "edge_dump" if ".sdp-edge-dump." in source else "checkpoint_dump"
elif "-jv" in args or "-jvp" in args:
mode = "verify"
if mode == os.environ.get("FAIL_P4D_MODE"):
print("injected p4d " + mode + " failure", file=sys.stderr)
sys.exit(7)
if "-jr" in args or "-jrp" in args:
target = Path(args[args.index("-r") + 1])
(target / "db.counters").write_text("restored counters")
(target / "db.rev").write_text("restored revisions")
elif "-jd" in args or "-jdpm" in args:
if source == "db.counters":
print("@pv@ 0 @db.counters@ @journal@ @6@")
elif source != "-":
artifact = Path(source)
if "-jdpm" in args:
artifact.mkdir()
(artifact / "db.counters.ckp.gz").write_text("dump")
else:
artifact.write_text("dump")
Path(str(artifact) + ".md5").write_text("digest")
''')
def master_checkpoint(self, parallel):
source = self.f.root / ("master.ckp" if parallel else "master.ckp.gz")
if parallel:
source.mkdir()
(source / "db.counters.ckp.gz").write_text("checkpoint")
else:
source.write_text("checkpoint")
Path(str(source) + ".md5").write_text("digest")
return source
def run_edge(self, source, extra=None):
return subprocess.run([BASH, str(self.script), "1", str(source)],
env=dict(self.f.env, **(extra or {})), text=True,
capture_output=True, timeout=15)
def test_serial_master_checkpoint_is_staged_and_verified_before_restart(self):
result = self.run_edge(self.master_checkpoint(False))
self.assertEqual(result.returncode, 0, result.stderr + (self.f.logs / "recover_edge.log").read_text())
calls = [json.loads(x) for x in (self.f.root / "p4d_calls.jsonl").read_text().splitlines()]
master = [args for args in calls if "master.ckp.gz" in args[-1]]
self.assertIn("-jv", master[0])
self.assertTrue(all("-jr" in args for args in master[1:]))
self.assertEqual((self.f.root / "services").read_text(), "stopped\nstarted\n")
self.assertEqual((self.f.root / "root/db.counters").read_text(), "restored counters")
self.assertTrue((self.f.root / "offline_db/offline_db_usable.txt").exists())
self.assertFalse((self.f.logs / "ckp_running.txt").exists())
def test_edge_recovery_with_unwritable_metadata_parent_survives_next_switch(self):
physical = self.f.root / "root-owned-metadata-parent"
physical.mkdir()
moved = physical / "offline"
shutil.move(self.f.new, moved)
self.f.new = moved
(self.f.root / "offline_db").unlink()
(self.f.root / "offline_db").symlink_to(moved)
source = self.master_checkpoint(True)
physical.chmod(0o555)
self.addCleanup(physical.chmod, 0o755)
result = self.run_edge(source)
self.assertEqual(result.returncode, 0, result.stderr)
candidate = (self.f.root / "root").resolve()
self.assertTrue(candidate.is_relative_to(moved))
self.assertEqual((moved / "db.counters").read_text(), "new counters")
self.assertEqual((candidate / "db.counters").read_text(), "restored counters")
# A subsequent ordinary two-root switch must preserve its new active DB.
result = self.f.run_bash("switch_db_files; complete_db_switch")
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual((self.f.root / "root/db.counters").read_text(), "restored counters")
result = self.run_edge(source)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual((self.f.root / "root/db.counters").read_text(), "restored counters")
def test_parallel_master_checkpoint_uses_parallel_verify_and_restore(self):
result = self.run_edge(self.master_checkpoint(True))
self.assertEqual(result.returncode, 0, result.stderr)
calls = [json.loads(x) for x in (self.f.root / "p4d_calls.jsonl").read_text().splitlines()]
master = [args for args in calls if args[-1].endswith("master.ckp")]
self.assertIn("-jvp", master[0])
self.assertTrue(all("-jrp" in args for args in master[1:]))
def test_failed_edge_switch_restores_both_original_database_links(self):
result = self.run_edge(self.master_checkpoint(True), {"FAIL_MOVE": str(self.f.root / "offline_db")})
self.assertEqual(result.returncode, 1, result.stderr)
self.assertEqual((self.f.root / "root").resolve(), self.f.old)
self.assertEqual((self.f.root / "offline_db").resolve(), self.f.new)
self.assertEqual((self.f.old / "db.counters").read_text(), "old counters")
self.assertEqual((self.f.new / "db.counters").read_text(), "new counters")
self.assertEqual((self.f.root / "services").read_text(), "stopped\nstarted\n")
def test_failed_edge_replay_preserves_live_database_and_releases_owned_lock(self):
result = self.run_edge(self.master_checkpoint(True), {"FAIL_P4D_MODE": "edge_replay"})
self.assertEqual(result.returncode, 1, result.stderr)
self.assertEqual((self.f.old / "db.counters").read_text(), "old counters")
self.assertEqual((self.f.new / "db.counters").read_text(), "new counters")
self.assertEqual((self.f.root / "root").resolve(), self.f.old)
self.assertEqual((self.f.root / "services").read_text(), "stopped\n")
self.assertFalse((self.f.logs / "ckp_running.txt").exists())
class MissingTransfers(unittest.TestCase):
def setUp(self):
self.f = maintenance_fixtures.MaintenanceLogging(methodName="runTest")
self.f.setUp()
self.addCleanup(self.f.doCleanups)
self.f.prepare_rsync_transport_probe()
def calls(self):
probe = self.f.root / "rsync-probe.jsonl"
return [json.loads(x) for x in probe.read_text().splitlines()] if probe.exists() else []
def test_detailed_log_transfers_literal_paths_once_without_generated_shell(self):
filename = 'path with spaces;$(touch INJECTED)`touch BACKTICK`\'".txt'
(self.f.logs / "verify_output.log").write_text(
f"info: //depot/{filename}#1 - MISSING!\ninfo: //depot/{filename}#2 - MISSING!\n")
(self.f.logs / "p4verify.log").write_text("summary only\n")
result = self.f.run_script("rsync_missing.sh", "1")
self.assertEqual(result.returncode, 0, result.stderr)
calls = self.calls()
self.assertEqual(len(calls), 1)
self.assertEqual(calls[0]["args"], ["-avs", "--", "test@master:" + str(self.f.home / "depots/depot" / filename) + ",d/",
str(self.f.home / "depots/depot" / filename) + ",d/"])
self.assertFalse((self.f.root / "INJECTED").exists())
self.assertFalse((self.f.root / "BACKTICK").exists())
self.assertFalse((self.f.home / "tmp/temp_rsync.sh").exists())
def test_bad_record_prevents_all_transfers(self):
for record in ["info: //depot/../escape#1 - MISSING!", "unexpected MISSING! message"]:
with self.subTest(record=record):
(self.f.logs / "verify_output.log").write_text("info: //depot/good#1 - MISSING!\n" + record + "\n")
result = self.f.run_script("rsync_missing.sh", "1")
self.assertEqual(result.returncode, 1, result.stderr)
self.assertEqual(self.calls(), [])
def test_missing_detailed_log_requires_explicit_legacy_input(self):
(self.f.logs / "verify_output.log").unlink(missing_ok=True)
result = self.f.run_script("rsync_missing.sh", "1")
self.assertEqual(result.returncode, 1)
self.assertEqual(self.calls(), [])
(self.f.logs / "p4verify.log").write_text("info: //depot/file#1 - MISSING!\n")
result = self.f.run_script("rsync_missing.sh", "1", str(self.f.logs / "p4verify.log"))
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(len(self.calls()), 1)
class CoverageReporting(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory(prefix="sdp-coverage-")
self.addCleanup(self.tmp.cleanup)
self.root = Path(self.tmp.name)
self.source = self.root / "source"
self.results = self.root / "results"
self.source.mkdir(); self.results.mkdir()
spec = importlib.util.spec_from_file_location("sdp_coverage", Path(__file__).parent / "containers/coverage.py")
self.coverage = importlib.util.module_from_spec(spec)
spec.loader.exec_module(self.coverage)
self.script = self.source / "Server/Unix/p4/common/bin/rsync_missing.sh"
self.script.parent.mkdir(parents=True)
self.script.write_text("#!/bin/bash\nexit 0\n")
self.manifest = [{"path": self.script.relative_to(self.source).as_posix(),
"sha256": hashlib.sha256(self.script.read_bytes()).hexdigest(), "symlink": None}]
(self.source / ".export-manifest.json").write_text(json.dumps(self.manifest))
def test_uninventoried_stale_script_is_excluded_and_syntax_is_not_behavior(self):
(self.script.parent / "deleted_in_depot.sh").write_text("#!/bin/bash\nexit 0\n")
result = self.coverage.report(self.source, self.results)
self.assertEqual(len(result["scripts"]), 1)
self.assertEqual(result["scripts"][0]["level"], "static_only")
self.assertFalse(result["source_matches_results"])
def test_live_evidence_requires_matching_source_manifest(self):
(self.results / "runtime.log").write_text(json.dumps({"scenarios": [
{"name": "ssh_host_trust", "scripts": ["rsync_missing.sh"], "status": "passed", "detail": "fixture handshake"}]}))
result = self.coverage.report(self.source, self.results)
self.assertEqual(result["scripts"][0]["level"], "static_only")
(self.results / "source-manifest.json").write_text(json.dumps(self.manifest))
result = self.coverage.report(self.source, self.results)
self.assertEqual(result["scripts"][0]["level"], "live")
self.script.write_text("#!/bin/bash\nexit 1\n")
result = self.coverage.report(self.source, self.results)
self.assertEqual(result["scripts"][0]["level"], "static_only")
def test_unittest_parser_keeps_failures_and_skips_distinct(self):
result = self.coverage.unittest_results("test_a (test_mod.Case.test_a) ... ok\n"
"test_b (test_mod.Case) ... FAIL\n"
"test_c (test_mod.Case) ... skipped 'no service'\n"
"test_d (test_mod.Case) ... diagnostic output\nmore details\nok\n"
" test_e (test_mod.Case.test_e) (mode='failed') ... FAIL\n")
self.assertEqual([row["status"] for row in result], ["passed", "failed", "skipped", "passed", "failed"])
self.assertEqual(result[0]["test"], "test_mod.Case.test_a")
def test_passing_case_never_hides_failed_harness_case(self):
self.coverage.HARNESS["rsync_missing.sh"] = ["rsync_missing.sh"]
(self.results / "source-manifest.json").write_text(json.dumps(self.manifest))
(self.results / "integration.log").write_text(
"[PASS] rsync_missing.sh\n\x1b[31m[FAIL]\x1b[0m rsync_missing.sh -- exit=1\n")
row = self.coverage.report(self.source, self.results)["scripts"][0]
self.assertEqual(row["level"], "live")
self.assertEqual(row["outcome"], "failed")
self.assertEqual([case["status"] for case in row["live"]], ["passed", "failed"])
def test_skips_make_passing_scope_partial(self):
passed = {"status": "passed"}
self.assertEqual(self.coverage.aggregate_outcome(passed, [passed, {"status": "skipped"}]), "partial")
self.assertEqual(self.coverage.aggregate_outcome(passed, []), "no-behavioral-evidence")
def test_python_installer_uses_real_outcome_and_log(self):
self.script.rename(self.script.with_name("install_sdp_python.sh"))
self.manifest[0]["path"] = str(Path(self.manifest[0]["path"]).with_name("install_sdp_python.sh"))
(self.source / ".export-manifest.json").write_text(json.dumps(self.manifest))
(self.results / "source-manifest.json").write_text(json.dumps(self.manifest))
(self.results / "test-outcomes.json").write_text(json.dumps({"python-installer": 0}))
row = self.coverage.report(self.source, self.results)["scripts"][0]
self.assertEqual(row["outcome"], "failed", "Missing log must not claim successful execution")
(self.results / "python-installer.log").write_text("Built and installed\n")
row = self.coverage.report(self.source, self.results)["scripts"][0]
self.assertEqual(row["outcome"], "passed-for-listed-scenarios")
self.assertEqual(row["level"], "live")
def test_live_runner_refuses_host_without_container_marker(self):
runner = Path(__file__).parent / "containers/runtime_integration.py"
result = subprocess.run([sys.executable, str(runner), "--root", str(self.root),
"--instance", "1", "--port", "19999", "--os-user", "perforce"],
capture_output=True, text=True, timeout=10)
self.assertEqual(result.returncode, 2)
self.assertIn("guard_error", json.loads(result.stdout))
if __name__ == "__main__":
unittest.main()
| # | Change | User | Description | Committed | |
|---|---|---|---|---|---|
| #2 | 34016 | Russell C. Jackson (Rusty) |
Restore owned checkpoint-marker cleanup on handled failures and use MD5 completion markers for recovery. Release only the acquiring shell’s ckp_running.txt marker in die(), including Bash 3.2 subshell ownership checks. Remove the separate full checkpoint verification pass from load_checkpoint and recreate_db_checkpoint, retain readable sidecar/source checks, and fix gzip sidecar path selection. Update regression tests and operational documentation. Validation: combined acceptance gate passed; 44 platform stages, 30 topology scenarios, and 112 historical release scenarios across 14 RHEL 8/9 cells passed. All 8 Linux offline suites passed 526 tests each. Focused recovery tests passed on Bash 3.2 and 5.3. |
||
| #1 | 34004 | Russell C. Jackson (Rusty) |
Improve SDP backup/recovery, Maintenance, logging and cloud operation. Add owned checkpoint locks, recovery-aware retention, preserved database switches, bounded cleanup and coordinated AWS/Azure snapshots. Fix service-user password initialization, edge staging, standby promotion state parsing, proxy cache setup, typed depot verification and historical-server configuration/error handling. Remove unused OTP programs; keep Windows removed, server.locks cleanup and the journal-count condition unchanged. Add no global shell strict modes. Update operational documentation and add reproducible mkdirs.sh/mkdirs.cfg container assurance. Validate Ubuntu 26.04 and UBI 8.10/9.8/10.2 across both Python runtimes: 44 stages; 428 offline tests per runtime/OS, zero offline skips; 19 deployed-archive comparisons per combination. Three six-node topology runs pass all 30 recovery/failure scenarios. P4D 2023.1 through 2026.1 on UBI 8/9 pass all 14 cells and 98 scenarios. Static, documentation and provenance checks pass. Containers do not certify every fleet kernel, patch build or integration. Tested source manifest SHA-256: e1a23e17b8ced8c1148d9a250589977c6f6e98606bf2a81dc3ff5d7c909e3695 Deployed comparison archive SHA-256: 1bd23217356ee3808e040d39aea5be9519bda7c8f2b4924656a70aff8ad7293a |