1*0e16a31dSJim Jagielski#************************************************************** 2*0e16a31dSJim Jagielski# 3*0e16a31dSJim Jagielski# Licensed to the Apache Software Foundation (ASF) under one 4*0e16a31dSJim Jagielski# or more contributor license agreements. See the NOTICE file 5*0e16a31dSJim Jagielski# distributed with this work for additional information 6*0e16a31dSJim Jagielski# regarding copyright ownership. The ASF licenses this file 7*0e16a31dSJim Jagielski# to you under the Apache License, Version 2.0 (the 8*0e16a31dSJim Jagielski# "License"); you may not use this file except in compliance 9*0e16a31dSJim Jagielski# with the License. You may obtain a copy of the License at 10*0e16a31dSJim Jagielski# 11*0e16a31dSJim Jagielski# http://www.apache.org/licenses/LICENSE-2.0 12*0e16a31dSJim Jagielski# 13*0e16a31dSJim Jagielski# Unless required by applicable law or agreed to in writing, 14*0e16a31dSJim Jagielski# software distributed under the License is distributed on an 15*0e16a31dSJim Jagielski# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16*0e16a31dSJim Jagielski# KIND, either express or implied. See the License for the 17*0e16a31dSJim Jagielski# specific language governing permissions and limitations 18*0e16a31dSJim Jagielski# under the License. 19*0e16a31dSJim Jagielski# 20*0e16a31dSJim Jagielski 21*0e16a31dSJim Jagielski# usage: cov_report.py <task-dir> <corpus-name>... [-- <observed-name>...] 22*0e16a31dSJim Jagielski# Joins the schema universe (cov_schema.py), the names the exporters can emit (cov_source.py) and what a corpus exercises 23*0e16a31dSJim Jagielski# (cov_corpus.py) under <task-dir>/{schema,source,<corpus-name>} and prints the gaps, element by element. 24*0e16a31dSJim Jagielski# Emittable is by name only: the source scan cannot tell which element an attribute is written on, and it misses 25*0e16a31dSJim Jagielski# table-driven sites, so names seen in the observed corpora count as emittable too. 26*0e16a31dSJim Jagielskiimport sys, os, collections 27*0e16a31dSJim Jagielski 28*0e16a31dSJim JagielskiT = sys.argv[1] 29*0e16a31dSJim JagielskiARGS = sys.argv[2:] 30*0e16a31dSJim JagielskiCORPORA, OBSERVED = (ARGS[:ARGS.index("--")], ARGS[ARGS.index("--") + 1:]) if "--" in ARGS else (ARGS, []) 31*0e16a31dSJim Jagielski 32*0e16a31dSJim Jagielski 33*0e16a31dSJim Jagielskidef rows(path, n): 34*0e16a31dSJim Jagielski with open(path) as f: 35*0e16a31dSJim Jagielski return [tuple(l.rstrip("\n").split("\t")[:n]) for l in f if l.strip() and not l.startswith("#")] 36*0e16a31dSJim Jagielski 37*0e16a31dSJim Jagielski 38*0e16a31dSJim Jagielskidef load(sub, name, n): 39*0e16a31dSJim Jagielski return rows(os.path.join(T, sub, name), n) 40*0e16a31dSJim Jagielski 41*0e16a31dSJim Jagielski 42*0e16a31dSJim JagielskiS_EL, S_AT, S_VAL = set(), {}, collections.defaultdict(set) 43*0e16a31dSJim Jagielskifor sub in ("schema", "schema/manifest"): 44*0e16a31dSJim Jagielski S_EL |= set(load(sub, "elements.tsv", 2)) 45*0e16a31dSJim Jagielski S_AT.update({r[:4]: r[4] for r in load(sub, "attributes.tsv", 5)}) 46*0e16a31dSJim Jagielski for r in load(sub, "values.tsv", 5): 47*0e16a31dSJim Jagielski S_VAL[r[:4]].add(r[4]) 48*0e16a31dSJim JagielskiSTATIC = {r[:2] for r in load("source", "emit.tsv", 2)} 49*0e16a31dSJim JagielskiC_EL, C_AT, C_VAL = set(), set(), collections.defaultdict(set) 50*0e16a31dSJim Jagielskifor c in CORPORA: 51*0e16a31dSJim Jagielski C_EL |= {r[:2] for r in load(c, "elements.tsv", 2)} 52*0e16a31dSJim Jagielski C_AT |= {(r[0], r[1], r[2], r[3]) for r in load(c, "attributes.tsv", 4)} 53*0e16a31dSJim Jagielski for r in load(c, "values.tsv", 5): 54*0e16a31dSJim Jagielski C_VAL[r[:4]].add(r[4]) 55*0e16a31dSJim Jagielski 56*0e16a31dSJim Jagielski 57*0e16a31dSJim JagielskiOBS = set() 58*0e16a31dSJim Jagielskifor c in CORPORA + OBSERVED: 59*0e16a31dSJim Jagielski OBS |= {r[:2] for r in load(c, "elements.tsv", 2)} | {r[2:4] for r in load(c, "attributes.tsv", 4)} 60*0e16a31dSJim JagielskiEMIT = STATIC | OBS 61*0e16a31dSJim Jagielski 62*0e16a31dSJim Jagielski 63*0e16a31dSJim Jagielskidef short(ns, loc): 64*0e16a31dSJim Jagielski return ns.rsplit("xmlns:", 1)[-1].replace(":1.0", "") + ":" + loc if "xmlns:" in ns else ns + " " + loc 65*0e16a31dSJim Jagielski 66*0e16a31dSJim Jagielski 67*0e16a31dSJim Jagielskidef block(title, items): 68*0e16a31dSJim Jagielski print("\n== %s: %d" % (title, len(items))) 69*0e16a31dSJim Jagielski for i in sorted(items): 70*0e16a31dSJim Jagielski print(" " + i) 71*0e16a31dSJim Jagielski 72*0e16a31dSJim Jagielski 73*0e16a31dSJim Jagielskiprint("schema: %d elements, %d element/attribute pairs; emit set: %d names; corpora: %s" % 74*0e16a31dSJim Jagielski (len(S_EL), len(S_AT), len(EMIT), ", ".join(CORPORA))) 75*0e16a31dSJim Jagielskiprint("static emit set %d names; observed %d names, of which %d not found by the static scan" % 76*0e16a31dSJim Jagielski (len(STATIC), len(OBS), len(OBS - STATIC))) 77*0e16a31dSJim Jagielskiprint("corpus covers %d of %d schema elements and %d of %d schema pairs" % 78*0e16a31dSJim Jagielski (len(S_EL & C_EL), len(S_EL), len(set(S_AT) & C_AT), len(S_AT))) 79*0e16a31dSJim Jagielski 80*0e16a31dSJim Jagielskimiss_el = S_EL - C_EL 81*0e16a31dSJim Jagielskiblock("elements not exercised, AOO can emit (GAP: generate)", [short(*e) for e in miss_el if e in EMIT]) 82*0e16a31dSJim Jagielskiblock("elements not exercised, no emit site found and never observed (UNKNOWN: names built at run time are missed, e.g. animation, text fields)", [short(*e) for e in miss_el if e not in EMIT]) 83*0e16a31dSJim Jagielski 84*0e16a31dSJim Jagielskimiss_at = {k for k in S_AT if k not in C_AT and k[:2] in C_EL} 85*0e16a31dSJim Jagielskiblock("attributes not exercised on an exercised element, name emitted by AOO (GAP)", 86*0e16a31dSJim Jagielski ["%s @%s" % (short(*k[:2]), short(*k[2:])) for k in miss_at if k[2:] in EMIT]) 87*0e16a31dSJim Jagielskiblock("attributes not exercised on an exercised element, name never emitted", 88*0e16a31dSJim Jagielski ["%s @%s" % (short(*k[:2]), short(*k[2:])) for k in miss_at if k[2:] not in EMIT]) 89*0e16a31dSJim Jagielski 90*0e16a31dSJim Jagielskin_val = sum(len(v) for v in S_VAL.values()) 91*0e16a31dSJim Jagielskihit_val = sum(len(vs & C_VAL[k]) for k, vs in S_VAL.items()) 92*0e16a31dSJim Jagielskiprint("corpus covers %d of %d schema enumerated values" % (hit_val, n_val)) 93*0e16a31dSJim Jagielskimiss_val = [] 94*0e16a31dSJim Jagielskifor k, vs in S_VAL.items(): 95*0e16a31dSJim Jagielski if k in C_AT: 96*0e16a31dSJim Jagielski for v in sorted(vs - C_VAL[k]): 97*0e16a31dSJim Jagielski miss_val.append("%s @%s = %s" % (short(*k[:2]), short(*k[2:]), v)) 98*0e16a31dSJim Jagielskiblock("enumerated values not exercised on an exercised attribute", miss_val) 99*0e16a31dSJim Jagielski 100*0e16a31dSJim Jagielskiemit_unknown = [short(*e) for e in EMIT if e[0].startswith("urn:oasis:names:tc:opendocument:xmlns:") and e not in S_EL 101*0e16a31dSJim Jagielski and not any(k[2:] == e for k in S_AT)] 102*0e16a31dSJim Jagielskiblock("emitted names in ODF namespaces that the schema defines neither as element nor attribute", emit_unknown) 103