Files
AnyPS5/tools/tests/test_test_registration.py
T
Dean ad622fc18d Merge pull request #11430 from StoneDC/fix/ctest-quoted-semicolons
fix(tools): preserve quoted CMake semicolon escapes
2026-10-27 18:38:27 +13:00

536 lines
32 KiB
Python

from pathlib import Path
import contextlib
import io
import os
import shutil
import subprocess
import sys
import tempfile
import unittest
from types import SimpleNamespace
from unittest.mock import patch
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
import check_test_registration
class RegistrationTests(unittest.TestCase):
@unittest.skipUnless(shutil.which("cmake"), "CMake is required for the argument oracle")
def test_quoted_semicolons_match_real_cmake_arguments(self):
values = ["left" + "\\" * count + ";right" for count in range(9)]
values += [r"\;", r"a\;b\;c", r"a;\;b", "a\\\n\\;b", "a\\\r\n\\;b", "\u00e9\\;tail"]
commands = [f'add_test("case{index}" "{value}")\n' for index, value in enumerate(values)]
commands += ['add_test("bracket" [=[a\\;b]=])\n']
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
script = root / "oracle.cmake"
script.write_bytes(('cmake_minimum_required(VERSION 3.20)\n'
'function(add_test name value)\n'
'string(HEX "${value}" encoded)\n'
'file(WRITE "${CMAKE_CURRENT_LIST_DIR}/${name}.hex" "${encoded}")\n'
'endfunction()\n' + "".join(commands)).encode("utf-8"))
subprocess.run(["cmake", "-P", str(script)], capture_output=True, text=True, check=True)
for content in commands:
command, arguments = list(check_test_registration.commands(content))[0]
with self.subTest(name=arguments[0]):
actual = bytes.fromhex((root / f"{arguments[0]}.hex").read_text(encoding="ascii")).decode("utf-8")
self.assertEqual((command, arguments), ("add_test", [arguments[0], actual]))
@unittest.skipUnless(shutil.which("cmake"), "CMake is required for the argument oracle")
def test_quoted_continuations_follow_cmake(self):
for ending, backslashes in (("\n", 1), ("\r\n", 1), ("\n", 2), ("\n", 3), ("\n", 4)):
with self.subTest(ending=repr(ending), backslashes=backslashes), tempfile.TemporaryDirectory() as directory:
content = 'add_test("real" "tools/tests/test_' + '\\' * backslashes + ending + 'bounds.py")\n'
script = Path(directory) / "oracle.cmake"
script.write_bytes(('function(add_test)\n'
'file(WRITE "${CMAKE_CURRENT_LIST_DIR}/argument.txt" "${ARGV1}")\n'
'endfunction()\n' + content).encode())
subprocess.run(["cmake", "-P", str(script)], capture_output=True, text=True, check=True)
actual = (Path(directory) / "argument.txt").read_text()
expected = "tools/tests/test_" + '\\' * (backslashes // 2) + ("\n" if backslashes % 2 == 0 else "") + "bounds.py"
self.assertEqual(actual, expected)
self.assertEqual(list(check_test_registration.commands(content)), [("add_test", ["real", actual])])
if backslashes == 1:
(Path(directory) / "CMakeLists.txt").write_bytes(content.encode())
self.assertEqual(check_test_registration.run_scripts(Path(directory), ["CMakeLists.txt"]), {actual})
def test_literal_escaped_newline_and_bracket_continuation_are_preserved(self):
quoted = r'add_test("real" "a\nb")' + '\n'
bracket = 'add_test("real" [=[a' + '\\' + '\n' + 'b]=])\n'
self.assertEqual(list(check_test_registration.commands(quoted)), [("add_test", ["real", "a\nb"])])
self.assertEqual(list(check_test_registration.commands(bracket)), [("add_test", ["real", "a\\\nb"])])
def test_bracket_arguments_ignore_only_one_initial_newline(self):
values = (("path", "path"), ("\npath", "path"), ("\n\npath", "\npath"),
("\n path", " path"), (" \npath", " \npath"), ("\\npath", "\\npath"),
("\n", ""), ("\npath\n", "path\n"))
for equals in ("", "=", "==="):
for content, expected in values:
with self.subTest(equals=equals, content=content):
command = f"subdirs([{equals}[{content}]{equals}])\n"
self.assertEqual(list(check_test_registration.commands(command)), [("subdirs", [expected])])
self.assertEqual(list(check_test_registration.commands('subdirs("\npath")\n')),
[("subdirs", ["\npath"])])
def test_bracket_initial_newlines_do_not_hide_disabled_tests(self):
content = ('add_test([=[\nbounds]=] "/build/tests/buffer_bounds_tests")\n'
'set_tests_properties(bounds PROPERTIES DISABLED [=[\nTRUE]=])\n')
self.assertNotIn("buffer_bounds_tests", self.registered(content))
def test_bracket_initial_newline_in_subdirectory_is_ignored(self):
with tempfile.TemporaryDirectory() as directory:
build = Path(directory)
(build / "nested").mkdir()
(build / "CTestTestfile.cmake").write_text('subdirs([=[\nnested]=])\n', encoding="utf-8")
(build / "nested/CTestTestfile.cmake").write_text(
'add_test(bounds "/build/tests/buffer_bounds_tests")\n', encoding="utf-8")
self.assertEqual(check_test_registration.registered(build), {"buffer_bounds_tests"})
def test_literal_prefixes_cannot_expose_commands_inside_arguments(self):
prefixes = (r"\#", r"\(", '"prefix" ', '"" ', '"line\nprefix" ', "[[prefix]] ",
"[=[prefix]=] ", "[===[line\nprefix]===] ")
for prefix in prefixes:
with self.subTest(prefix=prefix):
content = (f"set(example\n{prefix}add_test(ghost /build/tests/missing_tests)\n)\n"
'add_test("real" "/build/tests/real_tests")\n')
self.assertEqual(self.registered(content), {"real_tests"})
def test_commands_inside_literals_do_not_register_tests(self):
for opening, closing in (('"', '"'), ("[[", "]]"), ("[=[", "]=]"), ("[===[", "]===]")):
with self.subTest(opening=opening):
content = (f"set(example {opening}\nadd_test(ghost /build/tests/missing_tests)\n{closing})\n"
'add_test("real" "/build/tests/real_tests")\n')
self.assertEqual(self.registered(content), {"real_tests"})
def test_properties_inside_literals_do_not_disable_a_real_test(self):
for opening, closing in (('"', '"'), ("[[", "]]"), ("[=[", "]=]"), ("[===[", "]===]")):
with self.subTest(opening=opening):
content = ('add_test("real" "/build/tests/real_tests")\n'
f"set(example {opening}\nset_tests_properties(real PROPERTIES DISABLED TRUE)\n{closing})\n")
self.assertEqual(self.registered(content), {"real_tests"})
def test_subdirectories_inside_literals_are_not_followed(self):
with tempfile.TemporaryDirectory() as directory:
build = Path(directory)
ignored = build / "ignored"
ignored.mkdir()
(ignored / "CTestTestfile.cmake").write_text(
'add_test("ghost" "/build/tests/missing_tests")\n', encoding="utf-8")
for opening, closing in (('"', '"'), ("[[", "]]"), ("[=[", "]=]"), ("[===[", "]===]")):
with self.subTest(opening=opening):
content = (f"set(example {opening}\nsubdirs(ignored)\n{closing})\n"
'add_test("real" "/build/tests/real_tests")\n')
(build / "CTestTestfile.cmake").write_text(content, encoding="utf-8")
self.assertEqual(check_test_registration.registered(build), {"real_tests"})
def test_disabled_property_is_local_to_each_configuration(self):
for configurations in (("OFF", "TRUE"), ("TRUE", "OFF"), ("TRUE", "TRUE")):
with self.subTest(configurations=configurations):
content = 'if(CTEST_CONFIGURATION_TYPE STREQUAL "Debug")\n'
for index, disabled in enumerate(configurations):
if index:
content += 'else()\n'
content += f'add_test("bounds" "/build/tests/config{index}/buffer_bounds_tests")\n'
content += f'set_tests_properties("bounds" PROPERTIES DISABLED {disabled})\n'
content += 'endif()\n'
names = self.registered(content)
self.assertEqual("buffer_bounds_tests" in names, "OFF" in configurations)
def registered(self, content):
with tempfile.TemporaryDirectory() as directory:
build = Path(directory)
(build / "CTestTestfile.cmake").write_text(content, encoding="utf-8")
return check_test_registration.registered(build)
def test_bracket_comments_do_not_register_tests(self):
for equals in ("", "=", "==="):
with self.subTest(equals=equals):
content = (f"#[{equals}[\nadd_test(ghost /build/tests/missing_tests)\n]{equals}]\n"
'add_test("real" "/build/tests/real_tests")\n')
self.assertEqual(self.registered(content), {"real_tests"})
def test_bracket_comment_does_not_disable_a_registered_test(self):
content = ('add_test("real" "/build/tests/real_tests")\n'
'#[=[\nset_tests_properties("real" PROPERTIES DISABLED TRUE)\n]=]\n')
self.assertEqual(self.registered(content), {"real_tests"})
def test_bracket_comment_inside_arguments_does_not_end_the_command(self):
content = ('add_test("real" #[=[ )\nadd_test(ghost /build/tests/missing_tests)\n]=]\n'
'"/build/tests/real_tests")\n')
self.assertEqual(self.registered(content), {"real_tests"})
def test_comment_markers_inside_literals_remain_arguments(self):
content = ('add_test("real" "/build/tests/real_tests" "#[[" [=[# comment]=] "\\\\#")\n')
self.assertEqual(list(check_test_registration.commands(content)),
[("add_test", ["real", "/build/tests/real_tests", "#[[", "# comment", "\\#"])])
def test_test_name_does_not_register_executable(self):
names = self.registered('add_test("missing_tests" "/usr/bin/python3" "unrelated.py")\n')
self.assertNotIn("missing_tests", names)
def test_direct_executable(self):
names = self.registered('add_test("bounds" "/build/tests/buffer_bounds_tests")\n')
self.assertIn("buffer_bounds_tests", names)
self.assertNotIn("bounds", names)
def test_wrapped_executable(self):
names = self.registered('add_test("bounds" "/usr/bin/python3" "runner.py" "/build/tests/buffer_bounds_tests")\n')
self.assertIn("buffer_bounds_tests", names)
def test_windows_executable_and_test_name(self):
names = self.registered('add_test("missing_tests.exe" "C:/Program Files/Python/python.exe" "runner.py" "C:/build/tests/buffer_bounds_tests.exe")\n')
self.assertIn("buffer_bounds_tests", names)
self.assertNotIn("missing_tests", names)
def test_hyphenated_executable_is_discovered(self):
output = "tests/buffer-bounds_tests: CXX_EXECUTABLE_LINKER\n"
with tempfile.TemporaryDirectory() as directory:
build = Path(directory)
(build / "CMakeCache.txt").write_text("CMAKE_MAKE_PROGRAM:FILEPATH=/configured/ninja\nCMAKE_GENERATOR:INTERNAL=Ninja\n", encoding="utf-8")
with patch.object(check_test_registration.subprocess, "run", side_effect=lambda command, **kwargs: SimpleNamespace(
stdout="Build file encoding: UTF-8\n" if command[-1] == "wincodepage" else output)) as run:
self.assertEqual(check_test_registration.built(build), {"buffer-bounds_tests"})
self.assertEqual(run.call_args[0][0][0], "/configured/ninja")
def test_nested_test_output_is_discovered(self):
output = "tests/gpu/buffer_bounds_tests: CXX_EXECUTABLE_LINKER\n"
with tempfile.TemporaryDirectory() as directory:
build = Path(directory)
(build / "CMakeCache.txt").write_text("CMAKE_MAKE_PROGRAM:FILEPATH=/configured/ninja\nCMAKE_GENERATOR:INTERNAL=Ninja Multi-Config\n", encoding="utf-8")
with patch.object(check_test_registration.subprocess, "run", side_effect=lambda command, **kwargs: SimpleNamespace(
stdout="Build file encoding: UTF-8\n" if command[-1] == "wincodepage" else output)) as run:
self.assertEqual(check_test_registration.built(build), {"buffer_bounds_tests"})
self.assertEqual(run.call_args[0][0][0], "/configured/ninja")
def test_quoted_cmake_path_is_unescaped(self):
names = self.registered(r'add_test("bounds" "/build/tests/a\"b_tests")' + '\n')
self.assertIn('a"b_tests', names)
def test_bracket_quoted_command_is_recognized(self):
names = self.registered('add_test([=[bounds]=] [=[/build/tests/buffer_bounds_tests]=])\n')
self.assertIn("buffer_bounds_tests", names)
def test_multiline_test_command_is_recognized(self):
names = self.registered('add_test("bounds"\n "/build/tests/buffer_bounds_tests"\n)\n')
self.assertIn("buffer_bounds_tests", names)
def test_multi_config_branches_register_the_executable(self):
names = self.registered(
'if(CTEST_CONFIGURATION_TYPE MATCHES "^([Dd][Ee][Bb][Uu][Gg])$")\n'
' add_test([=[bounds]=] "/build/tests/Debug/buffer_bounds_tests")\n'
' set_tests_properties([=[bounds]=] PROPERTIES TIMEOUT "20")\n'
'elseif(CTEST_CONFIGURATION_TYPE MATCHES "^([Rr][Ee][Ll][Ee][Aa][Ss][Ee])$")\n'
' add_test([=[bounds]=] "/build/tests/Release/buffer_bounds_tests")\n'
' set_tests_properties([=[bounds]=] PROPERTIES TIMEOUT "20")\n'
'else()\n'
' add_test([=[bounds]=] NOT_AVAILABLE)\n'
'endif()\n')
self.assertIn("buffer_bounds_tests", names)
def test_disabled_test_is_not_counted_as_run(self):
names = self.registered('add_test("bounds" "/build/tests/buffer_bounds_tests")\nset_tests_properties("bounds" PROPERTIES DISABLED TRUE)\n')
self.assertNotIn("buffer_bounds_tests", names)
def test_option_value_does_not_register_executable(self):
names = self.registered('add_test("bounds" "/usr/bin/python3" "runner.py" "--report=/build/tests/missing_tests")\n')
self.assertNotIn("missing_tests", names)
def test_reenabled_test_is_counted_as_run(self):
names = self.registered('add_test("bounds" "/build/tests/buffer_bounds_tests")\nset_tests_properties("bounds" PROPERTIES DISABLED TRUE)\nset_tests_properties("bounds" PROPERTIES DISABLED OFF)\n')
self.assertIn("buffer_bounds_tests", names)
def test_disabled_wrapper_does_not_register_its_argument(self):
names = self.registered('add_test("bounds" "/usr/bin/python3" "runner.py" "/build/tests/buffer_bounds_tests")\nset_tests_properties([=[bounds]=] PROPERTIES DISABLED "ON")\n')
self.assertNotIn("buffer_bounds_tests", names)
def test_command_text_inside_an_argument_is_not_another_test(self):
names = self.registered('add_test("bounds" "/usr/bin/python3" [=[\nadd_test("fake" "/build/tests/missing_tests")\n]=])\n')
self.assertNotIn("missing_tests", names)
def test_windows_escaped_path_is_decoded(self):
names = self.registered(r'add_test("bounds" "C:\\build\\tests\\buffer_bounds_tests.exe")' + '\n')
self.assertIn("buffer_bounds_tests", names)
def test_unreferenced_registration_directory_does_not_count(self):
with tempfile.TemporaryDirectory() as directory:
build = Path(directory)
(build / "CTestTestfile.cmake").write_text("", encoding="utf-8")
stale = build / "removed"
stale.mkdir()
(stale / "CTestTestfile.cmake").write_text(
'add_test("bounds" "/build/tests/buffer_bounds_tests")\n', encoding="utf-8")
self.assertNotIn("buffer_bounds_tests", check_test_registration.registered(build))
(build / "CTestTestfile.cmake").unlink()
self.assertNotIn("buffer_bounds_tests", check_test_registration.registered(build))
def test_only_reachable_nested_directories_are_registered(self):
with tempfile.TemporaryDirectory() as directory:
build = Path(directory)
active = build / "active tests"
nested = active / "nested"
removed = active / "removed"
nested.mkdir(parents=True)
removed.mkdir()
(build / "CTestTestfile.cmake").write_text('subdirs("active tests")\n', encoding="utf-8")
(active / "CTestTestfile.cmake").write_text('subdirs([=[nested]=])\n', encoding="utf-8")
for path, name in ((nested, "active_tests"), (removed, "removed_tests")):
(path / "CTestTestfile.cmake").write_text(
f'add_test("check" "/build/tests/{name}")\n', encoding="utf-8")
self.assertEqual(check_test_registration.registered(build), {"active_tests"})
def test_external_registration_directory_is_followed(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
build, external = root / "build", root / "external"
build.mkdir()
external.mkdir()
(build / "CTestTestfile.cmake").write_text(
f'subdirs("{external.as_posix()}")\n', encoding="utf-8")
(external / "CTestTestfile.cmake").write_text(
'add_test("bounds" "/build/tests/buffer_bounds_tests")\n', encoding="utf-8")
self.assertEqual(check_test_registration.registered(build), {"buffer_bounds_tests"})
def test_unsupported_generator_never_runs_build_tool(self):
for generator in ("Unix Makefiles", "Visual Studio 17 2022", ""):
with self.subTest(generator=generator), tempfile.TemporaryDirectory() as directory:
build = Path(directory)
(build / "CMakeCache.txt").write_text("CMAKE_MAKE_PROGRAM:FILEPATH=make\nCMAKE_GENERATOR:INTERNAL=" + generator + "\n", encoding="utf-8")
with patch.object(check_test_registration.subprocess, "run") as run:
with self.assertRaisesRegex(ValueError, "requires a Ninja generator"):
check_test_registration.built(build)
run.assert_not_called()
def test_missing_make_program_never_runs_build_tool(self):
for program in (None, "", "CMAKE_MAKE_PROGRAM-NOTFOUND"):
with self.subTest(program=program), tempfile.TemporaryDirectory() as directory:
build = Path(directory)
cache = "CMAKE_GENERATOR:INTERNAL=Ninja\n"
if program is not None:
cache += "CMAKE_MAKE_PROGRAM:FILEPATH=" + program + "\n"
(build / "CMakeCache.txt").write_text(cache, encoding="utf-8")
with patch.object(check_test_registration.subprocess, "run") as run:
with self.assertRaises(FileNotFoundError):
check_test_registration.built(build)
run.assert_not_called()
@unittest.skipIf(shutil.which("ninja") is None, "requires a local Ninja to exercise its real output encoding")
def test_ninja_output_encoding_with_a_cp1252_default(self):
program = shutil.which("ninja")
actual_run = check_test_registration.subprocess.run
for encoding in (("utf-8", "mbcs") if sys.platform == "win32" else ("utf-8",)):
with self.subTest(encoding=encoding), tempfile.TemporaryDirectory() as directory:
build = Path(directory)
name = "caf\u00e9_tests".encode(encoding, errors="ignore").decode(encoding)
(build / "CMakeCache.txt").write_text(
f"CMAKE_MAKE_PROGRAM:FILEPATH={program}\nCMAKE_GENERATOR:INTERNAL=Ninja\n", encoding="utf-8")
(build / "build.ninja").write_text(
"rule CXX_EXECUTABLE_LINKER\n command = true\n"
f"build tests/{name}: CXX_EXECUTABLE_LINKER\n", encoding=encoding)
def run(command, **kwargs):
if command[-1] == "wincodepage":
return SimpleNamespace(stdout="Build file encoding: " + ("ANSI" if encoding == "mbcs" else "UTF-8"))
kwargs.setdefault("encoding", "cp1252")
return actual_run(command, **kwargs)
with patch.object(check_test_registration.subprocess, "run", side_effect=run):
self.assertEqual(check_test_registration.built(build), {name})
def test_unknown_windows_ninja_encoding_is_rejected(self):
with tempfile.TemporaryDirectory() as directory:
build = Path(directory)
(build / "CMakeCache.txt").write_text(
"CMAKE_MAKE_PROGRAM:FILEPATH=/configured/ninja\nCMAKE_GENERATOR:INTERNAL=Ninja\n", encoding="utf-8")
with patch.object(check_test_registration.sys, "platform", "win32"), patch.object(
check_test_registration.subprocess, "run", return_value=SimpleNamespace(stdout="unknown")) as run:
with self.assertRaisesRegex(ValueError, "unsupported Ninja build-file encoding"):
check_test_registration.built(build)
run.assert_called_once()
def run_tool(self, build):
return subprocess.run([sys.executable, "-I", str(Path(check_test_registration.__file__)), str(build)],
capture_output=True, text=True, timeout=30)
def test_missing_build_directory_is_reported_without_a_traceback(self):
with tempfile.TemporaryDirectory() as directory:
result = self.run_tool(Path(directory) / "no build")
self.assertEqual(result.returncode, 2)
self.assertIn("error:", result.stderr)
self.assertIn("CMakeCache.txt", result.stderr)
self.assertNotIn("Traceback", result.stderr)
def test_unsupported_generator_is_reported_without_a_traceback(self):
with tempfile.TemporaryDirectory() as directory:
(Path(directory) / "CMakeCache.txt").write_text("CMAKE_GENERATOR:INTERNAL=Unix Makefiles\n", encoding="utf-8")
result = self.run_tool(directory)
self.assertEqual(result.returncode, 2)
self.assertIn("requires a Ninja generator", result.stderr)
self.assertNotIn("Traceback", result.stderr)
class ScriptRegistrationTests(unittest.TestCase):
def unrun(self, files):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
check_test_registration.subprocess.run(["git", "init", "-q", str(root)], check=True)
for name, content in files.items():
(root / name).parent.mkdir(parents=True, exist_ok=True)
(root / name).write_text(content, encoding="utf-8")
return check_test_registration.unrun_scripts(root)[1]
def test_script_registered_by_a_nested_add_test_runs(self):
self.assertEqual(self.unrun({
"core/relinker/CMakeLists.txt": "if(WIN32)\n add_test(NAME a COMMAND ${Python3_EXECUTABLE}\n ${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_a.py)\nendif()\n",
"core/relinker/relinker/tests/test_a.py": ""}), [])
def test_script_in_bracket_argument_with_initial_newline_is_registered(self):
self.assertEqual(self.unrun({
"CMakeLists.txt": "add_test(NAME a COMMAND python [=[\ntools/tests/test_a.py]=])\n",
"tools/tests/test_a.py": ""}), [])
def test_unregistered_script_is_reported(self):
self.assertEqual(self.unrun({
"CMakeLists.txt": "add_test(NAME a COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_a.py)\n",
"tools/tests/test_a.py": "", "tools/tests/test_b.py": "", "tools/tests/helper.py": ""}), ["tools/tests/test_b.py"])
def test_commented_add_test_does_not_run_a_script(self):
self.assertEqual(self.unrun({
"CMakeLists.txt": "#add_test(NAME a COMMAND python ${CMAKE_CURRENT_SOURCE_DIR}/test_a.py)\n#[[\nadd_test(NAME a COMMAND python test_a.py)\n]]\n",
"test_a.py": ""}), ["test_a.py"])
def test_registration_names_its_directory(self):
self.assertEqual(self.unrun({
"CMakeLists.txt": "add_test(NAME a COMMAND python ${CMAKE_CURRENT_SOURCE_DIR}/a/tests/test_a.py)\n",
"a/tests/test_a.py": "", "b/tests/test_a.py": ""}), ["b/tests/test_a.py"])
def test_workflow_run_counts_and_a_paths_filter_does_not(self):
self.assertEqual(self.unrun({
".github/workflows/pages.yml": "jobs:\n web:\n steps:\n - run: python3 tools/test_web.py --build build-wasm\n",
".github/workflows/web.yml": "on:\n pull_request:\n paths:\n - tools/test_paths.py\n",
"tools/test_web.py": "", "tools/test_paths.py": ""}), ["tools/test_paths.py"])
def test_workflow_script_inside_a_longer_name_does_not_run(self):
self.assertEqual(self.unrun({
".github/workflows/build.yml": "jobs:\n t:\n steps:\n - run: python3 tools/check_test_registration.py build\n"
" - run: python3 tools/run_test_a.py\n",
"tools/check_test_registration.py": "", "tools/run_test_a.py": "",
"tools/tests/test_registration.py": "", "tools/tests/test_a.py": ""}),
["tools/tests/test_a.py", "tools/tests/test_registration.py"])
def test_workflow_script_with_a_longer_suffix_does_not_run(self):
self.assertEqual(self.unrun({
".github/workflows/ci.yml": "jobs:\n t:\n steps:\n - run: python3 tools/tests/test_a.pyc\n"
" - run: python3 tools/tests/test_b.py.bak\n"
" - run: python3 \"./tools/tests/test_c.py\" --quick\n",
"tools/tests/test_a.py": "", "tools/tests/test_b.py": "", "tools/tests/test_c.py": ""}),
["tools/tests/test_a.py", "tools/tests/test_b.py"])
def test_commented_workflow_line_does_not_run_a_script(self):
self.assertEqual(self.unrun({
".github/workflows/ci.yml": "# python3 tools/tests/test_f.py\njobs:\n t:\n steps:\n # python3 tools/tests/test_a.py\n"
" - run: |\n #python3 tools/tests/test_b.py\n"
" \t# python3 tools/tests/test_c.py\n"
" echo done\t# python3 tools/tests/test_d.py\n"
" python3 tools/tests/test_e.py\n",
"tools/tests/test_a.py": "", "tools/tests/test_b.py": "", "tools/tests/test_c.py": "",
"tools/tests/test_d.py": "", "tools/tests/test_e.py": "", "tools/tests/test_f.py": ""}),
["tools/tests/test_a.py", "tools/tests/test_b.py", "tools/tests/test_c.py", "tools/tests/test_d.py",
"tools/tests/test_f.py"])
def test_workflow_metadata_does_not_register_scripts(self):
script = "tools/tests/test_python_probe.py"
job = "jobs:\n t:\n runs-on: ubuntu-latest\n steps:\n"
echo = " - run: echo done\n"
workflows = (
f"name: python3 {script}\n" + job + echo,
f"env:\n DESCRIPTION: python3 {script}\n" + job + echo,
job + f" - name: python3 {script}\n run: echo done\n",
job + f" - env:\n DESCRIPTION: python3 {script}\n run: echo done\n",
job + f" - env:\n run: python3 {script}\n run: echo done\n",
job + f" - uses: ./{script}\n",
job.replace(" steps:\n", " defaults:\n run:\n shell: bash\n"
f" working-directory: {script}\n steps:\n") + echo,
f"env:\n DESCRIPTION: |\n jobs:\n fake:\n steps:\n - run: python3 {script}\n" + job + echo,
)
for workflow in workflows:
with self.subTest(workflow=workflow):
self.assertEqual(self.unrun({".github/workflows/ci.yml": workflow, script: ""}), [script])
def test_workflow_run_scalars_with_defaults_register_scripts(self):
header = "jobs:\n t:\n defaults:\n run:\n shell: bash\n steps:\n"
steps = (" - run: python3 tools/tests/test_inline.py\n"
" - name: named step\n shell: bash\n run: |\n"
" python3 \"./tools/tests/test_block.py\"\n"
" - shell: bash\n run: 'python tools/tests/test_named.py'\n")
for body in (steps, steps.replace(" - ", " - ").replace(" run:", " run:")
.replace(" shell:", " shell:").replace(" python", " python")):
source = header + body
for quote in ("", "'", '"'):
workflow = source
for key in ("jobs", "t", "steps", "run"):
workflow = workflow.replace(f"{key}:", f"{quote}{key}{quote}:")
with self.subTest(workflow=workflow):
self.assertEqual(self.unrun({".github/workflows/ci.yml": workflow,
"tools/tests/test_inline.py": "", "tools/tests/test_block.py": "",
"tools/tests/test_named.py": ""}), [])
def test_workflow_run_block_stops_at_sibling_metadata(self):
workflow = ("jobs:\n t:\n steps:\n - run: |\n"
" python3 tools/tests/test_actual.py\n"
" name: python3 tools/tests/test_name.py\n"
" env:\n DESCRIPTION: python3 tools/tests/test_env.py\n"
" - name: python3 tools/tests/test_later.py\n run: echo done\n")
self.assertEqual(self.unrun({".github/workflows/ci.yml": workflow,
"tools/tests/test_actual.py": "", "tools/tests/test_name.py": "",
"tools/tests/test_env.py": "", "tools/tests/test_later.py": ""}),
["tools/tests/test_env.py", "tools/tests/test_later.py", "tools/tests/test_name.py"])
class CheckRegistrationCliTests(unittest.TestCase):
def test_cache_with_utf8_bom_is_read(self):
with tempfile.TemporaryDirectory() as directory:
build = Path(directory)
cache = build / "CMakeCache.txt"
cache.write_text("\ufeffCMAKE_MAKE_PROGRAM:FILEPATH=ninja\nCMAKE_GENERATOR:INTERNAL=Ninja\n", encoding="utf-8")
output = "tests/dummy_tests: EXECUTABLE\n"
with patch.object(check_test_registration.subprocess, "run", side_effect=lambda command, **kwargs: SimpleNamespace(
stdout="Build file encoding: UTF-8\n" if command[-1] == "wincodepage" else output)):
names = check_test_registration.built(build)
self.assertEqual(names, {"dummy_tests"})
def test_main_scripts_with_explicit_argv(self):
with patch.object(check_test_registration, "unrun_scripts", return_value=(["test_a.py"], [])):
with contextlib.redirect_stdout(io.StringIO()):
code = check_test_registration.main(["--scripts"])
self.assertEqual(code, 0)
def test_main_reconfigures_streams(self):
class Stream:
def __init__(self):
self.encoding = None
self.errors = None
def reconfigure(self, encoding=None, errors=None):
self.encoding = encoding
self.errors = errors
def write(self, text):
pass
def flush(self):
pass
out, err = Stream(), Stream()
with patch.object(check_test_registration.sys, "stdout", out), patch.object(check_test_registration.sys, "stderr", err):
try:
check_test_registration.main(["--invalid-argument"])
except SystemExit:
pass
self.assertEqual(out.encoding, "utf-8")
self.assertEqual(out.errors, "backslashreplace")
self.assertEqual(err.encoding, "utf-8")
self.assertEqual(err.errors, "backslashreplace")
if __name__ == "__main__":
unittest.main()