Files
ARSV/scripts/rpmsetinit/compare_random_sets.py
2026-07-21 02:10:54 +03:00

132 lines
3.9 KiB
Python
Executable File

#!/usr/bin/env python3
"""Continuously compare set strings produced by set.c and newset.c."""
from __future__ import annotations
import random
import shlex
import subprocess
import tempfile
import time
from datetime import datetime
from pathlib import Path
MAX_N = 1000 # max words in file
MAX_M = 100 # max len for word
MIN_BPP = 10
MAX_BPP = 32
ALPHABET = ".0123456789@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz"
CC = "cc"
CFLAGS = ("-O2", "-std=gnu11", "-D_GNU_SOURCE")
SCRIPT_DIR = Path(__file__).resolve().parent
REPO_ROOT = SCRIPT_DIR.parent.parent
OLD_SET_SOURCE = REPO_ROOT / "set.c"
NEW_SET_SOURCE = REPO_ROOT / "reimplement" / "newset.c"
MKSET_SOURCE = SCRIPT_DIR / "mkset.c"
COMPAT_HEADER = SCRIPT_DIR / "newset_compat.h"
ERROR_DIR = SCRIPT_DIR / "error"
SET_HEADER = """\
#ifndef ARSV_SET_H
#define ARSV_SET_H
struct set;
struct set *set_new(void);
void set_add(struct set *set, const char *symbol);
const char *set_fini(struct set *set, int bpp);
#endif
"""
def build_mkset(source: Path, output: Path, build_dir: Path) -> None:
command = [
CC,
*CFLAGS,
f"-I{build_dir}",
"-include",
str(COMPAT_HEADER),
str(source),
str(MKSET_SOURCE),
"-o",
str(output),
]
completed = subprocess.run(command, text=True, capture_output=True)
if completed.returncode != 0:
raise RuntimeError(
f"Compilation failed: {shlex.join(command)}\n{completed.stderr}"
)
def run_mkset(binary: Path, input_path: Path, bpp: int) -> str:
with input_path.open("r", encoding="ascii") as input_file:
completed = subprocess.run(
[str(binary), str(bpp)],
stdin=input_file,
text=True,
capture_output=True,
)
if completed.returncode != 0:
raise RuntimeError(
f"{binary.name} failed with code {completed.returncode}:\n"
f"{completed.stderr}"
)
return completed.stdout.strip()
def generate_input(path: Path, n: int) -> None:
with path.open("w", encoding="ascii") as output:
for _ in range(n):
m = random.randint(1, MAX_M)
output.write("".join(random.choices(ALPHABET, k=m)))
output.write("\n")
def main() -> None:
if MAX_N < 1 or MAX_M < 1:
raise ValueError("MAX_N and MAX_M must be at least 1")
if not 10 <= MIN_BPP <= MAX_BPP <= 32:
raise ValueError("MIN_BPP and MAX_BPP must be in the range 10..32")
ERROR_DIR.mkdir(parents=True, exist_ok=True)
with tempfile.TemporaryDirectory(prefix="arsv-rpmsetinit-") as temporary:
build_dir = Path(temporary)
(build_dir / "rpmlib.h").touch()
(build_dir / "system.h").touch()
(build_dir / "set.h").write_text(SET_HEADER, encoding="ascii")
mkset = build_dir / "mkset"
mkset_new = build_dir / "mkset_new"
input_path = build_dir / "input.txt"
build_mkset(OLD_SET_SOURCE, mkset, build_dir)
build_mkset(NEW_SET_SOURCE, mkset_new, build_dir)
tests_completed = 0
while True:
n = random.randint(1, MAX_N)
bpp = random.randint(MIN_BPP, MAX_BPP)
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
print(
f"{current_time} tests={tests_completed} bpp={bpp} n={n}",
flush=True,
)
generate_input(input_path, n)
new_result = run_mkset(mkset_new, input_path, bpp)
old_result = run_mkset(mkset, input_path, bpp)
tests_completed += 1
if new_result != old_result:
error_path = ERROR_DIR / (
f"test_{tests_completed}_bpp{bpp}_n{n}_{time.time_ns()}.txt"
)
error_path.write_bytes(input_path.read_bytes())
print(f"mismatch: saved input to {error_path}", flush=True)
if __name__ == "__main__":
main()