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 22"""Shared pytest configuration for the AOO test suite.""" 23 24import os 25 26import pytest 27 28from aootest import documents, expect, install, pipeline, session, twins 29from aootest.odfversion import SAVE_MODES, odf_versions 30 31 32def pytest_addoption(parser): 33 g = parser.getgroup("aoo", "AOO test suite") 34 g.addoption("--office", default=None, help="an AOO 5+ install: .app, install root, program dir or soffice (default: $AOO_OFFICE)") 35 g.addoption("--uno-python", default=None, help="Python that can import uno (default: $AOO_UNO_PYTHON, else probed)") 36 g.addoption("--connect", choices=("socket", "pipe"), default="socket", help="how the worker reaches the office") 37 g.addoption("--start-timeout", type=float, default=240.0, help="seconds an office may take to start and accept a connection (default %(default)s)") 38 g.addoption("--max-restarts", type=int, default=5, help="office restarts allowed per mode before the session aborts") 39 g.addoption("--require-office", action="store_true", help="fail instead of skipping when no usable office is found") 40 g.addoption("--documents", default=None, help="a directory of ODF documents to run through every save mode, in addition to the ones the office ships") 41 g.addoption("--shipped-limit", type=int, default=documents.DEFAULT_LIMIT, help="how many of the templates and documents shipped with the office to use (default %(default)s)") 42 g.addoption("--twin-corpus", default=None, help="a directory with v12, v13, v12ext and v13ext subdirectories holding the same documents saved as ODF 1.2 and 1.3 by another producer (default: $AOO_TWIN_CORPUS)") 43 g.addoption("--strict-expect", action="store_true", help="an item that a data/expect row expects to fail must fail (XPASS becomes a failure)") 44 g.addoption("--write-kinds", action="store_true", help="write data/baseline/ from this run instead of comparing with it (tests/odf/test_kinds.py)") 45 g.addoption("--require-odf13", action="store_true", help="fail instead of skipping the ODF 1.3 save modes when the office cannot write 1.3") 46 g.addoption("--require-odf14", action="store_true", help="fail instead of skipping the ODF 1.4 save modes when the office cannot write 1.4") 47 48 49def _unavailable(config, message): 50 if config.getoption("--require-office"): 51 pytest.fail(message, pytrace=False) 52 pytest.skip(message) 53 54 55@pytest.fixture(scope="session") 56def aoo_layout(pytestconfig): 57 """The install named by --office or AOO_OFFICE. There is no search of default places: an office is only started on request.""" 58 location = office_location(pytestconfig) 59 if not location: 60 _unavailable(pytestconfig, "set AOO_OFFICE or --office to an installed AOO 5 to run office tests") 61 try: 62 return install.discover(location) 63 except install.LayoutError as e: 64 _unavailable(pytestconfig, "no usable office at %s: %s" % (location, e)) 65 66 67@pytest.fixture(scope="session") 68def aoo_python(pytestconfig, aoo_layout, tmp_path_factory): 69 try: 70 return install.find_uno_python(aoo_layout, explicit=pytestconfig.getoption("--uno-python"), 71 cache_file=tmp_path_factory.getbasetemp() / "uno-python.json") 72 except install.NoUnoPython as e: 73 _unavailable(pytestconfig, "no UNO Python for this install: %s; tried %s" % (e, e.tried)) 74 75 76class Offices: 77 """One lazily started office and worker per ODF mode (None = product default, or a Save/ODF/DefaultVersion number).""" 78 79 def __init__(self, layout, python, workdir, connect, max_restarts, start_timeout=240.0): 80 self.layout, self.python, self.workdir, self.connect, self.max_restarts = layout, python, workdir, connect, max_restarts 81 self.start_timeout = start_timeout 82 self.sessions = {} 83 84 def get(self, mode=None, extra=None, tag=None): 85 """The session for `mode`; `extra` (other Save/ODF settings) needs a `tag` that names it.""" 86 key = (mode, tag) 87 if key not in self.sessions: 88 self.sessions[key] = session.ModeSession(self.layout, self.python, mode, self.workdir, self.connect, self.max_restarts, 89 connect_timeout=self.start_timeout, extra=extra, tag=tag) 90 return self.sessions[key] 91 92 def close(self): 93 for s in self.sessions.values(): 94 s.close() 95 left = [(s.name, o.pid) for s in self.sessions.values() for o in s.started if o.alive()] 96 assert not left, "offices still running after the session: %s" % left 97 98 99@pytest.fixture(scope="session") 100def aoo_offices(pytestconfig, aoo_layout, aoo_python, tmp_path_factory): 101 offices = Offices(aoo_layout, aoo_python, tmp_path_factory.mktemp("aoo"), pytestconfig.getoption("--connect"), 102 pytestconfig.getoption("--max-restarts"), pytestconfig.getoption("--start-timeout")) 103 yield offices 104 offices.close() 105 106 107def office_location(config): 108 return config.getoption("--office") or os.environ.get("AOO_OFFICE") 109 110 111@pytest.fixture(scope="session") 112def odf_runs(pytestconfig, aoo_offices, tmp_path_factory): 113 """{mode name: ModeRun}, created on first use: `odf_runs("strict")`.""" 114 work = tmp_path_factory.mktemp("odf") 115 runs = {} 116 sources = documents.from_config(pytestconfig) 117 # the sources are made in ODF 1.2 extended mode, which keeps every feature the reload checks look for 118 prepared = documents.Prepared(aoo_offices.get(SAVE_MODES["ext"]), work / "src", 119 lambda builder: aoo_offices.get(SAVE_MODES["ext"], tag="build-" + builder)) 120 121 def get(name): 122 if name not in runs: 123 runs[name] = pipeline.ModeRun(name, aoo_offices.get(SAVE_MODES[name]), work, sources, prepared) 124 return runs[name] 125 return get 126 127 128@pytest.fixture(scope="session") 129def twin_run(pytestconfig, aoo_offices, tmp_path_factory): 130 """The TwinRun over the corpus named by --twin-corpus or AOO_TWIN_CORPUS; skips when there is none.""" 131 root = pytestconfig.getoption("--twin-corpus") or os.environ.get("AOO_TWIN_CORPUS") 132 if not root: 133 pytest.skip("set AOO_TWIN_CORPUS or --twin-corpus to a corpus of ODF 1.2 and 1.3 twins to run the import twin tests") 134 problem = twins.corpus_problem(root) 135 if problem: 136 pytest.fail("twin corpus: " + problem, pytrace=False) 137 return twins.TwinRun(aoo_offices.get(None), root, tmp_path_factory.mktemp("twins")) 138 139 140_EXPECT_ROWS = [] 141 142 143def pytest_collection_modifyitems(config, items): 144 rows = _EXPECT_ROWS[:] or expect.load() 145 _EXPECT_ROWS[:] = rows 146 strict = config.getoption("--strict-expect") 147 for item in items: 148 row = expect.match(rows, item.name) 149 if row is None: 150 continue 151 row.used += 1 152 if row.kind in ("xfail", "flaky"): 153 item.add_marker(pytest.mark.xfail(reason=row.reason, strict=strict and row.kind == "xfail")) 154 else: 155 item.add_marker(pytest.mark.skip(reason=row.reason)) 156 157 158@pytest.fixture(scope="session") 159def odf13(aoo_offices, tmp_path_factory): 160 """Whether this build writes ODF 1.3 in save mode 5: builds from before the ODF 1.3 work ignore the setting.""" 161 dst = tmp_path_factory.mktemp("odf13") / "probe.odt" 162 aoo_offices.get(5).call("new_and_store", {"app": "writer", "dst": str(dst), "filter": "writer8"}, timeout=120) 163 return odf_versions(dst) == ("1.3", "1.3") 164 165 166@pytest.fixture(scope="session") 167def odf14(aoo_offices, tmp_path_factory): 168 """Whether this build writes ODF 1.4 in save mode 7: builds from before the ODF 1.4 work ignore the setting.""" 169 dst = tmp_path_factory.mktemp("odf14") / "probe.odt" 170 aoo_offices.get(7).call("new_and_store", {"app": "writer", "dst": str(dst), "filter": "writer8"}, timeout=120) 171 return odf_versions(dst) == ("1.4", "1.4") 172 173 174@pytest.fixture 175def need_mode(request, pytestconfig): 176 """need_mode(mode): skip (or fail with --require-odf13 or --require-odf14) when the build cannot write that save mode (names or numbers).""" 177 def check(mode): 178 number = SAVE_MODES.get(mode, mode) 179 for numbers, probe, option, label in (((5, 6), "odf13", "--require-odf13", "1.3"), ((7, 8), "odf14", "--require-odf14", "1.4")): 180 if number in numbers and not request.getfixturevalue(probe): 181 message = "this build does not write ODF %s (save mode %s)" % (label, mode) 182 if pytestconfig.getoption(option): 183 pytest.fail(message, pytrace=False) 184 pytest.skip(message) 185 return check 186 187 188def pytest_terminal_summary(terminalreporter, config): 189 unused = [r for r in _EXPECT_ROWS if not r.used] 190 if unused and not config.option.keyword and not config.args[1:]: 191 terminalreporter.write_line("expectation rows that matched no collected item: %d (first: %s at %s)" % (len(unused), unused[0].pattern, unused[0].origin)) 192