diff --git a/tools/pasture/town/gen.py b/tools/pasture/town/gen.py index 45dc786..178db78 100644 --- a/tools/pasture/town/gen.py +++ b/tools/pasture/town/gen.py @@ -192,7 +192,7 @@ class Planner: # circles: members are local siblings' neighbours and remote followers of the owner for ref, grp in sorted(self.groups.items()): if grp["kind"] == "circle": - pool = sorted(k for k in micro if k != grp["owner"] and k.split("/")[0] in ("privapub", "gts", "mastodon", "akkoma")) + pool = sorted(k for k in micro if k != grp["owner"] and k.split("/")[0] in ("privapub", "gts", "mastodon", "akkoma", "hollo")) for member in rng.sample(pool, min(len(pool), self.spec.get("circleMembers", 4))): grp["members"].add(member) self.step("graph", member, "join", {"group": ref}) diff --git a/tools/pasture/town/report.py b/tools/pasture/town/report.py index 4b251e1..2ac123e 100644 --- a/tools/pasture/town/report.py +++ b/tools/pasture/town/report.py @@ -12,7 +12,7 @@ import gen DIRS = ["in", "out", "via", "control", "local"] DIR_LABEL = {"in": "into PrivaPub", "out": "from PrivaPub", "via": "through PrivaPub", "control": "peer to peer", "local": "on its own server"} -GROUPS = [("deliver", "Delivery"), ("confine", "Confinement"), ("see", "Seen by the audience"), +GROUPS = [("client", "decePub"), ("deliver", "Delivery"), ("confine", "Confinement"), ("see", "Seen by the audience"), ("hide", "Hidden from outsiders"), ("count", "Counts"), ("thread", "Threads"), ("edit", "Edits"), ("delete", "Deletes"), ("graph", "Follows"), ("privacy", "Privacy")] STATUS_ORDER = ["fail", "xpass", "xfail", "pass"] @@ -84,7 +84,7 @@ def render(result, scenarios, title): cells = result["cells"] totals = Counter(result["totals"]) observers = [p for p in gen.HOSTS if any(k.split("|")[2] == p for k in cells)] + \ - (["*"] if any(k.split("|")[2] == "*" for k in cells) else []) + [o for o in ("*", "decepub") if any(k.split("|")[2] == o for k in cells)] features = sorted({k.split("|")[0] for k in cells}) out = [f"{html.escape(title)}", '', @@ -93,7 +93,7 @@ def render(result, scenarios, title): f"

Every object the town's accounts made, checked on every server it should and should not have reached, " f"through each server's database and API. A cell's squares are the directions it was checked in: " f"into PrivaPub, from PrivaPub, through PrivaPub, peer to peer, and on the author's own server.

" - f"
{html.escape(result['run'])} · checked {html.escape(result['when'])}
") + f"
{html.escape(os.path.basename(result['run']))} · checked {html.escape(result['when'])}
") out.append('
' + "".join( f'
{totals.get(s, 0)}{s}
' for s in STATUS_ORDER) + "
") out.append('
pass fail' @@ -149,6 +149,22 @@ def main(args): if os.path.exists(path): with open(path) as f: scenarios = [json.loads(line) for line in f if line.strip()] + # decePub's e2e cells (tests/e2e) join the servers' in the same matrix, under the observer "decepub" + client = os.path.join(seed.OUT, "client.jsonl") + if os.path.exists(client): + import check + with open(client) as f: + lines = [json.loads(line) for line in f if line.strip()] + latest = {} + for c in lines: + latest[(c["cell"], c.get("ref"))] = c # the latest result of each test + checks = check.classify(list(latest.values()), check.load_gaps()) + for cell, v in check.summarise(checks).items(): + result["cells"][cell] = v + from collections import Counter + totals = Counter(result["totals"]) + totals.update(Counter(c["status"] for c in checks)) + result["totals"] = totals page = render(result, scenarios, "PrivaPub Town Matrix") target = os.path.join(run_dir, "report.html") with open(target, "w") as f: diff --git a/tools/pasture/town/seed.py b/tools/pasture/town/seed.py index fc0d0bd..0183e39 100644 --- a/tools/pasture/town/seed.py +++ b/tools/pasture/town/seed.py @@ -203,10 +203,11 @@ class Seeder: def _wait_pending(self, s): platform = s["actor"].split("/")[0] d, sess, follower = driver(platform), self.session(s["actor"]), self.acct(s["args"]["target"]) - for _ in range(30): + # a refused first delivery is retried on the sender's backoff (16 s, then 31 s): wait out two of them + for _ in range(45): if follower.lower() in (p.lower() for p in d.pending(sess)): return d, sess, follower - time.sleep(1) + time.sleep(2) raise TimeoutError(f"{follower}'s follow request never reached {s['actor']}") def v_accept(self, s): diff --git a/tools/pasture/town/specs/hollo-pair.json b/tools/pasture/town/specs/hollo-pair.json new file mode 100644 index 0000000..791c6f2 --- /dev/null +++ b/tools/pasture/town/specs/hollo-pair.json @@ -0,0 +1,22 @@ +{ + "schema": "pasture-town/1", + "name": "hollo-pair", + "seed": 20261005, + "peers": { + "privapub": {"roots": 1, "personas": [2, 2], "circles": 1, "communities": 0}, + "hollo": {"accounts": 3} + }, + "profiles": {"locked": 0.2, "bot": 0.0, "fields": [0, 2], "avatar": 0.9, "header": 0.3, "bioWords": [5, 12], + "langs": {"en": 80, "it": 20}}, + "graph": {"follows": [2, 4], "mutual": 0.6, "pending": 0.0, "rejected": 0.0}, + "circleMembers": 2, + "content": { + "posts": 24, + "mix": {"text": 40, "cw": 10, "tags": 10, "mention": 10, "image": 10, "poll": 10, "quote": 10}, + "visibility": {"public": 50, "unlisted": 10, "followers": 20, "direct": 10, "circle": 10}, + "replyRounds": 2, "replies": 0.5, "deeperReplies": 0.4 + }, + "interactions": {"likes": [0, 3], "boosts": [0, 1], "react": 0.4, "vote": 0.8, "bookmark": 0.1}, + "mutations": {"edit": 0.15, "delete": 0.05, "blocks": 0, "mutes": 1, "reports": 0}, + "time": {"roundSettleSeconds": 15, "graphSettleSeconds": 20, "settleSeconds": 30, "deadlineSeconds": 120} +} diff --git a/tools/pasture/town/town.py b/tools/pasture/town/town.py index 7bc3292..016d4ff 100644 --- a/tools/pasture/town/town.py +++ b/tools/pasture/town/town.py @@ -41,7 +41,7 @@ def main(argv): print(json.dumps(made.__dict__)) else: result = getattr(d, verb)(s, **params) - print(json.dumps(result if not hasattr(result, "__dict__") else result.__dict__, default=str)) + print(json.dumps(result if not hasattr(result, "__dict__") else result.__dict__, default=str, ensure_ascii=False)) return 0 if cmd == "stored": rows = driver(args[0]).stored(args[1:])