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