175 lines
5.7 KiB
Python
Executable File
175 lines
5.7 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
"""Regression tests for the jobserver_pool.py script."""
|
|
|
|
import os
|
|
import re
|
|
import platform
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
import typing as T
|
|
|
|
_SCRIPT_DIR = os.path.dirname(__file__)
|
|
_JOBSERVER_SCRIPT = os.path.join(_SCRIPT_DIR, "jobserver_pool.py")
|
|
_JOBSERVER_CMD = [sys.executable, _JOBSERVER_SCRIPT]
|
|
|
|
_IS_WINDOWS = sys.platform == "win32"
|
|
|
|
# This is only here to avoid depending on the non-standard
|
|
# scanf package which does the job properly :-)
|
|
|
|
|
|
def _simple_scanf(pattern: str, input: str) -> T.Sequence[T.Any]:
|
|
"""Extract values from input using a scanf-like pattern.
|
|
|
|
This is very basic and only used to avoid depending on the
|
|
non-standard scanf package which does the job properly.
|
|
Only supports %d, %s and %%, does not support any fancy
|
|
escaping.
|
|
"""
|
|
re_pattern = ""
|
|
groups = ""
|
|
from_pos = 0
|
|
|
|
# Just in case.
|
|
assert "." not in pattern, f"Dots in pattern not supported."
|
|
assert "?" not in pattern, f"Question marks in pattern not supported."
|
|
|
|
while True:
|
|
next_percent = pattern.find("%", from_pos)
|
|
if next_percent < 0 or next_percent + 1 >= len(pattern):
|
|
re_pattern += pattern[from_pos:]
|
|
break
|
|
|
|
re_pattern += pattern[from_pos:next_percent]
|
|
|
|
from_pos = next_percent + 2
|
|
formatter = pattern[next_percent + 1]
|
|
if formatter == "%":
|
|
re_pattern += "%"
|
|
elif formatter == "d":
|
|
groups += formatter
|
|
re_pattern += "(\\d+)"
|
|
elif formatter == "s":
|
|
groups += formatter
|
|
re_pattern += "(\\S+)"
|
|
else:
|
|
assert False, f"Unsupported scanf formatter: %{formatter}"
|
|
|
|
m = re.match(re_pattern, input)
|
|
if not m:
|
|
return None
|
|
|
|
result = []
|
|
for group_index, formatter in enumerate(groups, start=1):
|
|
if formatter == "d":
|
|
result.append(int(m.group(group_index)))
|
|
elif formatter == "s":
|
|
result.append(m.group(group_index))
|
|
else:
|
|
assert False, f"Unsupported formatter {formatter}"
|
|
|
|
return result
|
|
|
|
|
|
class JobserverPool(unittest.TestCase):
|
|
def _run_jobserver_echo_MAKEFLAGS(
|
|
self, cmd_args_prefix
|
|
) -> "subprocess.CompletedProcess[str]":
|
|
if _IS_WINDOWS:
|
|
cmd_args = cmd_args_prefix + ["cmd.exe", "/c", "echo %MAKEFLAGS%"]
|
|
else:
|
|
cmd_args = cmd_args_prefix + ["sh", "-c", 'echo "$MAKEFLAGS"']
|
|
|
|
ret = subprocess.run(
|
|
cmd_args,
|
|
text=True,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
)
|
|
ret.check_returncode()
|
|
return ret
|
|
|
|
def _test_echo_MAKEFLAGS(self, cmd_args_prefix, expected_core_count: int):
|
|
ret = self._run_jobserver_echo_MAKEFLAGS(cmd_args_prefix)
|
|
makeflags = ret.stdout.rstrip()
|
|
|
|
if expected_core_count == 0:
|
|
if _IS_WINDOWS:
|
|
# On Windows, echo %FOO% prints "%FOO%" if FOO is not defined!
|
|
self.assertEqual(makeflags.strip(), "%MAKEFLAGS%")
|
|
else:
|
|
self.assertEqual(makeflags.strip(), "")
|
|
|
|
else: # expected_core_count > 0
|
|
if _IS_WINDOWS:
|
|
expected_format = " -j%d --jobserver-auth=%s"
|
|
else:
|
|
expected_format = " -j%d --jobserver-auth=fifo:%s"
|
|
|
|
m = _simple_scanf(expected_format, makeflags)
|
|
self.assertTrue(
|
|
m,
|
|
f"Invalid MAKEFLAGS value, expected format [{expected_format}], got: [{makeflags}]",
|
|
)
|
|
|
|
if _IS_WINDOWS:
|
|
sem_name = m[1]
|
|
self.assertEqual(
|
|
sem_name,
|
|
"jobserver_pool",
|
|
f"Invalid semaphore name in MAKEFLAGS value [{makeflags}]",
|
|
)
|
|
else:
|
|
fifo_name = os.path.basename(m[1])
|
|
self.assertEqual(
|
|
fifo_name,
|
|
"jobserver_pool",
|
|
f"Invalid fifo name in MAKEFLAGS value [{makeflags}]",
|
|
)
|
|
|
|
core_count = m[0]
|
|
self.assertEqual(
|
|
core_count,
|
|
expected_core_count,
|
|
f"Invalid core count {core_count}, expected {expected_core_count}",
|
|
)
|
|
|
|
def test_MAKEFLAGS_default(self):
|
|
self._test_echo_MAKEFLAGS(_JOBSERVER_CMD, os.cpu_count())
|
|
|
|
def test_MAKEFLAGS_with_10_jobs(self):
|
|
self._test_echo_MAKEFLAGS(_JOBSERVER_CMD + ["-j10"], 10)
|
|
self._test_echo_MAKEFLAGS(_JOBSERVER_CMD + ["--jobs=10"], 10)
|
|
self._test_echo_MAKEFLAGS(_JOBSERVER_CMD + ["--jobs", "10"], 10)
|
|
|
|
def test_MAKEFLAGS_with_no_jobs(self):
|
|
self._test_echo_MAKEFLAGS(_JOBSERVER_CMD + ["-j0"], 0)
|
|
self._test_echo_MAKEFLAGS(_JOBSERVER_CMD + ["--jobs=0"], 0)
|
|
self._test_echo_MAKEFLAGS(_JOBSERVER_CMD + ["--jobs", "0"], 0)
|
|
|
|
@unittest.skipIf(_IS_WINDOWS, "--fifo is not supported on Windows")
|
|
def test_MAKEFLAGS_with_fifo(self):
|
|
fifo_name = "test_fifo"
|
|
fifo_path = os.path.abspath(fifo_name)
|
|
ret = self._run_jobserver_echo_MAKEFLAGS(
|
|
_JOBSERVER_CMD + ["-j10", "--fifo", fifo_name]
|
|
)
|
|
makeflags = ret.stdout.rstrip()
|
|
self.assertEqual(makeflags, " -j10 --jobserver-auth=fifo:" + fifo_path)
|
|
|
|
@unittest.skipIf(not _IS_WINDOWS, "--name is not supported on Posix")
|
|
def test_MAKEFLAGS_with_name(self):
|
|
sem_name = "test_semaphore"
|
|
ret = self._run_jobserver_echo_MAKEFLAGS(
|
|
_JOBSERVER_CMD + ["-j10", "--name", sem_name]
|
|
)
|
|
makeflags = ret.stdout.rstrip()
|
|
self.assertEqual(makeflags, " -j10 --jobserver-auth=" + sem_name)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|