Conduit is a simple, fast and reliable chat server powered by Matrix https://conduit.rs
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
260 lines
9.0 KiB
260 lines
9.0 KiB
#!/usr/bin/env python3 |
|
|
|
from __future__ import division |
|
import argparse |
|
import re |
|
import sys |
|
|
|
# Usage: $ ./are-we-synapse-yet.py [-v] results.tap |
|
# This script scans a results.tap file from Dendrite's CI process and spits out |
|
# a rating of how close we are to Synapse parity, based purely on SyTests. |
|
# The main complexity is grouping tests sensibly into features like 'Registration' |
|
# and 'Federation'. Then it just checks the ones which are passing and calculates |
|
# percentages for each group. Produces results like: |
|
# |
|
# Client-Server APIs: 29% (196/666 tests) |
|
# ------------------- |
|
# Registration : 62% (20/32 tests) |
|
# Login : 7% (1/15 tests) |
|
# V1 CS APIs : 10% (3/30 tests) |
|
# ... |
|
# |
|
# or in verbose mode: |
|
# |
|
# Client-Server APIs: 29% (196/666 tests) |
|
# ------------------- |
|
# Registration : 62% (20/32 tests) |
|
# ✓ GET /register yields a set of flows |
|
# ✓ POST /register can create a user |
|
# ✓ POST /register downcases capitals in usernames |
|
# ... |
|
# |
|
# You can also tack `-v` on to see exactly which tests each category falls under. |
|
|
|
test_mappings = { |
|
"nsp": "Non-Spec API", |
|
"f": "Federation", # flag to mark test involves federation |
|
|
|
"federation_apis": { |
|
"fky": "Key API", |
|
"fsj": "send_join API", |
|
"fmj": "make_join API", |
|
"fsl": "send_leave API", |
|
"fiv": "Invite API", |
|
"fqu": "Query API", |
|
"frv": "room versions", |
|
"fau": "Auth", |
|
"fbk": "Backfill API", |
|
"fme": "get_missing_events API", |
|
"fst": "State APIs", |
|
"fpb": "Public Room API", |
|
"fdk": "Device Key APIs", |
|
"fed": "Federation API", |
|
}, |
|
|
|
"client_apis": { |
|
"reg": "Registration", |
|
"log": "Login", |
|
"lox": "Logout", |
|
"v1s": "V1 CS APIs", |
|
"csa": "Misc CS APIs", |
|
"pro": "Profile", |
|
"dev": "Devices", |
|
"dvk": "Device Keys", |
|
"pre": "Presence", |
|
"crm": "Create Room", |
|
"syn": "Sync API", |
|
"rmv": "Room Versions", |
|
"rst": "Room State APIs", |
|
"pub": "Public Room APIs", |
|
"mem": "Room Membership", |
|
"ali": "Room Aliases", |
|
"jon": "Joining Rooms", |
|
"lev": "Leaving Rooms", |
|
"inv": "Inviting users to Rooms", |
|
"ban": "Banning users", |
|
"snd": "Sending events", |
|
"get": "Getting events for Rooms", |
|
"rct": "Receipts", |
|
"red": "Read markers", |
|
"med": "Media APIs", |
|
"cap": "Capabilities API", |
|
"typ": "Typing API", |
|
"psh": "Push APIs", |
|
"acc": "Account APIs", |
|
"eph": "Ephemeral Events", |
|
"plv": "Power Levels", |
|
"xxx": "Redaction", |
|
"3pd": "Third-Party ID APIs", |
|
"gst": "Guest APIs", |
|
"ath": "Room Auth", |
|
"fgt": "Forget APIs", |
|
"ctx": "Context APIs", |
|
"upg": "Room Upgrade APIs", |
|
"tag": "Tagging APIs", |
|
"sch": "Search APIs", |
|
"oid": "OpenID API", |
|
"std": "Send-to-Device APIs", |
|
"adm": "Server Admin API", |
|
"ign": "Ignore Users", |
|
"udr": "User Directory APIs", |
|
"app": "Application Services API", |
|
}, |
|
} |
|
|
|
# optional 'not ' with test number then anything but '#' |
|
re_testname = re.compile(r"^(not )?ok [0-9]+ ([^#]+)") |
|
|
|
# Parses lines like the following: |
|
# |
|
# SUCCESS: ok 3 POST /register downcases capitals in usernames |
|
# FAIL: not ok 54 (expected fail) POST /createRoom creates a room with the given version |
|
# SKIP: ok 821 Multiple calls to /sync should not cause 500 errors # skip lack of can_post_room_receipts |
|
# EXPECT FAIL: not ok 822 (expected fail) Guest user can call /events on another world_readable room (SYN-606) # TODO expected fail |
|
# |
|
# Only SUCCESS lines are treated as success, the rest are not implemented. |
|
# |
|
# Returns a dict like: |
|
# { name: "...", ok: True } |
|
def parse_test_line(line): |
|
if not line.startswith("ok ") and not line.startswith("not ok "): |
|
return |
|
re_match = re_testname.match(line) |
|
test_name = re_match.groups()[1].replace("(expected fail) ", "").strip() |
|
test_pass = False |
|
if line.startswith("ok ") and not "# skip " in line: |
|
test_pass = True |
|
return { |
|
"name": test_name, |
|
"ok": test_pass, |
|
} |
|
|
|
# Prints the stats for a complete section. |
|
# header_name => "Client-Server APIs" |
|
# gid_to_tests => { gid: { <name>: True|False }} |
|
# gid_to_name => { gid: "Group Name" } |
|
# verbose => True|False |
|
# Produces: |
|
# Client-Server APIs: 29% (196/666 tests) |
|
# ------------------- |
|
# Registration : 62% (20/32 tests) |
|
# Login : 7% (1/15 tests) |
|
# V1 CS APIs : 10% (3/30 tests) |
|
# ... |
|
# or in verbose mode: |
|
# Client-Server APIs: 29% (196/666 tests) |
|
# ------------------- |
|
# Registration : 62% (20/32 tests) |
|
# ✓ GET /register yields a set of flows |
|
# ✓ POST /register can create a user |
|
# ✓ POST /register downcases capitals in usernames |
|
# ... |
|
def print_stats(header_name, gid_to_tests, gid_to_name, verbose): |
|
subsections = [] # Registration: 100% (13/13 tests) |
|
subsection_test_names = {} # 'subsection name': ["✓ Test 1", "✓ Test 2", "× Test 3"] |
|
total_passing = 0 |
|
total_tests = 0 |
|
for gid, tests in gid_to_tests.items(): |
|
group_total = len(tests) |
|
group_passing = 0 |
|
test_names_and_marks = [] |
|
for name, passing in tests.items(): |
|
if passing: |
|
group_passing += 1 |
|
test_names_and_marks.append(f"{'✓' if passing else '×'} {name}") |
|
|
|
total_tests += group_total |
|
total_passing += group_passing |
|
pct = "{0:.0f}%".format(group_passing/group_total * 100) |
|
line = "%s: %s (%d/%d tests)" % (gid_to_name[gid].ljust(25, ' '), pct.rjust(4, ' '), group_passing, group_total) |
|
subsections.append(line) |
|
subsection_test_names[line] = test_names_and_marks |
|
|
|
pct = "{0:.0f}%".format(total_passing/total_tests * 100) |
|
print("%s: %s (%d/%d tests)" % (header_name, pct, total_passing, total_tests)) |
|
print("-" * (len(header_name)+1)) |
|
for line in subsections: |
|
print(" %s" % (line,)) |
|
if verbose: |
|
for test_name_and_pass_mark in subsection_test_names[line]: |
|
print(" %s" % (test_name_and_pass_mark,)) |
|
print("") |
|
print("") |
|
|
|
def main(results_tap_path, verbose): |
|
# Load up test mappings |
|
test_name_to_group_id = {} |
|
fed_tests = set() |
|
client_tests = set() |
|
groupless_tests = set() |
|
with open("./are-we-synapse-yet.list", "r") as f: |
|
for line in f.readlines(): |
|
test_name = " ".join(line.split(" ")[1:]).strip() |
|
groups = line.split(" ")[0].split(",") |
|
for gid in groups: |
|
if gid == "f" or gid in test_mappings["federation_apis"]: |
|
fed_tests.add(test_name) |
|
else: |
|
client_tests.add(test_name) |
|
if gid == "f": |
|
continue # we expect another group ID |
|
test_name_to_group_id[test_name] = gid |
|
|
|
# parse results.tap |
|
summary = { |
|
"client": { |
|
# gid: { |
|
# test_name: OK |
|
# } |
|
}, |
|
"federation": { |
|
# gid: { |
|
# test_name: OK |
|
# } |
|
}, |
|
"nonspec": { |
|
"nsp": {} |
|
}, |
|
} |
|
with open(results_tap_path, "r") as f: |
|
for line in f.readlines(): |
|
test_result = parse_test_line(line) |
|
if not test_result: |
|
continue |
|
name = test_result["name"] |
|
group_id = test_name_to_group_id.get(name) |
|
if not group_id: |
|
groupless_tests.add(name) |
|
# raise Exception("The test '%s' doesn't have a group" % (name,)) |
|
if group_id == "nsp": |
|
summary["nonspec"]["nsp"][name] = test_result["ok"] |
|
elif group_id in test_mappings["federation_apis"]: |
|
group = summary["federation"].get(group_id, {}) |
|
group[name] = test_result["ok"] |
|
summary["federation"][group_id] = group |
|
elif group_id in test_mappings["client_apis"]: |
|
group = summary["client"].get(group_id, {}) |
|
group[name] = test_result["ok"] |
|
summary["client"][group_id] = group |
|
|
|
print("Are We Synapse Yet?") |
|
print("===================") |
|
print("") |
|
print_stats("Non-Spec APIs", summary["nonspec"], test_mappings, verbose) |
|
print_stats("Client-Server APIs", summary["client"], test_mappings["client_apis"], verbose) |
|
print_stats("Federation APIs", summary["federation"], test_mappings["federation_apis"], verbose) |
|
if verbose: |
|
print("The following tests don't have a group:") |
|
for name in groupless_tests: |
|
print(" %s" % (name,)) |
|
else: |
|
print("%d tests don't have a group" % len(groupless_tests)) |
|
|
|
|
|
|
|
if __name__ == '__main__': |
|
parser = argparse.ArgumentParser() |
|
parser.add_argument("tap_file", help="path to results.tap") |
|
parser.add_argument("-v", action="store_true", help="show individual test names in output") |
|
args = parser.parse_args() |
|
main(args.tap_file, args.v)
|
|
|