|
16 | 16 | # ======================= LEGACY=COMPATIBLE FIXTURES =========================
|
17 | 17 | # The following fixtures are designed to work with both pytest system test
|
18 | 18 | # runner and the legacy system test framework.
|
| 19 | +# |
| 20 | +# FUTURE: Rewrite the individual port fixtures to re-use the `ports` fixture. |
19 | 21 |
|
20 | 22 |
|
21 | 23 | @pytest.fixture(scope="module")
|
@@ -53,12 +55,19 @@ def control_port():
|
53 | 55 | from pathlib import Path
|
54 | 56 | import re
|
55 | 57 | import subprocess
|
| 58 | + import time |
| 59 | + |
| 60 | + # Silence warnings caused by passing a pytest fixture to another fixture. |
| 61 | + # pylint: disable=redefined-outer-name |
56 | 62 |
|
57 | 63 | # ----------------------- Globals definition -----------------------------
|
58 | 64 |
|
59 | 65 | XDIST_WORKER = os.environ.get("PYTEST_XDIST_WORKER", "")
|
60 | 66 | FILE_DIR = os.path.abspath(Path(__file__).parent)
|
61 | 67 | ENV_RE = re.compile("([^=]+)=(.*)")
|
| 68 | + PORT_MIN = 5001 |
| 69 | + PORT_MAX = 32767 |
| 70 | + PORTS_PER_TEST = 20 |
62 | 71 |
|
63 | 72 | # ---------------------- Module initialization ---------------------------
|
64 | 73 |
|
@@ -119,3 +128,78 @@ def pytest_configure():
|
119 | 128 | logging.error("failed to compile test files: %s", exc)
|
120 | 129 | raise exc
|
121 | 130 | logging.debug(proc.stdout)
|
| 131 | + |
| 132 | + # --------------------------- Fixtures ----------------------------------- |
| 133 | + |
| 134 | + @pytest.fixture(scope="session") |
| 135 | + def modules(): |
| 136 | + """Sorted list of all modules. Used to determine port distribution.""" |
| 137 | + mods = [] |
| 138 | + for dirpath, _dirs, files in os.walk(os.getcwd()): |
| 139 | + for file in files: |
| 140 | + if file.startswith("tests_") and file.endswith(".py"): |
| 141 | + mod = f"{dirpath}/{file}" |
| 142 | + mods.append(mod) |
| 143 | + return sorted(mods) |
| 144 | + |
| 145 | + @pytest.fixture(scope="session") |
| 146 | + def module_base_ports(modules): |
| 147 | + """ |
| 148 | + Dictionary containing assigned base port for every module. |
| 149 | +
|
| 150 | + Note that this is a session-wide fixture. The port numbers are |
| 151 | + deterministically assigned before any testing starts. This fixture MUST |
| 152 | + return the same value when called again during the same test session. |
| 153 | + When running tests in parallel, this is exactly what happens - every |
| 154 | + worker thread will call this fixture to determine test ports. |
| 155 | + """ |
| 156 | + port_min = PORT_MIN |
| 157 | + port_max = PORT_MAX - len(modules) * PORTS_PER_TEST |
| 158 | + if port_max < port_min: |
| 159 | + raise RuntimeError( |
| 160 | + "not enough ports to assign unique port set to each module" |
| 161 | + ) |
| 162 | + |
| 163 | + # Rotate the base port value over time to detect possible test issues |
| 164 | + # with using random ports. This introduces a very slight race condition |
| 165 | + # risk. If this value changes between pytest invocation and spawning |
| 166 | + # worker threads, multiple tests may have same port values assigned. If |
| 167 | + # these tests are then executed simultaneously, the test results will |
| 168 | + # be misleading. |
| 169 | + base_port = int(time.time() // 3600) % (port_max - port_min) |
| 170 | + |
| 171 | + return {mod: base_port + i * PORTS_PER_TEST for i, mod in enumerate(modules)} |
| 172 | + |
| 173 | + @pytest.fixture(scope="module") |
| 174 | + def base_port(request, module_base_ports): |
| 175 | + """Start of the port range assigned to a particular test module.""" |
| 176 | + port = module_base_ports[request.fspath] |
| 177 | + return port |
| 178 | + |
| 179 | + @pytest.fixture(scope="module") |
| 180 | + def ports(base_port): |
| 181 | + """Dictionary containing port names and their assigned values.""" |
| 182 | + return { |
| 183 | + "PORT": str(base_port), |
| 184 | + "TLSPORT": str(base_port + 1), |
| 185 | + "HTTPPORT": str(base_port + 2), |
| 186 | + "HTTPSPORT": str(base_port + 3), |
| 187 | + "EXTRAPORT1": str(base_port + 4), |
| 188 | + "EXTRAPORT2": str(base_port + 5), |
| 189 | + "EXTRAPORT3": str(base_port + 6), |
| 190 | + "EXTRAPORT4": str(base_port + 7), |
| 191 | + "EXTRAPORT5": str(base_port + 8), |
| 192 | + "EXTRAPORT6": str(base_port + 9), |
| 193 | + "EXTRAPORT7": str(base_port + 10), |
| 194 | + "EXTRAPORT8": str(base_port + 11), |
| 195 | + "CONTROLPORT": str(base_port + 12), |
| 196 | + } |
| 197 | + |
| 198 | + @pytest.fixture(scope="module") |
| 199 | + def env(ports): |
| 200 | + """Dictionary containing environment variables for the test.""" |
| 201 | + env = CONF_ENV.copy() |
| 202 | + env.update(ports) |
| 203 | + env["builddir"] = f"{env['TOP_BUILDDIR']}/bin/tests/system" |
| 204 | + env["srcdir"] = f"{env['TOP_SRCDIR']}/bin/tests/system" |
| 205 | + return env |
0 commit comments