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