xref: /trunk/test/odf-validate/cov_report.py (revision 0e16a31d78a652c56c5c0fc79c752f48b9048df9)
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