"""空白测试服(M0)的判据。 ★ 最重要的一条:判据必须能**失败**(工程纪律 #2)。 本文件的 test_status_proves_protocol 靠**真的连一次服务端**来判 —— 端口开着但服务端不是 MC,它会红。 没起服时**跳过**(不误报失败)。 """ import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parents[1])) from mcfold import server # noqa: E402 def test_config_is_generated_consistently(): """配置文件由模块生成 ⇒ 内容必须与常量一致(防手工改歪)。 ★ 踩过的坑:properties 里写行内注释(`k=v # 注释`)会被解析成 值的一部分 —— `level-type=minecraft\\:flat \\# 超平坦` 就是这样坏的。 所以生成器**不写行内注释**,这条守护它。 """ gen = server.SERVER_PROPERTIES.format(port=server.PORT) for line in gen.splitlines(): if line.startswith("#") or not line.strip(): continue assert " #" not in line, f"不能有行内注释(会被当值):{line}" assert "#" not in line.split("=", 1)[1], f"值里不能有 #:{line}" assert f"server-port={server.PORT}" in gen assert "level-type=minecraft\\:flat" in gen assert "online-mode=false" in gen print(f" ✓ 配置生成一致(port={server.PORT}, flat, offline)") def test_three_layer_isolation(): """内容根与运行期必须是**不同**的层(版本隔离开支)。""" assert server.CONTENT != server.RUNTIME assert "store" in str(server.CONTENT), f"服务端内容应在 store/ 下:{server.CONTENT}" assert "runtime" in str(server.RUNTIME), f"日志应在 runtime/ 下:{server.RUNTIME}" # 日志不能写进内容根(那是可只读共享的) assert server.CONTENT not in server.LOG.parents, \ f"日志不该落在内容根:{server.LOG}" print(f" ✓ 三层隔离(内容={server.CONTENT.name}/ 运行期={server.RUNTIME.name}/)") def test_port_avoids_production(): """端口必须避开现役服的 25565。""" assert server.PORT != 25565, "测试服不该占现役端口" print(f" ✓ 端口避让(测试 {server.PORT} vs 现役 25565)") def test_status_proves_protocol(): """★ 核心判据:服务端的 status 查询必须回出 MC 协议信息。 这一条**能失败**: · 端口没开 ⇒ status() 返回 None ⇒ 跳过(不是失败) · 端口开着但非 MC ⇒ status() 返回 None ⇒ 跳过(无法区分,诚实跳过) · 服务端真在跑 ⇒ 必须回出 protocol == 767 起服时它验协议;没起服时它不误报。 """ st = server.status() if st is None: print(f" - 跳过({server.PORT} 上没探到 MC 服务端)") return assert st["version"] == server.MC_VERSION, \ f"版本不符:{st['version']} != {server.MC_VERSION}" assert st["protocol"] == server.PROTOCOL, \ f"协议号不符:{st['protocol']} != {server.PROTOCOL}" assert st["players_max"] > 0, st print(f" ✓ 协议级探活({st['version']} protocol {st['protocol']}," f"players {st['players_online']}/{st['players_max']})") if __name__ == "__main__": tests = [v for k, v in sorted(globals().items()) if k.startswith("test_")] failed = [] for t in tests: try: t() except AssertionError as e: failed.append(f"{t.__name__}: {e}") print(f" ✗ {t.__name__}: {e}") except Exception as e: failed.append(f"{t.__name__}: {e!r}") print(f" ✗ {t.__name__}: {e!r}") print() if failed: print(f"空白测试服: {len(tests)-len(failed)}/{len(tests)} 通过") sys.exit(1) print(f"空白测试服: {len(tests)}/{len(tests)} 全部通过")