mirror of
https://github.com/ninja-build/ninja.git
synced 2026-08-05 15:41:05 +00:00
This implements a GNU jobserver token pool that will be used for testing the upcoming jobserver Ninja client implementation. Note that the implementation is basic and doesn't try to deal with broken protocol clients (which release more tokens than they acquired). Supporting them would require something vastly more complex that would monitor the state of the pipe/fifo at all times.
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()
|