132 lines
3.9 KiB
Python
Executable File
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()
|