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