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 22*0e16a31dSJim Jagielski# usage: selftest.py -- validator negative controls; exits 1 if any control misbehaves 23*0e16a31dSJim Jagielskiimport collections, json, os, subprocess, sys 24*0e16a31dSJim JagielskiD = os.path.dirname(os.path.abspath(__file__)) 25*0e16a31dSJim Jagielskival = os.path.join(D, "runs", "control", "val.json") 26*0e16a31dSJim Jagielskios.makedirs(os.path.dirname(val), exist_ok=True) 27*0e16a31dSJim Jagielskisubprocess.run([sys.executable, "-I", os.path.join(D, "make-controls.py")], check=True) 28*0e16a31dSJim Jagielskisubprocess.run([sys.executable, "-I", os.path.join(D, "validate.py"), os.path.join(D, "fixtures", "control"), val], check=True, capture_output=True) 29*0e16a31dSJim Jagielskid = json.load(open(val)) 30*0e16a31dSJim Jagielskidef errs(v, pkg): return [e[3] for e in d["errors"][v] if e[0].startswith(pkg + "/")] 31*0e16a31dSJim Jagielskichecks = { 32*0e16a31dSJim Jagielski "good.odt clean in 1.2 and 1.4": not errs("12", "good.odt") and not errs("14", "good.odt"), 33*0e16a31dSJim Jagielski "bad.odt flags text:bogus in 1.2 and 1.4": all(any('"text:bogus"' in m for m in errs(v, "bad.odt")) for v in ("12", "14")), 34*0e16a31dSJim Jagielski "wrongver.odt flags office:version in 1.2 and 1.4": all(any('office:version' in m for m in errs(v, "wrongver.odt")) for v in ("12", "14")), 35*0e16a31dSJim Jagielski "13: good.odt clean in 1.3 and 1.3 extended": not errs("13", "good.odt") and not errs("13x", "good.odt"), 36*0e16a31dSJim Jagielski "13: bad.odt flags text:bogus in 1.3 and 1.3 extended": all(any('"text:bogus"' in m for m in errs(v, "bad.odt")) for v in ("13", "13x")), 37*0e16a31dSJim Jagielski "13: wrongver.odt flags office:version in 1.3 and 1.3 extended": all(any("office:version" in m for m in errs(v, "wrongver.odt")) for v in ("13", "13x")), 38*0e16a31dSJim Jagielski "13: new13.odt (text:drop-down) invalid in 1.2, valid in 1.3 and 1.3 extended": bool(errs("12", "new13.odt")) and not errs("13", "new13.odt") and not errs("13x", "new13.odt"), 39*0e16a31dSJim Jagielski "13: extforeign.odt invalid as written, valid once foreign content is removed": bool(errs("13", "extforeign.odt")) and not errs("13x", "extforeign.odt"), 40*0e16a31dSJim Jagielski "13: meta:creator-initials invalid in 1.2, valid in 1.3 and 1.3 extended; the note without it is clean in all": any('"meta:creator-initials"' in m for m in errs("12", "newinit.odt")) 41*0e16a31dSJim Jagielski and not errs("13", "newinit.odt") and not errs("13x", "newinit.odt") and not any(errs(v, "baseinit.odt") for v in ("12", "13", "13x")), 42*0e16a31dSJim Jagielski "13: chart:coordinate-region invalid in 1.2, valid in 1.3 and 1.3 extended; the chart without it is clean in all": any('"chart:coordinate-region"' in m for m in errs("12", "newregion.odc")) 43*0e16a31dSJim Jagielski and not errs("13", "newregion.odc") and not errs("13x", "newregion.odc") and not any(errs(v, "baseregion.odc") for v in ("12", "13", "13x")), 44*0e16a31dSJim Jagielski "13: style:scale-to-X/Y together with style:scale-to invalid in 1.3 and 1.3 extended": all(any('"style:scale-to"' in m for m in errs(v, "scaleboth.odt")) for v in ("13", "13x")), 45*0e16a31dSJim Jagielski "13: style:scale-to-X/Y alone and style:scale-to alone valid in 1.3 and 1.3 extended; X/Y invalid in 1.2": not any(errs(v, p) for v in ("13", "13x") for p in ("scalexy.odt", "scaleone.odt")) 46*0e16a31dSJim Jagielski and any('"style:scale-to-X"' in m for m in errs("12", "scalexy.odt")) and not errs("12", "scaleone.odt"), 47*0e16a31dSJim Jagielski "mathobj.odt MathML object skipped": d["mathml_skipped"] == ["mathobj.odt/Object 1/content.xml"] and not errs("12", "mathobj.odt"), 48*0e16a31dSJim Jagielski "no unparsed jing output": not any(e[0] == "?" for v in d["errors"] for e in d["errors"][v]), 49*0e16a31dSJim Jagielski} 50*0e16a31dSJim Jagielskiids = os.path.join(D, "runs", "control", "ids.json") 51*0e16a31dSJim Jagielskisubprocess.run([sys.executable, "-I", os.path.join(D, "idcheck.py"), os.path.join(D, "fixtures", "control"), ids], check=True, capture_output=True) 52*0e16a31dSJim Jagielskii = json.load(open(ids)) 53*0e16a31dSJim Jagielskidef ierrs(pkg): return [e[3] for e in i["errors"] if e[0].startswith(pkg + "/")] 54*0e16a31dSJim Jagielskichecks.update({ 55*0e16a31dSJim Jagielski "idcheck: good.odt, goodid.odt (normalized ids) clean": not ierrs("good.odt") and not ierrs("goodid.odt"), 56*0e16a31dSJim Jagielski "idcheck: dupid.odt flags the duplicate after normalization": any("duplicates" in m for m in ierrs("dupid.odt")), 57*0e16a31dSJim Jagielski "idcheck: badid.odt, badid2.odt flag non-NCName ids": all(any("not an NCName" in m for m in ierrs(p)) for p in ("badid.odt", "badid2.odt")), 58*0e16a31dSJim Jagielski "idcheck: badns.odt flags the unbound prefix": any("unbound prefix" in m for m in ierrs("badns.odt")), 59*0e16a31dSJim Jagielski "idcheck: badsig.odt flags a duplicate id in a signature file": any("duplicates" in m for m in ierrs("badsig.odt")), 60*0e16a31dSJim Jagielski "idcheck: no errors in other packages": {e[0].split("/")[0] for e in i["errors"]} == {"dupid.odt", "badid.odt", "badid2.odt", "badns.odt", "badsig.odt"}, 61*0e16a31dSJim Jagielski}) 62*0e16a31dSJim Jagielskisubprocess.run([sys.executable, "-I", os.path.join(D, "make-pkg-controls.py")], check=True) 63*0e16a31dSJim Jagielskipk = os.path.join(D, "runs", "control", "pkg.json") 64*0e16a31dSJim Jagielskipdir = os.path.join(D, "fixtures", "control-pkg") 65*0e16a31dSJim Jagielskisubprocess.run([sys.executable, "-I", os.path.join(D, "pkgcheck.py"), pdir, pk], check=True, capture_output=True) 66*0e16a31dSJim Jagielskipe = json.load(open(pk))["errors"] 67*0e16a31dSJim Jagielskiexpected = json.load(open(os.path.join(pdir, ".expected.json"))) 68*0e16a31dSJim Jagielskidef found(pkg): return sorted({tuple(m.split("]")[0].split(": [")) for m in (e[3] for e in pe if e[0].startswith(pkg + "/"))}) 69*0e16a31dSJim Jagielskifor pkg in sorted(expected): 70*0e16a31dSJim Jagielski checks["pkgcheck: " + pkg + " reports exactly " + (", ".join(r for _, r in map(tuple, expected[pkg])) or "nothing")] = found(pkg) == [tuple(x) for x in expected[pkg]] 71*0e16a31dSJim Jagielskisubprocess.run([sys.executable, "-I", os.path.join(D, "make-doctype-controls.py")], check=True) 72*0e16a31dSJim Jagielskidt = os.path.join(D, "runs", "control", "dt.json") 73*0e16a31dSJim Jagielskiddir = os.path.join(D, "fixtures", "control-doctype") 74*0e16a31dSJim Jagielskisubprocess.run([sys.executable, "-I", os.path.join(D, "doctypecheck.py"), ddir, dt], check=True, capture_output=True) 75*0e16a31dSJim Jagielskide = json.load(open(dt))["errors"] 76*0e16a31dSJim Jagielskidexpected = json.load(open(os.path.join(ddir, ".expected.json"))) 77*0e16a31dSJim Jagielskidef dfound(doc): return sorted({tuple(m.split("]")[0].split(": [")) for m in (e[3] for e in de if e[0].startswith(doc + "/"))}) 78*0e16a31dSJim Jagielskifor doc in sorted(dexpected): 79*0e16a31dSJim Jagielski checks["doctypecheck: " + doc + " reports exactly " + (", ".join(r for _, r in dexpected[doc]) or "nothing")] = dfound(doc) == [tuple(x) for x in dexpected[doc]] 80*0e16a31dSJim Jagielskiimport importlib.util 81*0e16a31dSJim Jagielskifrom xml.sax.saxutils import quoteattr 82*0e16a31dSJim Jagielskispec = importlib.util.spec_from_file_location("formulacheck", os.path.join(D, "formulacheck.py")) 83*0e16a31dSJim Jagielskifc = importlib.util.module_from_spec(spec) 84*0e16a31dSJim Jagielskispec.loader.exec_module(fc) 85*0e16a31dSJim Jagielskicases = [l.rstrip("\n").split("\t") for l in open(os.path.join(D, "formula-cases.tsv"), encoding="utf-8") if not l.startswith("#") and l.strip()] 86*0e16a31dSJim JagielskiNSDECL = ('xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" ' 87*0e16a31dSJim Jagielski 'xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" ' 88*0e16a31dSJim Jagielski 'xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:ooow="http://openoffice.org/2004/writer"') 89*0e16a31dSJim Jagielskilines = ["<office:document-content %s><office:body>" % NSDECL] 90*0e16a31dSJim Jagielskifor st, attr, val, msg in cases: 91*0e16a31dSJim Jagielski val = val.replace("\\n", "\n").replace("\\u00a0", "\u00a0") 92*0e16a31dSJim Jagielski name, _, ctx = attr.partition("@") 93*0e16a31dSJim Jagielski tag = "office:text" if ctx == "text" else "office:spreadsheet" 94*0e16a31dSJim Jagielski lines.append("<%s><table:table-cell %s=%s/></%s>" % (tag, name, quoteattr(val, {"\n": " "}), tag)) 95*0e16a31dSJim Jagielskilines.append("</office:body></office:document-content>") 96*0e16a31dSJim Jagielskigot = collections.defaultdict(list) 97*0e16a31dSJim Jagielskifor line, col, level, rule, m in fc.check_stream("\n".join(lines).encode("utf-8"), collections.Counter()): 98*0e16a31dSJim Jagielski got[line - 2].append((level + ":" + rule, m)) 99*0e16a31dSJim Jagielskibad_cases = [] 100*0e16a31dSJim Jagielskifor n, (st, attr, val, msg) in enumerate(cases): 101*0e16a31dSJim Jagielski want = [] if st == "ok" else st.split("+") 102*0e16a31dSJim Jagielski if sorted(r for r, _ in got[n]) != sorted(want) or (msg and not any(msg in m for _, m in got[n])): 103*0e16a31dSJim Jagielski bad_cases.append((n, st, attr, val, got[n])) 104*0e16a31dSJim Jagielskifor c in bad_cases[:10]: 105*0e16a31dSJim Jagielski print("CASE", *c) 106*0e16a31dSJim Jagielskichecks["formulacheck: all %d cases in formula-cases.tsv give exactly the expected findings" % len(cases)] = not bad_cases and len(cases) > 100 107*0e16a31dSJim Jagielskichecks.update({ 108*0e16a31dSJim Jagielski "extended: extforeign.odt invalid as written, valid once foreign content is removed": bool(errs("12", "extforeign.odt")) and not errs("12x", "extforeign.odt"), 109*0e16a31dSJim Jagielski "extended: good.odt, goodid.odt clean": not errs("12x", "good.odt") and not errs("12x", "goodid.odt"), 110*0e16a31dSJim Jagielski "extended: bad.odt (unknown element in an ODF namespace) still flagged": any('"text:bogus"' in m for m in errs("12x", "bad.odt")), 111*0e16a31dSJim Jagielski "extended: wrongver.odt (attribute in an ODF namespace) still flagged": any("office:version" in m for m in errs("12x", "wrongver.odt")), 112*0e16a31dSJim Jagielski "extended: badns.odt (unbound prefix) still flagged": any("not bound" in m for m in errs("12x", "badns.odt")), 113*0e16a31dSJim Jagielski}) 114*0e16a31dSJim Jagielskifspec = importlib.util.spec_from_file_location("foreign", os.path.join(D, "foreign.py")) 115*0e16a31dSJim Jagielskifg = importlib.util.module_from_spec(fspec) 116*0e16a31dSJim Jagielskifspec.loader.exec_module(fg) 117*0e16a31dSJim JagielskiMIXED = fg.mixed_elements(os.path.join(D, "schema", "OpenDocument-v1.2-os-schema.rng")) 118*0e16a31dSJim JagielskiHDR = ('<office:text xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" ' 119*0e16a31dSJim Jagielski 'xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:foo="urn:foo" xmlns:dc="http://purl.org/dc/elements/1.1/" ' 120*0e16a31dSJim Jagielski 'xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xf="http://www.w3.org/2002/xforms"') 121*0e16a31dSJim JagielskiFOREIGN_CASES = [ 122*0e16a31dSJim Jagielski ("foreign attribute removed", '<text:p foo:x="1" text:style-name="a">t</text:p>', '<text:p text:style-name="a">t</text:p>'), 123*0e16a31dSJim Jagielski ("foreign element outside a paragraph dropped with its content", '<foo:x>stuff<text:p>q</text:p></foo:x><text:p>t</text:p>', '<text:p>t</text:p>'), 124*0e16a31dSJim Jagielski ("foreign element in a paragraph: tags removed, content kept", '<text:p>a<foo:x>b<text:span>c</text:span></foo:x>d</text:p>', '<text:p>a<text:span>c</text:span>bd</text:p>'.replace("<text:span>c</text:span>bd", "b<text:span>c</text:span>d")), 125*0e16a31dSJim Jagielski ("foreign element in a span in a paragraph: tags removed", '<text:p><text:span>a<foo:x>b</foo:x></text:span></text:p>', '<text:p><text:span>ab</text:span></text:p>'), 126*0e16a31dSJim Jagielski ("foreign element below a frame (no character data): dropped", '<text:p><draw:frame><foo:x>b</foo:x></draw:frame></text:p>', '<text:p><draw:frame></draw:frame></text:p>'), 127*0e16a31dSJim Jagielski ("namespace declared on an unwrapped element is kept for its children", '<text:p><foo:x xmlns:t2="urn:oasis:names:tc:opendocument:xmlns:text:1.0"><t2:span>a</t2:span></foo:x></text:p>', 128*0e16a31dSJim Jagielski '<text:p><t2:span xmlns:t2="urn:oasis:names:tc:opendocument:xmlns:text:1.0">a</t2:span></text:p>'), 129*0e16a31dSJim Jagielski ("elements and attributes of table 4 namespaces kept", '<text:p xlink:href="u"><dc:title>t</dc:title></text:p>', '<text:p xlink:href="u"><dc:title>t</dc:title></text:p>'), 130*0e16a31dSJim Jagielski ("content below a table 4 element kept (wildcard content)", '<xf:model><foo:data><bar/></foo:data></xf:model>', '<xf:model><foo:data><bar></bar></foo:data></xf:model>'), 131*0e16a31dSJim Jagielski ("text is escaped again", '<text:p>a & b < c</text:p>', '<text:p>a & b < c</text:p>'), 132*0e16a31dSJim Jagielski] 133*0e16a31dSJim Jagielskifor name, src, want in FOREIGN_CASES: 134*0e16a31dSJim Jagielski got = fg.strip_foreign(HDR + ">" + src + "</office:text>", MIXED).split("\n", 1)[1].strip() 135*0e16a31dSJim Jagielski inner = got[got.index(">") + 1:-len("</office:text>")] if got.startswith("<office:text") else got 136*0e16a31dSJim Jagielski checks["foreign.py: " + name] = inner == want 137*0e16a31dSJim JagielskiODFV_JAR = os.environ.get("ODFV_ODFVALIDATOR", os.path.join(D, "tools", "odfvalidator-1.1.8-incubating-jar-with-dependencies.jar")) 138*0e16a31dSJim Jagielskiif os.path.isfile(ODFV_JAR): 139*0e16a31dSJim Jagielski cc = os.path.join(D, "runs", "control", "cross.json") 140*0e16a31dSJim Jagielski subprocess.run([sys.executable, "-I", os.path.join(D, "crosscheck.py"), os.path.join(D, "fixtures", "control"), os.path.join(D, "runs", "control", "val.json"), cc], check=True, capture_output=True) 141*0e16a31dSJim Jagielski x = json.load(open(cc)) 142*0e16a31dSJim Jagielski c, e = x["conformance"], x["extended"] 143*0e16a31dSJim Jagielski checks.update({ 144*0e16a31dSJim Jagielski "crosscheck: odfvalidator flags bad.odt in both modes": all("bad.odt/content.xml" in m["odfvalidator_error_streams"] for m in (c, e)), 145*0e16a31dSJim Jagielski "crosscheck: odfvalidator flags extforeign.odt as written but not as an extended document": "extforeign.odt/content.xml" in c["odfvalidator_error_streams"] and "extforeign.odt/content.xml" not in e["odfvalidator_error_streams"], 146*0e16a31dSJim Jagielski "crosscheck: odfvalidator finds the duplicate xml:id that jing -i cannot": "dupid.odt/content.xml" in c["odfvalidator_error_streams"], 147*0e16a31dSJim Jagielski "crosscheck: good.odt clean in odfvalidator": not any("good.odt/" in k for m in (c, e) for k in m["odfvalidator_error_streams"]), 148*0e16a31dSJim Jagielski "crosscheck: no stream where only jing reports errors": not c["only_jing"] and not e["only_jing"], 149*0e16a31dSJim Jagielski }) 150*0e16a31dSJim Jagielskielse: 151*0e16a31dSJim Jagielski print("SKIP crosscheck controls: odfvalidator not installed (fetch-odfvalidator.sh)") 152*0e16a31dSJim Jagielskiimport tempfile, time 153*0e16a31dSJim Jagielskigspec = importlib.util.spec_from_file_location("gate", os.path.join(D, "gate.py")) 154*0e16a31dSJim Jagielskigt = importlib.util.module_from_spec(gspec) 155*0e16a31dSJim Jagielskigspec.loader.exec_module(gt) 156*0e16a31dSJim Jagielskivol = gt.read_volatile() 157*0e16a31dSJim Jagielskichecks["gate: gate-volatile.tsv lists the 12 documents with a reason"] = len(vol) == 12 and all(len(r) > 10 for r in vol.values()) 158*0e16a31dSJim Jagielskichecks["gate: rewrite_jobs keeps sources and moves outputs"] = gt.rewrite_jobs([("/a/b.odt", "/x/0001_b.odt")], "/new") == [("/a/b.odt", "/new/0001_b.odt")] 159*0e16a31dSJim Jagielskifake = {"0067_AddInChart.ods": {"ndiff": 1}, "0001_x.odt": {"ndiff": 0}, "0002_y.odt": {"ndiff": 3}} 160*0e16a31dSJim Jagielskichecks["gate: classify separates volatile from unexplained differences"] = gt.classify(fake, vol) == (1, ["0067_AddInChart.ods"], ["0002_y.odt"]) 161*0e16a31dSJim Jagielskigtmp = tempfile.mkdtemp() 162*0e16a31dSJim Jagielskistub = os.path.join(gtmp, "stub.py") 163*0e16a31dSJim Jagielskiopen(stub, "w").write("import sys,os,time,json\ndest=sys.argv[1]\nfor f in sys.argv[2:]:\n d=os.path.join(dest,os.path.basename(f)+'.json')\n" 164*0e16a31dSJim Jagielski " if os.path.exists(d): continue\n open(os.path.join(dest,'.current'),'w').write(f)\n time.sleep(600 if 'hang' in f else 0.6)\n json.dump({'type':'x'},open(d,'w'))\n") 165*0e16a31dSJim Jagielskigsrc = os.path.join(gtmp, "src") 166*0e16a31dSJim Jagielskios.makedirs(gsrc) 167*0e16a31dSJim Jagielskifor n in ("a", "b", "hang", "c"): 168*0e16a31dSJim Jagielski open(os.path.join(gsrc, n), "w").write("x") 169*0e16a31dSJim Jagielskimk = lambda port, dest, files: [sys.executable, stub, dest] + files 170*0e16a31dSJim Jagielskir1 = gt.dump_dir(gsrc, os.path.join(gtmp, "d1"), "p", 0, 2, os.path.join(gtmp, "l1"), tool=mk, poll=0.3) 171*0e16a31dSJim Jagielskihang = json.load(open(os.path.join(gtmp, "d1", "hang.json"))) 172*0e16a31dSJim Jagielskichecks["gate: a stalled document gets one HANG marker, the others are dumped"] = r1 == {"documents": 4, "stalls": 1} and hang["error"].startswith("HANG") \ 173*0e16a31dSJim Jagielski and all("error" not in json.load(open(os.path.join(gtmp, "d1", n + ".json"))) for n in ("a", "b", "c")) 174*0e16a31dSJim Jagielskios.remove(os.path.join(gsrc, "hang")) 175*0e16a31dSJim Jagielskifor n in ("d", "e", "f"): 176*0e16a31dSJim Jagielski open(os.path.join(gsrc, n), "w").write("x") 177*0e16a31dSJim Jagielskit0 = time.time() 178*0e16a31dSJim Jagielskir2 = gt.dump_dir(gsrc, os.path.join(gtmp, "d2"), "p", 0, 2, os.path.join(gtmp, "l2"), tool=mk, poll=0.3) 179*0e16a31dSJim Jagielskichecks["gate: a batch longer than the stall limit that keeps progressing is not killed"] = r2 == {"documents": 6, "stalls": 0} and time.time() - t0 > 2 180*0e16a31dSJim Jagielskistub2 = os.path.join(gtmp, "stub2.py") # a document that "kills the office": it and the rest get a bridge error, once 181*0e16a31dSJim Jagielskiopen(stub2, "w").write("import sys,os,json\ndest=sys.argv[1]\nonce=os.path.join(dest,'.once')\nbad=False\nfor f in sys.argv[2:]:\n d=os.path.join(dest,os.path.basename(f)+'.json')\n" 182*0e16a31dSJim Jagielski " if os.path.exists(d): continue\n if os.path.basename(f)=='bad' and not os.path.exists(once): open(once,'w').write('x'); bad=True\n" 183*0e16a31dSJim Jagielski " json.dump({'error':'Binary URP bridge already disposed'} if bad else {'type':'x'},open(d,'w'))\n") 184*0e16a31dSJim Jagielskistub3 = os.path.join(gtmp, "stub3.py") # the same document always kills the office 185*0e16a31dSJim Jagielskiopen(stub3, "w").write("import sys,os,json\ndest=sys.argv[1]\nbad=False\nfor f in sys.argv[2:]:\n d=os.path.join(dest,os.path.basename(f)+'.json')\n" 186*0e16a31dSJim Jagielski " if os.path.exists(d): continue\n bad = bad or os.path.basename(f)=='bad'\n" 187*0e16a31dSJim Jagielski " json.dump({'error':'Binary URP bridge already disposed'} if bad else {'type':'x'},open(d,'w'))\n") 188*0e16a31dSJim Jagielskigsrc2 = os.path.join(gtmp, "src2") 189*0e16a31dSJim Jagielskios.makedirs(gsrc2) 190*0e16a31dSJim Jagielskifor n in ("a", "bad", "c", "d"): 191*0e16a31dSJim Jagielski open(os.path.join(gsrc2, n), "w").write("x") 192*0e16a31dSJim Jagielskimk2 = lambda port, dest, files: [sys.executable, stub2, dest] + files 193*0e16a31dSJim Jagielskimk3 = lambda port, dest, files: [sys.executable, stub3, dest] + files 194*0e16a31dSJim Jagielskir3 = gt.dump_dir(gsrc2, os.path.join(gtmp, "d3"), "p", 0, 5, os.path.join(gtmp, "l3"), tool=mk2, poll=0.3) 195*0e16a31dSJim Jagielskichecks["gate: dumps that only say 'bridge disposed' are redone and counted"] = r3 == {"documents": 4, "stalls": 0, "bridge_retried": 3} \ 196*0e16a31dSJim Jagielski and not any(gt.bridge_dead(os.path.join(gtmp, "d3", n + ".json")) for n in ("a", "bad", "c", "d")) 197*0e16a31dSJim Jagielskir4 = gt.dump_dir(gsrc2, os.path.join(gtmp, "d4"), "p", 0, 5, os.path.join(gtmp, "l4"), tool=mk3, poll=0.3) 198*0e16a31dSJim Jagielskichecks["gate: a document that always kills the office stays an error after the retries (negative control)"] = r4["documents"] == 4 \ 199*0e16a31dSJim Jagielski and r4["bridge_retried"] == 9 and gt.bridge_dead(os.path.join(gtmp, "d4", "bad.json")) and not gt.bridge_dead(os.path.join(gtmp, "d4", "a.json")) 200*0e16a31dSJim Jagielskifor k, ok in checks.items(): 201*0e16a31dSJim Jagielski print(("PASS " if ok else "FAIL ") + k) 202*0e16a31dSJim Jagielskisys.exit(0 if all(checks.values()) else 1) 203