#!/usr/bin/env python3
"""Scan a synthetic school fixture with the local leita binary and check it.
The tree is made-up departments, not a customer. The scan runs on a 64 MiB
tmpfs inside a user namespace so the report's volume figures are that cap,
not the machine's disk. History is written under a temporary XDG_DATA_HOME.
"""
import hashlib
import html
import json
import os
import shutil
import subprocess
import sys
import tempfile
from pathlib import Path
SEED = "616-school-1"
DISCLOSURE = "Synthetic example data. Not a school and not a customer."
LABEL_NAME = "SYNTHETIC-EXAMPLE.txt"
ROOT_NAME = "synthetic-example-school"
TMPFS_BYTES = 64 * 1024 * 1024
HISTORY_MIN = 1024 * 1024
MOUNT = Path("/tmp/leita-school-example/mnt")
# Logical sizes. The checker compares Leita with the allocated size stat reports.
BEFORE = {
"administration/payroll.bin": 2 * 1024 * 1024,
"library/catalogue.bin": 4 * 1024 * 1024,
"science/lab.bin": 1 * 1024 * 1024,
"science/sensors.bin": 1 * 1024 * 1024,
"arts/recital.bin": 1 * 1024 * 1024,
}
AFTER_REPLACE = {
"science/sensors.bin": 3 * 1024 * 1024,
}
AFTER_CREATE = {
"arts/posters.bin": 1 * 1024 * 1024,
}
DEPARTMENTS = ("administration", "library", "science", "arts")
def allocated(path):
st = path.stat()
return st.st_blocks * 512
def write_file(path, size):
path.parent.mkdir(parents=True, exist_ok=True)
path.write_bytes(b"\0" * size)
def build_before(root):
if root.exists():
shutil.rmtree(root)
root.mkdir(parents=True)
(root / LABEL_NAME).write_text(DISCLOSURE + "\n", encoding="utf-8")
for name, size in BEFORE.items():
write_file(root / name, size)
def apply_mutation(root):
for name, size in AFTER_REPLACE.items():
write_file(root / name, size)
for name, size in AFTER_CREATE.items():
write_file(root / name, size)
def file_truth(root):
"""Allocated bytes of every regular file, keyed by path relative to root."""
out = {}
for path in sorted(root.rglob("*")):
if path.is_file():
out[str(path.relative_to(root))] = allocated(path)
return out
def department_bytes(files):
totals = {name: 0 for name in DEPARTMENTS}
label = 0
for name, size in files.items():
head = name.split("/", 1)[0]
if head in totals and "/" in name:
totals[head] += size
elif name == LABEL_NAME:
label += size
else:
raise SystemExit(f"unexpected fixture path: {name}")
return totals, label
def rows_by_path(report, key):
return {row["path"]: row for row in report[key]}
def require(condition, message, errors):
if not condition:
errors.append(message)
def check_scan(report, root, files, compared, errors):
"""Check one Leita JSON report against allocated fixture bytes."""
prefix = str(root)
totals, label = department_bytes(files)
expect_bytes = sum(totals.values()) + label
require(report.get("schema") == "leita.report/1", "schema is not leita.report/1", errors)
require(report.get("generator", {}).get("licensee") is None, "a licensee was present", errors)
target = report.get("target") or {}
require(target.get("path") == prefix, f"target path {target.get('path')!r}", errors)
require(target.get("volume") == "tmpfs", f"volume {target.get('volume')!r}", errors)
require(target.get("filesystem") == "other", f"filesystem {target.get('filesystem')!r}", errors)
require(target.get("engine") == "walk", f"engine {target.get('engine')!r}", errors)
got = report.get("totals") or {}
require(got.get("bytes") == expect_bytes, f"total bytes {got.get('bytes')} != {expect_bytes}", errors)
require(got.get("files") == len(files), f"file count {got.get('files')} != {len(files)}", errors)
require(got.get("directories") == len(DEPARTMENTS), f"directory count {got.get('directories')}", errors)
space = report.get("space") or {}
require(space.get("total") == TMPFS_BYTES, f"tmpfs cap {space.get('total')}", errors)
require(space.get("free") == TMPFS_BYTES - expect_bytes, f"free {space.get('free')}", errors)
dirs = rows_by_path(report, "largest_directories")
listed = rows_by_path(report, "largest_files")
for name, size in totals.items():
row = dirs.get(f"{prefix}/{name}")
require(row is not None and row.get("bytes") == size, f"{name} bytes {None if row is None else row.get('bytes')} != {size}", errors)
for name, size in files.items():
row = listed.get(f"{prefix}/{name}")
require(row is not None and row.get("bytes") == size, f"{name} listed {None if row is None else row.get('bytes')} != {size}", errors)
if compared is None:
require(report.get("compared") is None, "no-history scan still compared", errors)
return
require(report.get("compared") is not None, "second scan did not compare", errors)
for name, size in totals.items():
row = dirs.get(f"{prefix}/{name}") or {}
before = compared["departments"][name]
require(row.get("bytes_before") == before, f"{name} bytes_before {row.get('bytes_before')} != {before}", errors)
require(row.get("change") == size - before, f"{name} change {row.get('change')}", errors)
for name, size in files.items():
row = listed.get(f"{prefix}/{name}") or {}
before = compared["files"].get(name)
if before is None or before < HISTORY_MIN or size < HISTORY_MIN:
require(row.get("bytes_before") is None and row.get("change") is None, f"{name} should have an empty change", errors)
else:
require(row.get("bytes_before") == before, f"{name} bytes_before {row.get('bytes_before')}", errors)
require(row.get("change") == size - before, f"{name} change {row.get('change')}", errors)
def sanitised(text):
home = Path.home().name
return "/home/" not in text and "/Users/" not in text and home not in text
def published_command(argv, report):
shown = ["leita", *argv[1:]]
shown[shown.index(str(report))] = report.name
return " ".join(shown)
def run_leita(binary, root, history, report, fmt, no_history):
env = os.environ.copy()
env["XDG_DATA_HOME"] = str(history)
env["LEITA_COLOR"] = "never"
argv = [str(binary), "--top", "20", "--report", str(report), "--format", fmt]
if no_history:
argv.append("--no-history")
argv.append(str(root))
proc = subprocess.run(argv, env=env, text=True, capture_output=True, check=False)
if proc.returncode != 0:
raise SystemExit(f"leita exited {proc.returncode}\n{proc.stderr}\n{proc.stdout}")
if not sanitised(proc.stdout) or not sanitised(proc.stderr) or not sanitised(report.read_text(encoding="utf-8")):
raise SystemExit("scan output contained a home path; refusing to publish it")
return published_command(argv, report), proc.stdout
def sha16(path):
return hashlib.sha256(path.read_bytes()).hexdigest()[:16]
def write_page(out, truth, hashes):
def row(name, item):
change = "unchanged" if item["change"] == 0 else f"+{item['change']:,} bytes"
return (
"
" + html.escape(name) + "
"
f"
{item['before']:,}
"
f"
{item['after']:,}
"
f"
{html.escape(change)}
"
)
body = "\n".join(row(name.title(), truth["departments"][name]) for name in DEPARTMENTS)
commands = "\n".join(html.escape(line) for line in truth["commands"])
page = f"""
Synthetic school example · Leita
{html.escape(DISCLOSURE)} The departments below are not a customer, a deployment, or a testimonial. The figures are this run's totals, not a saving and not a performance claim.
Leita {html.escape(truth["binary"]["version"])} scanned seed {html.escape(SEED)} on a {TMPFS_BYTES // (1024 * 1024)} MiB tmpfs. The report calls the volume tmpfs and the filesystem "other", and names the walk engine. The kernel was {html.escape(truth["host"]["uname"])}. This run does not measure ext4, btrfs, XFS, NTFS, APFS, a network share, Windows, macOS, or a VPS.
What changed
Four directories stand in for departments. Every counted file is NUL bytes of a fixed length. Between the scans, science/sensors.bin grows from 1 MiB to 3 MiB and arts/posters.bin appears at 1 MiB. Administration and Library are untouched. A short text file at the root carries the disclosure.
Department
Before (bytes)
After (bytes)
Change
{body}
The after scan totals {truth["after"]["bytes"]:,} bytes in {truth["after"]["files"]} files and {truth["after"]["directories"]} directories. That is the allocated size of the department files plus the label file ({truth["label_bytes"]:,} bytes). Science's directory grew by {truth["departments"]["science"]["change"]:,} bytes and Arts by {truth["departments"]["arts"]["change"]:,}. The new poster file has no earlier entry, so its change is empty. The label file is under 1 MiB, and this build leaves the change empty for an entry that small; the current scan still counts it.
The comparison scan's duration field is {html.escape(truth["after"]["duration"])}. That is a tmpfs reading of a few megabytes on this machine. It is not a disk benchmark. Leita's summary on this machine began “{html.escape(truth["scan_notice"])}”.
What this binary wrote
No licence was loaded. The landing page says a current scan's CSV and JSON are free, and that historical comparisons, before-and-after reports, and HTML and PDF reports are Pro.
This build wrote the comparison JSON and the HTML report without a licence. The JSON is the second scan in one data directory. The HTML is the second scan of the same fixture in another data directory. Both end with: {html.escape(truth["footer"])} The current-scan CSV and JSON were separate --no-history invocations, and their comparison field is empty. PDF was not published from this run. Scheduled delivery, reusable report profiles, and a total across machines were not run.