"""起動→数十秒待つ→stop を繰り返し、起動直後のクラッシュ(本番 9/4 15:00 の型)が何回に1回出るか数える。 使い方: python labboot.py <回数> <待ち秒> [key=value ...] 例: python labboot.py C:\...\_lab3 boot_mt2 15 30 max-threads=2 結果: /boot_.log に全出力、最後に SUMMARY。 """ import re, subprocess, sys, threading, time from pathlib import Path LAB = Path(sys.argv[1]) tag = sys.argv[2] count = int(sys.argv[3]) hold = float(sys.argv[4]) overrides = dict(kv.split("=", 1) for kv in sys.argv[5:]) props = (LAB / "server.properties").read_text(encoding="utf-8").splitlines() seen = set() for i, line in enumerate(props): m = re.match(r"^([A-Za-z0-9_.-]+)=", line) if m and m.group(1) in overrides: props[i] = f"{m.group(1)}={overrides[m.group(1)]}" seen.add(m.group(1)) for k, v in overrides.items(): if k not in seen: props.append(f"{k}={v}") (LAB / "server.properties").write_text("\n".join(props) + "\n", encoding="utf-8") cur = {m.group(1): m.group(2) for m in (re.match(r"^([A-Za-z0-9_.-]+)=(.*)$", l) for l in props) if m} print("設定:", {k: cur.get(k) for k in ("max-threads", "view-distance", "client-side-chunk-generation-enabled", "server-port")}, flush=True) log = open(LAB / f"boot_{tag}.log", "w", encoding="utf-8", errors="replace") results = [] ALT = [v for v in overrides.get("lab.alt", "").split(",") if v] # 例 lab.alt=2,0 → max-threads を交互に per_cfg = {} RESTORE = overrides.get("lab.restore") # 毎回この DB フォルダを worlds/lab/db に復元してから起動 for it in range(1, count + 1): if RESTORE: import shutil dst = LAB / "worlds" / "lab" / "db" if dst.exists(): shutil.rmtree(dst) shutil.copytree(RESTORE, dst) (LAB / "worlds" / "lab" / "db" / "LOCK").unlink(missing_ok=True) mt = cur.get("max-threads") if ALT: mt = ALT[(it - 1) % len(ALT)] txt = (LAB / "server.properties").read_text(encoding="utf-8") txt = re.sub(r"(?m)^max-threads=.*$", f"max-threads={mt}", txt) (LAB / "server.properties").write_text(txt, encoding="utf-8") p = subprocess.Popen([str(LAB / "bedrock_server.exe")], cwd=str(LAB), stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, bufsize=0) started = threading.Event() t0 = time.time() def reader(): for raw in iter(p.stdout.readline, b""): s = raw.decode("utf-8", "replace").rstrip() log.write(f"[{it:02d} {time.time()-t0:6.1f}] {s}\n") if "Server started." in s: started.set() log.flush() threading.Thread(target=reader, daemon=True).start() ok = started.wait(180) t_start = time.time() - t0 died = None if ok: end = time.time() + hold while time.time() < end and p.poll() is None: time.sleep(0.5) if p.poll() is not None: died = time.time() - t0 - t_start else: died = -1 if p.poll() is None: try: p.stdin.write(b"stop\n"); p.stdin.flush() p.wait(90) except Exception: p.kill() rc = p.returncode if died is None: line = f" {it:02d} [mt={mt}]: 生存 (起動 {t_start:.1f}s, {hold:.0f}s 保持, 正常停止 rc={rc})" results.append("alive") per_cfg.setdefault(mt, []).append("alive") else: line = f" {it:02d} [mt={mt}]: ★死亡 起動後 {died:.1f}s exit=0x{rc & 0xffffffff:08X}" results.append("dead") per_cfg.setdefault(mt, []).append("dead") print(line, flush=True) log.write(line + "\n"); log.flush() time.sleep(3) n_dead = results.count("dead") detail = " / ".join(f"mt={k}: {v.count('dead')}死亡/{len(v)}回" for k, v in per_cfg.items()) summary = f"SUMMARY tag={tag} 起動 {count} 回中 死亡 {n_dead} 回 ({100*n_dead/count:.0f}%) [{detail}]" print(summary, flush=True) log.write(summary + "\n"); log.close()