xref: /trunk/test/aoo-test/src/aootest/pipeline.py (revision 2cb6d6f119bbe2f78742f1c5c8538cb0d4e72ede)
1*2cb6d6f1SJim Jagielski################################################################
2*2cb6d6f1SJim Jagielski#
3*2cb6d6f1SJim Jagielski#  Licensed to the Apache Software Foundation (ASF) under one
4*2cb6d6f1SJim Jagielski#  or more contributor license agreements.  See the NOTICE file
5*2cb6d6f1SJim Jagielski#  distributed with this work for additional information
6*2cb6d6f1SJim Jagielski#  regarding copyright ownership.  The ASF licenses this file
7*2cb6d6f1SJim Jagielski#  to you under the Apache License, Version 2.0 (the
8*2cb6d6f1SJim Jagielski#  "License"); you may not use this file except in compliance
9*2cb6d6f1SJim Jagielski#  with the License.  You may obtain a copy of the License at
10*2cb6d6f1SJim Jagielski#
11*2cb6d6f1SJim Jagielski#    http://www.apache.org/licenses/LICENSE-2.0
12*2cb6d6f1SJim Jagielski#
13*2cb6d6f1SJim Jagielski#  Unless required by applicable law or agreed to in writing,
14*2cb6d6f1SJim Jagielski#  software distributed under the License is distributed on an
15*2cb6d6f1SJim Jagielski#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16*2cb6d6f1SJim Jagielski#  KIND, either express or implied.  See the License for the
17*2cb6d6f1SJim Jagielski#  specific language governing permissions and limitations
18*2cb6d6f1SJim Jagielski#  under the License.
19*2cb6d6f1SJim Jagielski#
20*2cb6d6f1SJim Jagielski################################################################
21*2cb6d6f1SJim Jagielski
22*2cb6d6f1SJim Jagielski"""One save mode over the source documents: convert every document once, then run the checkers once over the output
23*2cb6d6f1SJim Jagielskidirectory (one JVM per mode). Everything is computed on first use and kept."""
24*2cb6d6f1SJim Jagielskifrom pathlib import Path
25*2cb6d6f1SJim Jagielski
26*2cb6d6f1SJim Jagielskifrom aootest import documents, tools
27*2cb6d6f1SJim Jagielskifrom aootest.checkers import crosscheck, doctypecheck, formulacheck, idcheck, pkgcheck, validate
28*2cb6d6f1SJim Jagielskifrom aootest.client import WorkerError, WorkerFailure, WorkerHang
29*2cb6d6f1SJim Jagielski
30*2cb6d6f1SJim JagielskiFILTERS = documents.FILTERS
31*2cb6d6f1SJim Jagielski
32*2cb6d6f1SJim Jagielski
33*2cb6d6f1SJim Jagielskidef by_package(errors):
34*2cb6d6f1SJim Jagielski    """{package: [entries]} for a checker's error list (the first item of an entry is '<package>/<part>')."""
35*2cb6d6f1SJim Jagielski    out = {}
36*2cb6d6f1SJim Jagielski    for e in errors:
37*2cb6d6f1SJim Jagielski        out.setdefault(e[0].split("/", 1)[0], []).append(e)
38*2cb6d6f1SJim Jagielski    return out
39*2cb6d6f1SJim Jagielski
40*2cb6d6f1SJim Jagielski
41*2cb6d6f1SJim Jagielskidef save_one(session, src, dst, timeout=180):
42*2cb6d6f1SJim Jagielski    """Converts `src` to `dst` with the filter for its extension. Returns {"status": ok|FAIL|HANG|CRASH, "detail": str}."""
43*2cb6d6f1SJim Jagielski    try:
44*2cb6d6f1SJim Jagielski        session.call("convert", {"src": src, "dst": dst, "filter": FILTERS[dst.rsplit(".", 1)[1]]}, timeout=timeout)
45*2cb6d6f1SJim Jagielski        return {"status": "ok", "detail": ""}
46*2cb6d6f1SJim Jagielski    except WorkerError as e:
47*2cb6d6f1SJim Jagielski        return {"status": "FAIL", "detail": "%s: %s" % (e.kind, e.message)}
48*2cb6d6f1SJim Jagielski    except WorkerHang as e:
49*2cb6d6f1SJim Jagielski        return {"status": "HANG", "detail": str(e)}
50*2cb6d6f1SJim Jagielski    except WorkerFailure as e:
51*2cb6d6f1SJim Jagielski        return {"status": "CRASH", "detail": str(e)}
52*2cb6d6f1SJim Jagielski
53*2cb6d6f1SJim Jagielski
54*2cb6d6f1SJim Jagielskiclass ModeRun:
55*2cb6d6f1SJim Jagielski    def __init__(self, name, session, workdir, sources, prepared):
56*2cb6d6f1SJim Jagielski        self.name, self.session, self.sources, self.prepared = name, session, sources, prepared
57*2cb6d6f1SJim Jagielski        self.dir = Path(workdir) / name
58*2cb6d6f1SJim Jagielski        self._saved = self._again = self._validation = self._cross = self._checks = None
59*2cb6d6f1SJim Jagielski
60*2cb6d6f1SJim Jagielski    @property
61*2cb6d6f1SJim Jagielski    def outdir(self):
62*2cb6d6f1SJim Jagielski        return self.dir / "out"
63*2cb6d6f1SJim Jagielski
64*2cb6d6f1SJim Jagielski    def saved(self):
65*2cb6d6f1SJim Jagielski        """{source id: {"status", "detail", "out"}}; status is ok, FAIL, HANG or CRASH, and "out" is the saved file when ok."""
66*2cb6d6f1SJim Jagielski        if self._saved is None:
67*2cb6d6f1SJim Jagielski            self.outdir.mkdir(parents=True, exist_ok=True)
68*2cb6d6f1SJim Jagielski            res = {}
69*2cb6d6f1SJim Jagielski            for source in self.sources:
70*2cb6d6f1SJim Jagielski                dst = str(self.outdir / documents.out_name(source))
71*2cb6d6f1SJim Jagielski                try:
72*2cb6d6f1SJim Jagielski                    src = self.prepared.file(source)
73*2cb6d6f1SJim Jagielski                except WorkerFailure as e:
74*2cb6d6f1SJim Jagielski                    status = "FAIL" if isinstance(e, WorkerError) else "HANG" if isinstance(e, WorkerHang) else "CRASH"
75*2cb6d6f1SJim Jagielski                    res[source.id] = {"status": status, "detail": str(e), "out": None}
76*2cb6d6f1SJim Jagielski                    continue
77*2cb6d6f1SJim Jagielski                r = save_one(self.session, src, dst)
78*2cb6d6f1SJim Jagielski                r["out"] = dst if r["status"] == "ok" else None
79*2cb6d6f1SJim Jagielski                res[source.id] = r
80*2cb6d6f1SJim Jagielski            self._saved = res
81*2cb6d6f1SJim Jagielski        return self._saved
82*2cb6d6f1SJim Jagielski
83*2cb6d6f1SJim Jagielski    def saved_again(self):
84*2cb6d6f1SJim Jagielski        """The same conversions as saved(), a second time, into out2/: {source id: {"status", "detail", "out"}}."""
85*2cb6d6f1SJim Jagielski        if self._again is None:
86*2cb6d6f1SJim Jagielski            self.saved()
87*2cb6d6f1SJim Jagielski            out2 = self.dir / "out2"
88*2cb6d6f1SJim Jagielski            out2.mkdir(parents=True, exist_ok=True)
89*2cb6d6f1SJim Jagielski            res = {}
90*2cb6d6f1SJim Jagielski            for source in self.sources:
91*2cb6d6f1SJim Jagielski                if self._saved[source.id]["out"] is None:
92*2cb6d6f1SJim Jagielski                    res[source.id] = {"status": "skipped", "detail": "the first save failed", "out": None}
93*2cb6d6f1SJim Jagielski                    continue
94*2cb6d6f1SJim Jagielski                dst = str(out2 / documents.out_name(source))
95*2cb6d6f1SJim Jagielski                r = save_one(self.session, self.prepared.file(source), dst)
96*2cb6d6f1SJim Jagielski                r["out"] = dst if r["status"] == "ok" else None
97*2cb6d6f1SJim Jagielski                res[source.id] = r
98*2cb6d6f1SJim Jagielski            self._again = res
99*2cb6d6f1SJim Jagielski        return self._again
100*2cb6d6f1SJim Jagielski
101*2cb6d6f1SJim Jagielski    def validation(self):
102*2cb6d6f1SJim Jagielski        """{"val", "pkg", "doctype", "ids", "formulas"}: each checker's result over the output directory; the Java ones are
103*2cb6d6f1SJim Jagielski        None when java, jing or the schemas are missing."""
104*2cb6d6f1SJim Jagielski        if self._validation is None:
105*2cb6d6f1SJim Jagielski            self.saved()
106*2cb6d6f1SJim Jagielski            res = {"ids": idcheck.check(str(self.outdir)), "doctype": doctypecheck.check(str(self.outdir)),
107*2cb6d6f1SJim Jagielski                   "formulas": formulacheck.check(str(self.outdir)), "val": None, "pkg": None}
108*2cb6d6f1SJim Jagielski            jing, schemas = tools.jing_command(), tools.schema_dir()
109*2cb6d6f1SJim Jagielski            if jing and schemas:
110*2cb6d6f1SJim Jagielski                res["val"] = validate.check(str(self.outdir), str(self.dir / "w-validate"), jing, schemas)
111*2cb6d6f1SJim Jagielski                res["pkg"] = pkgcheck.check(str(self.outdir), str(self.dir / "w-pkg"), jing, schemas)
112*2cb6d6f1SJim Jagielski            self._validation = res
113*2cb6d6f1SJim Jagielski        return self._validation
114*2cb6d6f1SJim Jagielski
115*2cb6d6f1SJim Jagielski    def crosscheck(self):
116*2cb6d6f1SJim Jagielski        """The odfvalidator comparison with jing over the output directory (aootest.checkers.crosscheck), or None when java,
117*2cb6d6f1SJim Jagielski        the odfvalidator jar, jing or the schemas are missing."""
118*2cb6d6f1SJim Jagielski        if self._cross is None:
119*2cb6d6f1SJim Jagielski            val = self.validation()["val"]
120*2cb6d6f1SJim Jagielski            jar, java, schemas = tools.odfvalidator(), tools.java(), tools.schema_dir()
121*2cb6d6f1SJim Jagielski            if val is None or not (jar and java and schemas):
122*2cb6d6f1SJim Jagielski                return None
123*2cb6d6f1SJim Jagielski            self._cross = crosscheck.check(str(self.outdir), val, str(self.dir / "w-cross"), jar, str(schemas), java)
124*2cb6d6f1SJim Jagielski        return self._cross
125*2cb6d6f1SJim Jagielski
126*2cb6d6f1SJim Jagielski    def reload_checks(self):
127*2cb6d6f1SJim Jagielski        """{source id: {check name: value} | {"__error__": text}} for the saved copies of the sources that have reload checks."""
128*2cb6d6f1SJim Jagielski        if self._checks is None:
129*2cb6d6f1SJim Jagielski            saved, res = self.saved(), {}
130*2cb6d6f1SJim Jagielski            for source in self.sources:
131*2cb6d6f1SJim Jagielski                if not source.checks or saved[source.id]["out"] is None:
132*2cb6d6f1SJim Jagielski                    continue
133*2cb6d6f1SJim Jagielski                checks = [{"name": c["name"], "expr": c["expr"]} for f in source.checks for c in documents.load_checks(f)]
134*2cb6d6f1SJim Jagielski                try:
135*2cb6d6f1SJim Jagielski                    res[source.id] = self.session.call("roundtrip", {"path": saved[source.id]["out"], "checks": checks}, timeout=300)
136*2cb6d6f1SJim Jagielski                except WorkerFailure as e:
137*2cb6d6f1SJim Jagielski                    res[source.id] = {"__error__": "%s: %s" % (type(e).__name__, e)}
138*2cb6d6f1SJim Jagielski            self._checks = res
139*2cb6d6f1SJim Jagielski        return self._checks
140