apply_jvm wrote instance.cfg and never restored it, so B5-B12a in run 1 ran on B4's last set (Shenandoah, 6 GB). Within-block verdicts hold (same JVM on both sides); B7's no-shader baseline is re-run in run 2. Also: B14 spawn-count automation (b14/: fresh seeded world per run, offline-located biome sites, datapack entity counts, journaled config variants) and RESULTS-proxy.md with run-1 verdicts.
414 lines
19 KiB
Python
414 lines
19 KiB
Python
"""Offline tests for b14.py (temp dirs only; no game, no git writes)."""
|
|
import gzip
|
|
import json
|
|
import struct
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
import zipfile
|
|
import zlib
|
|
from pathlib import Path
|
|
|
|
HERE = Path(__file__).resolve().parent
|
|
sys.path.insert(0, str(HERE.parent))
|
|
sys.path.insert(0, str(HERE.parent.parent))
|
|
import b14 # noqa: E402
|
|
import bench # noqa: E402
|
|
import nbt # noqa: E402
|
|
|
|
AM_PARTIAL = """# partial
|
|
[spawning]
|
|
\tgazelleSpawnWeight = 20
|
|
\tcapuchinMonkeySpawnWeight = 14
|
|
\telephantSpawnWeight = 15
|
|
\tstraddlerSpawnWeight = 35
|
|
\trockyRollerSpawnWeight = 30
|
|
\tunderminerSpawnWeight = 25
|
|
\tblobfishSpawnWeight = 15
|
|
\tmudskipperSpawnWeight = 14
|
|
\tcaimanSpawnWeight = 15
|
|
"""
|
|
FP_PARTIAL = """// partial
|
|
{
|
|
"duckSpawnWeight": 2, "duckMaxGroupSize": 4, "gooseSpawnWeight": 2,
|
|
"gooseMaxGroupSize": 3, "gullSpawnWeight": 3, "gullMaxGroupSize": 5,
|
|
"sparrowSpawnWeight": 12, "sparrowMaxGroupSize": 4
|
|
}
|
|
"""
|
|
CAC_PARTIAL = """{
|
|
"configVersion": 2,
|
|
"spawning": {
|
|
"leaf_insect": [
|
|
{"biome_spec": "#c:is_jungle", "weight": 8, "min": 1, "max": 1},
|
|
{"biome_spec": "#c:is_forest", "weight": 4, "min": 1, "max": 1}
|
|
],
|
|
"sea_bunny": [
|
|
{"biome_spec": "minecraft:ocean", "weight": 10, "min": 1, "max": 2},
|
|
{"biome_spec": "minecraft:warm_ocean", "weight": 20, "min": 1, "max": 3}
|
|
],
|
|
"dragonfly": [
|
|
{"biome_spec": "#c:is_river", "weight": 4, "min": 1, "max": 1},
|
|
{"biome_spec": "#c:is_lush", "weight": 6, "min": 1, "max": 1}
|
|
]
|
|
}
|
|
}
|
|
"""
|
|
SHIPPED = {"alexsmobs-common.toml": AM_PARTIAL, "fowlplay.json5": FP_PARTIAL,
|
|
"crittersandcompanions-common.json5": CAC_PARTIAL}
|
|
|
|
# What the mods write back (re-saved, json5 unquoted keys, tabs) with default values.
|
|
AM_RESAVED_DEFAULT = "[spawning]\n" + "".join(
|
|
f"\t#Spawn Weight\n\t# Default: {v}\n\t{k} = {v}\n"
|
|
for k, v in b14.EXPECT_DEFAULTS["alexsmobs-common.toml"].items())
|
|
FP_RESAVED_DEFAULT = "{\n" + ",\n".join(
|
|
f"\t{k}: {v}" for k, v in b14.EXPECT_DEFAULTS["fowlplay.json5"].items()) + "\n}"
|
|
CAC_RESAVED_DEFAULT = """{
|
|
\tconfigVersion: 2,
|
|
\tspawning: {
|
|
\t\tleaf_insect: [
|
|
\t\t\t{
|
|
\t\t\t\tbiome_spec: "#c:is_jungle",
|
|
\t\t\t\tweight: 14,
|
|
\t\t\t\tmin: 1,
|
|
\t\t\t\tmax: 1
|
|
\t\t\t},
|
|
\t\t\t{ biome_spec: "#c:is_forest", weight: 6, min: 1, max: 1 }
|
|
\t\t],
|
|
\t\tred_panda: [ { biome_spec: "#c:is_jungle", weight: 8, min: 1, max: 2 } ],
|
|
\t\tsea_bunny: [
|
|
\t\t\t{ biome_spec: "minecraft:ocean", weight: 16, min: 1, max: 2 },
|
|
\t\t\t{ biome_spec: "minecraft:warm_ocean", weight: 32, min: 1, max: 4 }
|
|
\t\t],
|
|
\t\tdragonfly: [
|
|
\t\t\t{ biome_spec: "#c:is_river", weight: 4, min: 1, max: 1 },
|
|
\t\t\t{ biome_spec: "#c:is_lush", weight: 10, min: 1, max: 1 }
|
|
\t\t]
|
|
\t}
|
|
}"""
|
|
|
|
|
|
class ConfigVariants(unittest.TestCase):
|
|
def test_render(self):
|
|
d = b14.render_variant("defaults", SHIPPED)
|
|
self.assertEqual(set(d), set(b14.SPAWN_FILES))
|
|
self.assertTrue(all(v is None for v in d.values()))
|
|
s = b14.render_variant("shipped", SHIPPED)
|
|
self.assertEqual(s["fowlplay.json5"], FP_PARTIAL)
|
|
with self.assertRaises(ValueError):
|
|
b14.render_variant("nope", SHIPPED)
|
|
|
|
def test_extract_partial_and_resaved(self):
|
|
keys = b14.EXPECT_DEFAULTS
|
|
got = b14.extract_values("crittersandcompanions-common.json5", CAC_PARTIAL,
|
|
keys["crittersandcompanions-common.json5"])
|
|
self.assertEqual(got["sea_bunny@minecraft:warm_ocean"], 20)
|
|
self.assertEqual(got["leaf_insect@#c:is_jungle"], 8)
|
|
self.assertEqual(got["dragonfly@#c:is_lush"], 6)
|
|
got = b14.extract_values("crittersandcompanions-common.json5", CAC_RESAVED_DEFAULT,
|
|
keys["crittersandcompanions-common.json5"])
|
|
self.assertEqual(got, keys["crittersandcompanions-common.json5"])
|
|
self.assertEqual(b14.extract_values("fowlplay.json5", FP_RESAVED_DEFAULT,
|
|
keys["fowlplay.json5"]), keys["fowlplay.json5"])
|
|
self.assertEqual(b14.extract_values("alexsmobs-common.toml", AM_RESAVED_DEFAULT,
|
|
keys["alexsmobs-common.toml"]),
|
|
keys["alexsmobs-common.toml"])
|
|
# "Default: 40" comment lines must not be mistaken for the value
|
|
self.assertEqual(b14.extract_values("alexsmobs-common.toml", AM_PARTIAL,
|
|
["gazelleSpawnWeight"]), {"gazelleSpawnWeight": 20})
|
|
|
|
def test_check_config_and_journaled_apply(self):
|
|
with tempfile.TemporaryDirectory() as td:
|
|
game = Path(td)
|
|
(game / "config").mkdir()
|
|
originals = {"alexsmobs-common.toml": AM_RESAVED_DEFAULT + "# orig\n",
|
|
"fowlplay.json5": FP_RESAVED_DEFAULT} # CAC absent on purpose
|
|
for n, t in originals.items():
|
|
(game / "config" / n).write_text(t)
|
|
j = bench.Journal.begin(game, "b14-1")
|
|
b14.apply_spawn_config(game, j, b14.render_variant("shipped", SHIPPED))
|
|
for n in b14.SPAWN_FILES:
|
|
self.assertEqual((game / "config" / n).read_text(), SHIPPED[n])
|
|
self.assertTrue(b14.check_config(game, "shipped", SHIPPED)["ok"])
|
|
chk = b14.check_config(game, "defaults", SHIPPED)
|
|
self.assertFalse(chk["ok"])
|
|
self.assertIn("fowlplay.json5:duckSpawnWeight want 4 got 2", chk["mismatch"])
|
|
self.assertEqual(bench.restore_pending(game), [])
|
|
for n, t in originals.items():
|
|
self.assertEqual((game / "config" / n).read_text(), t)
|
|
self.assertFalse((game / "config" / "crittersandcompanions-common.json5").exists())
|
|
self.assertFalse((game / bench.PENDING).exists())
|
|
|
|
def test_defaults_delete_and_restore(self):
|
|
with tempfile.TemporaryDirectory() as td:
|
|
game = Path(td)
|
|
(game / "config").mkdir()
|
|
for n in b14.SPAWN_FILES:
|
|
(game / "config" / n).write_text("x" + n)
|
|
j = bench.Journal.begin(game, "b14-2")
|
|
b14.apply_spawn_config(game, j, b14.render_variant("defaults", SHIPPED))
|
|
self.assertFalse(any((game / "config" / n).exists() for n in b14.SPAWN_FILES))
|
|
self.assertTrue(b14.check_config(game, "defaults", SHIPPED)["ok"]) # absent = defaults
|
|
self.assertFalse(b14.check_config(game, "shipped", SHIPPED)["ok"])
|
|
# what the mods write after load
|
|
(game / "config" / "alexsmobs-common.toml").write_text(AM_RESAVED_DEFAULT)
|
|
(game / "config" / "fowlplay.json5").write_text(FP_RESAVED_DEFAULT)
|
|
(game / "config" / "crittersandcompanions-common.json5").write_text(CAC_RESAVED_DEFAULT)
|
|
self.assertTrue(b14.check_config(game, "defaults", SHIPPED)["ok"])
|
|
bench.restore_pending(game)
|
|
for n in b14.SPAWN_FILES:
|
|
self.assertEqual((game / "config" / n).read_text(), "x" + n)
|
|
|
|
def test_config_diff(self):
|
|
with tempfile.TemporaryDirectory() as td:
|
|
game = Path(td)
|
|
(game / "config").mkdir()
|
|
(game / "config" / "fowlplay.json5").write_text(FP_RESAVED_DEFAULT)
|
|
txt = b14.config_diff(game, b14.render_variant("shipped", SHIPPED), SHIPPED)
|
|
self.assertIn("duckSpawnWeight: 4 -> 2 <- changes", txt)
|
|
self.assertIn("config/alexsmobs-common.toml: WRITE", txt)
|
|
txt = b14.config_diff(game, b14.render_variant("defaults", SHIPPED), SHIPPED)
|
|
self.assertIn("config/fowlplay.json5: DELETE", txt)
|
|
|
|
|
|
SITES = [("river", {"x": 904, "y": 63, "z": 88}), ("warm_ocean", {"x": -200, "y": 63, "z": 56})]
|
|
|
|
|
|
class Datapack(unittest.TestCase):
|
|
def setUp(self):
|
|
self.files = b14.generate_datapack("b14-20261018000000", SITES, 2, 20)
|
|
self.base = "data/b14/function/b14-20261018000000"
|
|
|
|
def test_structure(self):
|
|
f = self.files
|
|
self.assertEqual(json.loads(f["pack.mcmeta"])["pack"]["pack_format"], 48)
|
|
self.assertEqual(json.loads(f["data/minecraft/tags/function/load.json"]),
|
|
{"values": ["b14:b14-20261018000000/load"]})
|
|
begin = f[f"{self.base}/begin.mcfunction"]
|
|
self.assertIn("gamerule doMobSpawning true", begin)
|
|
self.assertIn("gamemode creative @a", begin)
|
|
self.assertNotIn("spectator", "".join(f.values()))
|
|
self.assertIn("step0 400t", begin)
|
|
# 2 sites x (tp, arrive, t=0..2) + done
|
|
steps = [k for k in f if "/step" in k]
|
|
self.assertEqual(len(steps), 2 * (2 + 3) + 1)
|
|
self.assertIn("B14_DONE", f[f"{self.base}/step{len(steps) - 1}.mcfunction"])
|
|
|
|
def test_counts_and_chain(self):
|
|
count = self.files[f"{self.base}/count.mcfunction"]
|
|
for _, eid in b14.TUNED + b14.VANILLA:
|
|
self.assertIn(f"@e[type={eid},distance=..128]", count)
|
|
self.assertIn("@e[type=!minecraft:player,distance=..128]", count)
|
|
self.assertEqual(count.count("+= #"), len(b14.TUNED))
|
|
tl = b14.build_timeline("b14-20261018000000", SITES, 2, 20)
|
|
delays = [d for _, d in tl]
|
|
self.assertEqual(delays[:5], [100, 200, 1200, 1200, 20])
|
|
self.assertEqual(delays[-1], 0)
|
|
for i in range(len(tl) - 1):
|
|
body = self.files[f"{self.base}/step{i}.mcfunction"]
|
|
self.assertIn(f"schedule function b14:b14-20261018000000/step{i + 1} ", body)
|
|
s2 = self.files[f"{self.base}/step2.mcfunction"]
|
|
self.assertIn("setblock 904 62 88 minecraft:barrier", s2)
|
|
self.assertIn("tp @a 904.5 63 88.5", s2)
|
|
self.assertIn("if biome ~ ~ ~ minecraft:river", s2)
|
|
|
|
def test_tellraw_roundtrip(self):
|
|
"""The rendered tellraw text, with scores substituted, parses back."""
|
|
cmd = b14.tellraw("b14-20261018000000", "river", 3)
|
|
parts = json.loads(cmd[len("tellraw @a "):])
|
|
text = "".join(p if isinstance(p, str) else p.get("text", "7") for p in parts)
|
|
line = ("[18Oct2026 00:10:00.000] [Render thread/INFO] [net.minecraft.client.gui."
|
|
f"components.ChatComponent/]: [System] [CHAT] {text}")
|
|
r = b14.parse_count_line(line)
|
|
self.assertEqual((r["sid"], r["biome"], r["t"], r["total"], r["biome_ok"]),
|
|
("b14-20261018000000", "river", 3, 7, 7))
|
|
self.assertEqual(set(r["counts"]), {eid for _, eid in b14.TUNED + b14.VANILLA})
|
|
|
|
def test_world_build(self):
|
|
with tempfile.TemporaryDirectory() as td:
|
|
td = Path(td)
|
|
zp = td / "w.zip"
|
|
with zipfile.ZipFile(zp, "w") as z:
|
|
z.writestr("onionbench/level.dat", b"lvl")
|
|
z.writestr("onionbench/region/r.0.0.mca", b"x")
|
|
z.writestr("onionbench/data/chunks.dat", b"x")
|
|
z.writestr("onionbench/datapacks/onion-bench/pack.mcmeta", "{}")
|
|
z.writestr("onionbench/serverconfig/a.toml", "a")
|
|
game = td / "game"
|
|
(game / "saves").mkdir(parents=True)
|
|
j = bench.Journal.begin(game, "b14-3")
|
|
root = b14.build_world(game, j, zp, self.files)
|
|
self.assertEqual((root / "level.dat").read_bytes(), b"lvl")
|
|
self.assertFalse((root / "region").exists())
|
|
self.assertFalse((root / "data").exists())
|
|
self.assertTrue((root / "serverconfig/a.toml").exists())
|
|
self.assertTrue((root / "datapacks" / b14.PACK / "pack.mcmeta").exists())
|
|
with self.assertRaises(ValueError):
|
|
b14.build_world(game, bench.Journal(game, "x"), zp, self.files)
|
|
bench.restore_pending(game)
|
|
self.assertFalse(root.exists())
|
|
|
|
|
|
def _line(sid, biome, t, total, counts, ok=1):
|
|
body = " ".join(f"{k}={v}" for k, v in counts.items())
|
|
return (f"[x] [Render thread/INFO] [ChatComponent/]: [System] [CHAT] B14 {sid} {biome} "
|
|
f"t={t} biome_ok={ok} total={total} nonitem={total} tuned=0 {body}")
|
|
|
|
|
|
class ParseAndSummary(unittest.TestCase):
|
|
def test_parse_dedupes_and_filters(self):
|
|
lines = [_line("b14-1", "river", 0, 5, {"fowlplay:duck": 1}),
|
|
_line("b14-1", "river", 0, 5, {"fowlplay:duck": 1}),
|
|
"[Server thread/INFO]: [Server] B14_ARRIVE b14-1 river 1 2 3",
|
|
_line("b14-2", "river", 0, 9, {"fowlplay:duck": 2}),
|
|
"noise B14 garbage"]
|
|
rows = b14.parse_lines(lines)
|
|
self.assertEqual(len(rows), 2)
|
|
self.assertEqual(len(b14.parse_lines(lines, "b14-2")), 1)
|
|
self.assertEqual(rows[0]["counts"], {"fowlplay:duck": 1})
|
|
|
|
def _rows(self, config, sid, biome, totals, duck):
|
|
out = []
|
|
for t, (tot, d) in enumerate(zip(totals, duck)):
|
|
r = b14.parse_count_line(_line(sid, biome, t, tot, {"fowlplay:duck": d,
|
|
"minecraft:cow": 1}))
|
|
out.append({**r, "config": config, "kind": "b14_count"})
|
|
return out
|
|
|
|
def _sess(self, sid, ok=True):
|
|
return {"kind": "b14_session", "sid": sid, "outcome": "ok",
|
|
"config_check": {"ok": ok}}
|
|
|
|
def test_keep_and_back_off(self):
|
|
counts = (self._rows("defaults", "b14-1", "river", [10, 40], [2, 6])
|
|
+ self._rows("shipped", "b14-2", "river", [8, 25], [0, 3]))
|
|
sess = [self._sess("b14-1"), self._sess("b14-2")]
|
|
out, verdicts, overall = b14.summarize(counts, sess)
|
|
self.assertEqual(verdicts["river"]["verdict"], "keep")
|
|
self.assertEqual(out["river"]["shipped"]["seen"]["fowlplay:duck"], 3)
|
|
self.assertEqual(overall, "incomplete") # jungle / warm_ocean missing
|
|
counts2 = (self._rows("defaults", "b14-1", "river", [10, 40], [2, 6])
|
|
+ self._rows("shipped", "b14-2", "river", [8, 25], [0, 0]))
|
|
_, v2, o2 = b14.summarize(counts2, sess)
|
|
self.assertEqual(v2["river"]["verdict"], "back off")
|
|
self.assertEqual(v2["river"]["disappeared"], ["fowlplay:duck"])
|
|
self.assertIn("back off", b14.render_summary(*b14.summarize(counts2, sess)))
|
|
|
|
def test_bad_sessions_and_biome_check_excluded(self):
|
|
counts = (self._rows("defaults", "b14-1", "river", [10, 40], [2, 6])
|
|
+ self._rows("shipped", "b14-2", "river", [8, 25], [1, 3]))
|
|
_, v, _ = b14.summarize(counts, [self._sess("b14-1"), self._sess("b14-2", ok=False)])
|
|
self.assertEqual(v["river"]["verdict"], "incomplete")
|
|
for r in counts:
|
|
r["biome_ok"] = 0
|
|
_, v, _ = b14.summarize(counts, [self._sess("b14-1"), self._sess("b14-2")])
|
|
self.assertTrue(v["river"]["verdict"].startswith("invalid"))
|
|
|
|
def test_all_biomes_keep(self):
|
|
counts, sess = [], []
|
|
for i, biome in enumerate(b14.BIOMES):
|
|
counts += self._rows("defaults", f"b14-{i}0", biome, [5, 30], [1, 2])
|
|
counts += self._rows("shipped", f"b14-{i}1", biome, [5, 20], [1, 1])
|
|
sess += [self._sess(f"b14-{i}0"), self._sess(f"b14-{i}1")]
|
|
self.assertEqual(b14.summarize(counts, sess)[2], "keep")
|
|
|
|
|
|
# ---------------------------------------------------------------- nbt / locate
|
|
|
|
def _tag(t, name, payload):
|
|
n = name.encode()
|
|
return bytes([t]) + struct.pack(">H", len(n)) + n + payload
|
|
|
|
|
|
def _str(s):
|
|
b = s.encode()
|
|
return struct.pack(">H", len(b)) + b
|
|
|
|
|
|
def _compound(*items):
|
|
return b"".join(items) + b"\x00"
|
|
|
|
|
|
def _chunk_nbt(biome, height=70, status="minecraft:full"):
|
|
hm_longs = []
|
|
vals = [height + 64] * 256 # stored relative to min_y -64, 9 bits, 7 per long
|
|
for i in range(0, 256, 7):
|
|
w = 0
|
|
for k, v in enumerate(vals[i:i + 7]):
|
|
w |= v << (9 * k)
|
|
hm_longs.append(w if w < 2 ** 63 else w - 2 ** 64)
|
|
hm = struct.pack(">i", len(hm_longs)) + struct.pack(f">{len(hm_longs)}q", *hm_longs)
|
|
section = _compound(
|
|
_tag(nbt.BYTE, "Y", struct.pack(">b", (height - 1) >> 4)),
|
|
_tag(nbt.COMPOUND, "biomes", _compound(
|
|
_tag(nbt.LIST, "palette", bytes([nbt.STRING]) + struct.pack(">i", 1) + _str(biome)))))
|
|
root = _compound(
|
|
_tag(nbt.STRING, "Status", _str(status)),
|
|
_tag(nbt.LIST, "sections", bytes([nbt.COMPOUND]) + struct.pack(">i", 1) + section),
|
|
_tag(nbt.COMPOUND, "Heightmaps", _compound(
|
|
_tag(nbt.LONG_ARRAY, "MOTION_BLOCKING_NO_LEAVES", hm))))
|
|
return bytes([nbt.COMPOUND]) + _str("") + root
|
|
|
|
|
|
def _region(chunks):
|
|
"""chunks: {(lx, lz): nbt bytes} -> .mca bytes."""
|
|
header, body, sector = bytearray(8192), bytearray(), 2
|
|
for (lx, lz), raw in chunks.items():
|
|
data = zlib.compress(raw)
|
|
blob = struct.pack(">I", len(data) + 1) + b"\x02" + data
|
|
blob += b"\x00" * (-len(blob) % 4096)
|
|
n = len(blob) // 4096
|
|
idx = lz * 32 + lx
|
|
header[idx * 4:idx * 4 + 4] = sector.to_bytes(3, "big") + bytes([n])
|
|
body += blob
|
|
sector += n
|
|
return bytes(header) + bytes(body)
|
|
|
|
|
|
class NbtAndLocate(unittest.TestCase):
|
|
def test_packed(self):
|
|
self.assertEqual(nbt.unpack_packed([0b10_01_00 | (3 << 62)], 2, 3), [0, 1, 2])
|
|
self.assertEqual(nbt.unpack_packed([-1], 4, 2), [15, 15])
|
|
|
|
def test_chunk_roundtrip(self):
|
|
_, c = nbt.loads(_chunk_nbt("minecraft:river", 63))
|
|
self.assertEqual(nbt.heightmap(c)[0], 63)
|
|
self.assertEqual(nbt.biome_at(c, 5, 62, 9), "minecraft:river")
|
|
ld = gzip.compress(bytes([nbt.COMPOUND]) + _str("") + _compound(
|
|
_tag(nbt.COMPOUND, "Data", _compound(_tag(nbt.INT, "SpawnX", struct.pack(">i", 7))))))
|
|
self.assertEqual(nbt.load_gzip(ld)[1]["Data"]["SpawnX"], 7)
|
|
|
|
def test_scan_and_pick(self):
|
|
reg = {}
|
|
for lx in range(5):
|
|
for lz in range(5):
|
|
reg[(lx, lz)] = _chunk_nbt("minecraft:river" if lx < 3 else "minecraft:forest", 63)
|
|
reg[(lx + 20, lz)] = _chunk_nbt("minecraft:warm_ocean", 63)
|
|
reg[(lx + 10, lz + 20)] = _chunk_nbt("minecraft:jungle", 80,
|
|
"minecraft:structure_starts")
|
|
with tempfile.TemporaryDirectory() as td:
|
|
zp = Path(td) / "w.zip"
|
|
with zipfile.ZipFile(zp, "w") as z:
|
|
z.writestr("w/level.dat", b"")
|
|
z.writestr("w/region/r.0.0.mca", _region(reg))
|
|
chunks = b14.scan_world(zp)
|
|
self.assertEqual(len(chunks), 50) # structure_starts placeholders skipped
|
|
sites = b14.pick_sites(chunks, avoid=[(-1000, -1000)])
|
|
self.assertEqual(sites["river"]["biome"], "minecraft:river")
|
|
self.assertEqual(sites["river"]["y"], 63)
|
|
self.assertIn("warm_ocean", sites)
|
|
self.assertNotIn("jungle", sites)
|
|
self.assertGreaterEqual(abs(sites["warm_ocean"]["x"] - sites["river"]["x"]), 320)
|
|
# avoid radius excludes everything near the avoid point
|
|
self.assertEqual(b14.pick_sites(chunks, avoid=[(40, 40), (360, 40)]), {})
|
|
|
|
|
|
class Busy(unittest.TestCase):
|
|
def test_ancestors_include_self(self):
|
|
import os
|
|
self.assertIn(os.getpid(), b14._ancestors(os.getpid()))
|
|
self.assertNotIn(os.getpid(), b14.bench_busy())
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|