grade.py: не падать на машине без компилятора — ПРОПУСК с причиной, авто-отключение санитайзеров, .exe под Windows, WSL-подсказка
This commit is contained in:
+3
-1
@@ -54,7 +54,9 @@ python3 facts_drill.py --domain algo # только алгоритмы
|
||||
python3 facts_drill.py --answers "20|62|ttl|..." # проверить строку ответов разом
|
||||
```
|
||||
|
||||
Порядок: сначала квиз, потом задачи с 01 по 09 (диагностика), затем 10–13 как добор. Зависание = FAIL (есть таймауты).
|
||||
Порядок: сначала квиз, потом задачи с 01 по 09 (диагностика), затем 10–13 как добор.
|
||||
Требования: Linux/WSL (или MinGW-w64 для задач 01–06, 10–13). Задачи 07/08/09 под
|
||||
Windows-native пропускаются с пометкой `ПРОПУСК` — это не провал, гоняй их в WSL. Зависание = FAIL (есть таймауты).
|
||||
Правила честности: без встроенных `popcount`/`reverse`-хелперов, без копирования чужих
|
||||
решений, `answer.txt` в задаче 09 — обязательная часть (оценивается ход разбора).
|
||||
|
||||
|
||||
+131
-15
@@ -22,9 +22,90 @@ HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
TASKS = os.path.join(HERE, "tasks")
|
||||
SUB = os.path.join(HERE, "submissions")
|
||||
|
||||
WINDOWS = os.name == "nt"
|
||||
LINUX = sys.platform.startswith("linux")
|
||||
|
||||
CXX = os.environ.get("CXX", "g++")
|
||||
CC = os.environ.get("CC", "gcc")
|
||||
SAN = "-fsanitize=address,undefined -fno-omit-frame-pointer"
|
||||
FAST = False
|
||||
CXX_PATH = None
|
||||
CC_PATH = None
|
||||
HAS_SAN = False
|
||||
|
||||
# что нужно задаче, чтобы её вообще можно было запускать на этой машине
|
||||
# (проверяется до компиляции; чего нет — задача попадает в ПРОПУСК, а не в FAIL)
|
||||
REQUIRE = {
|
||||
"07_epoll": ("linux+cxx", "epoll и unix-сокеты — только под Linux"),
|
||||
"08_bash": ("bash", "нужны bash и awk/sort по большому логу"),
|
||||
"09_gdb": ("cc-san", "нужны gcc/clang с ASAN/UBSAN"),
|
||||
}
|
||||
|
||||
|
||||
def tool(*names):
|
||||
for n in names:
|
||||
p = shutil.which(n)
|
||||
if p:
|
||||
return p
|
||||
return None
|
||||
|
||||
|
||||
def exe(name):
|
||||
return name + ".exe" if WINDOWS else name
|
||||
|
||||
|
||||
def run_path(name):
|
||||
"""Путь для запуска локального бинаря (на Windows — с .exe и в кавычках-агностично)."""
|
||||
return os.path.join(".", exe(name)) if not WINDOWS else exe(name)
|
||||
|
||||
|
||||
def probe_sanitizers():
|
||||
"""Может ли компилятор собрать простейший файл с ASAN/UBSAN."""
|
||||
src = os.path.join(HERE, ".san_probe.cpp")
|
||||
out = os.path.join(HERE, exe(".san_probe"))
|
||||
try:
|
||||
with open(src, "w", encoding="utf-8") as f:
|
||||
f.write("int main(){int*p=new int[4];p[0]=1;int v=p[0];delete[]p;return v-1;}\n")
|
||||
r = subprocess.run([CXX, "-std=c++17", "-O0"] + SAN.split() + [src, "-o", out],
|
||||
capture_output=True, text=True, timeout=120)
|
||||
ok = r.returncode == 0
|
||||
except Exception:
|
||||
ok = False
|
||||
finally:
|
||||
for p in (src, out):
|
||||
try:
|
||||
os.remove(p)
|
||||
except OSError:
|
||||
pass
|
||||
return ok
|
||||
|
||||
|
||||
def requirements_met(name):
|
||||
"""(можно ли запускать, причина пропуска)"""
|
||||
need, why = REQUIRE.get(name, (None, None))
|
||||
if need == "linux+cxx":
|
||||
if not LINUX:
|
||||
return False, why
|
||||
if not CXX_PATH:
|
||||
return False, "не найден компилятор C++ (g++/clang++)"
|
||||
return True, None
|
||||
if need == "bash":
|
||||
if WINDOWS:
|
||||
return False, why + " (под Windows — только в WSL)"
|
||||
if not tool("bash"):
|
||||
return False, "не найден bash"
|
||||
if not tool("awk"):
|
||||
return False, "не найден awk"
|
||||
return True, None
|
||||
if need == "cc-san":
|
||||
if not CC_PATH:
|
||||
return False, "не найден gcc/clang"
|
||||
if not HAS_SAN:
|
||||
return False, "компилятор не умеет ASAN/UBSAN (MinGW/MSVC — WSL нужен)"
|
||||
return True, None
|
||||
if not CXX_PATH:
|
||||
return False, "не найден компилятор C++ (g++/clang++)"
|
||||
return True, None
|
||||
|
||||
# имя задачи: (тип, что запускать, таймаут, флаги компиляции)
|
||||
SPEC = {
|
||||
@@ -49,6 +130,9 @@ def sh(cmd, cwd, timeout):
|
||||
try:
|
||||
r = subprocess.run(cmd, cwd=cwd, capture_output=True, text=True, timeout=timeout)
|
||||
return r.returncode, r.stdout + r.stderr, time.time() - t0, False
|
||||
except FileNotFoundError as e:
|
||||
return 127, f"[НЕТ ПРОГРАММЫ] {e.filename or cmd[0]}: не найдена в PATH\n" \
|
||||
f"Проверь установку компилятора/утилиты или запусти под WSL.", time.time() - t0, False
|
||||
except subprocess.TimeoutExpired as e:
|
||||
out = (e.stdout or b"").decode(errors="replace") if isinstance(e.stdout, bytes) else (e.stdout or "")
|
||||
return None, out + "\n[TIMEOUT] превышено время %s c" % timeout, time.time() - t0, True
|
||||
@@ -68,13 +152,14 @@ def run_task(name, root):
|
||||
extra = [] if FAST else ["-fsanitize=thread"]
|
||||
elif not FAST:
|
||||
extra = SAN.split()
|
||||
cmd = [CXX] + flags + (extra or []) + [test, "-o", "t_" + name]
|
||||
binname = "t_" + name
|
||||
cmd = [CXX] + flags + (extra or []) + [test, "-o", binname]
|
||||
rc, out, secs, timed_out = sh(cmd, d, timeout)
|
||||
log.append("$ " + " ".join(cmd) + "\n" + out)
|
||||
if rc != 0:
|
||||
return False, log, secs, timed_out
|
||||
rc, out, secs2, timed_out = sh(["./t_" + name], d, timeout)
|
||||
log.append("$ ./t_" + name + "\n" + out)
|
||||
rc, out, secs2, timed_out = sh([run_path(binname)], d, timeout)
|
||||
log.append("$ " + run_path(binname) + "\n" + out)
|
||||
secs += secs2
|
||||
if "unsupported VMA" in out and not FAST:
|
||||
# ThreadSanitizer не поддерживает этот размер адресного пространства
|
||||
@@ -84,8 +169,8 @@ def run_task(name, root):
|
||||
rc, out2, s, to = sh(cmd2, d, timeout)
|
||||
log.append("$ " + " ".join(cmd2) + "\n# TSan недоступен на этой платформе, откат на ASAN\n" + out2)
|
||||
if rc == 0:
|
||||
rc, out2, s2, to = sh(["./t_" + name], d, timeout)
|
||||
log.append("$ ./t_" + name + "\n" + out2)
|
||||
rc, out2, s2, to = sh([run_path("t_" + name)], d, timeout)
|
||||
log.append("$ " + run_path("t_" + name) + "\n" + out2)
|
||||
secs += s2
|
||||
elif kind == "bin":
|
||||
# epoll-сервер: solution.cpp -> бинарь solution, затем python-тест
|
||||
@@ -110,7 +195,7 @@ def run_task(name, root):
|
||||
|
||||
|
||||
def main():
|
||||
global FAST
|
||||
global FAST, CXX, CC, CXX_PATH, CC_PATH, HAS_SAN
|
||||
args = [a for a in sys.argv[1:]]
|
||||
FAST = "--fast" in args
|
||||
do_submit = "--submit" in args
|
||||
@@ -120,25 +205,56 @@ def main():
|
||||
else:
|
||||
names = list(SPEC)
|
||||
|
||||
CXX_PATH = tool(CXX, "g++", "clang++", "c++", "x86_64-w64-mingw32-g++")
|
||||
CC_PATH = tool(CC, "gcc", "clang", "cc", "x86_64-w64-mingw32-gcc")
|
||||
if CXX_PATH:
|
||||
CXX = CXX_PATH
|
||||
if CC_PATH:
|
||||
CC = CC_PATH
|
||||
os.environ["CC"] = CC
|
||||
HAS_SAN = probe_sanitizers() if CXX_PATH else False
|
||||
if not FAST and CXX_PATH and not HAS_SAN:
|
||||
print("# Компилятор не собирает с ASAN/UBSAN — санитайзеры отключены автоматически.")
|
||||
FAST = True
|
||||
|
||||
if WINDOWS:
|
||||
print("# Windows-native сборка: работают задачи с компилятором C++ (01-06, 10-13).")
|
||||
print("# 07 (epoll), 08 (bash/awk) и 09 (ASAN+gdb) требуют Linux — запускай их в WSL.")
|
||||
if not CXX_PATH:
|
||||
print("# Не найден компилятор C++ (g++/clang++). Поставь build-essential/MinGW-w64")
|
||||
print("# или запусти задачи в WSL: wsl --install -d Ubuntu")
|
||||
|
||||
results = {}
|
||||
print(f"{'задача':12s} {'итог':7s} {'проверок':9s} {'FAIL':5s} {'время':8s}")
|
||||
skipped = []
|
||||
print(f"{'задача':12s} {'итог':8s} {'проверок':9s} {'FAIL':5s} {'время':8s}")
|
||||
for n in names:
|
||||
can, why = requirements_met(n)
|
||||
if not can:
|
||||
results[n] = {"pass": False, "skipped": True, "reason": why,
|
||||
"checks_ok": 0, "checks_fail": 0, "seconds": 0.0, "log": []}
|
||||
skipped.append((n, why))
|
||||
print(f"{n:12s} {'ПРОПУСК':8s} — {why}")
|
||||
continue
|
||||
ok, log, secs, to = run_task(n, TASKS)
|
||||
body = log[-1]
|
||||
body = log[-1] if log else ""
|
||||
n_ok = body.count("ok ")
|
||||
n_bad = body.count("FAIL ")
|
||||
results[n] = {
|
||||
"pass": ok, "checks_ok": n_ok, "checks_fail": n_bad,
|
||||
"pass": ok, "skipped": False, "checks_ok": n_ok, "checks_fail": n_bad,
|
||||
"seconds": round(secs, 2), "timeout": to, "log": log,
|
||||
}
|
||||
print(f"{n:12s} {'PASS' if ok else 'FAIL':7s} {n_ok:<9d} {n_bad:<5d} {secs:8.2f}")
|
||||
print(f"{n:12s} {'PASS' if ok else 'FAIL':8s} {n_ok:<9d} {n_bad:<5d} {secs:8.2f}")
|
||||
|
||||
total = len(names)
|
||||
done = sum(1 for n in names if results[n]["pass"])
|
||||
tried = [n for n in names if not results[n].get("skipped")]
|
||||
done = sum(1 for n in tried if results[n]["pass"])
|
||||
with open(os.path.join(HERE, "results_tasks.json"), "w", encoding="utf-8") as f:
|
||||
json.dump({"passed": done, "total": total, "tasks": results}, f,
|
||||
json.dump({"passed": done, "total": len(tried), "skipped": len(skipped),
|
||||
"run_on": sys.platform, "tasks": results}, f,
|
||||
ensure_ascii=False, indent=2)
|
||||
print(f"\nИТОГ ЗАДАЧ: {done}/{total} PASS. Подробности: results_tasks.json")
|
||||
print(f"\nИТОГ ЗАДАЧ: {done}/{len(tried)} PASS. Подробности: results_tasks.json")
|
||||
if skipped:
|
||||
print(f"Пропущено (не запускались, это не провал): {len(skipped)} — "
|
||||
+ "; ".join(f"{n}: {why}" for n, why in skipped))
|
||||
|
||||
if do_submit:
|
||||
os.makedirs(SUB, exist_ok=True)
|
||||
@@ -156,7 +272,7 @@ def main():
|
||||
os.chmod(os.path.join(SUB, fn), 0o644)
|
||||
print(f"Ответы скопированы в {SUB}")
|
||||
print("Пакет: 01-09 диагностика, 10-13 добор по слабым доменам (см. WEEK7.md).")
|
||||
sys.exit(0 if done == total else 1)
|
||||
sys.exit(0 if done == len(tried) else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -6,7 +6,7 @@ import sys
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
SRC = os.path.join(HERE, "crash.c")
|
||||
BIN = os.path.join(HERE, "crash_checked")
|
||||
BIN = os.path.join(HERE, "crash_checked" + (".exe" if os.name == "nt" else ""))
|
||||
failures = 0
|
||||
|
||||
|
||||
@@ -20,7 +20,8 @@ def check(cond, name, extra=""):
|
||||
|
||||
|
||||
def main():
|
||||
r = subprocess.run(["gcc", "-g", "-O1", "-Wall", "-Wextra",
|
||||
cc = os.environ.get("CC", "gcc")
|
||||
r = subprocess.run([cc, "-g", "-O1", "-Wall", "-Wextra",
|
||||
"-fsanitize=address,undefined", "-fno-omit-frame-pointer",
|
||||
SRC, "-o", BIN], capture_output=True, text=True, timeout=180)
|
||||
check(r.returncode == 0, "сборка с ASAN/UBSAN прошла", r.stderr[:800])
|
||||
|
||||
Reference in New Issue
Block a user