Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,13 @@ if(BUILD_TESTING)
add_test(NAME check_main_sync COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_check_main_sync.py)
add_test(NAME configure_seed COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_configure_seed.py)
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_configure_seed.py ConfigureSeedTests)
add_test(NAME configure_seed_paths COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_configure_seed.py ConfigureSeedPathTests)
add_test(NAME configure_seed_round_trips COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_configure_seed.py ConfigureSeedRoundTripTests)
add_test(NAME configure_seed_parsing COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_configure_seed.py ChecksParsingTests)
add_test(NAME stubgen COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/tools/tests/test_stubgen.py)
add_test(NAME debt_split COMMAND ${Python3_EXECUTABLE}
Expand Down
9 changes: 6 additions & 3 deletions tools/configure_seed.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,14 @@ def main():
parser.add_argument("--exclude", action="append", default=[], metavar="GLOB",
help="omit matching check names after inclusion; patterns are case-sensitive")
args = parser.parse_args()
found = {name: entry for name, entry in checks(args.cache).items()
cache, seed = Path(args.cache), Path(args.seed)
if cache.resolve() == seed.resolve() or (cache.exists() and seed.exists() and cache.samefile(seed)):
parser.error("seed output would overwrite the input cache")
found = {name: entry for name, entry in checks(cache).items()
if (not args.include or any(fnmatch.fnmatchcase(name, pattern) for pattern in args.include))
and not any(fnmatch.fnmatchcase(name, pattern) for pattern in args.exclude)}
Path(args.seed).write_text("".join(f"set({quote(name)} {quote(value)} CACHE INTERNAL {quote(doc)})\n"
for name, (value, doc) in sorted(found.items())), encoding="utf-8")
seed.write_text("".join(f"set({quote(name)} {quote(value)} CACHE INTERNAL {quote(doc)})\n"
for name, (value, doc) in sorted(found.items())), encoding="utf-8")
print(f"{len(found)} check results")
return 0

Expand Down
141 changes: 113 additions & 28 deletions tools/tests/test_configure_seed.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import os
import shutil
import subprocess
import sys
Expand All @@ -24,17 +25,88 @@
"""


@unittest.skipUnless(shutil.which("ninja") and (shutil.which("gcc") or shutil.which("cc")), "needs ninja and a C compiler")
class ConfigureSeedTests(unittest.TestCase):
def test_check_names_with_spaces_round_trip_through_cmake(self):
cache = self.root / "named-check.txt"
cache.write_text('//Test HAVE FEATURE\nHAVE FEATURE:INTERNAL=1\n', encoding="utf-8")
output = self.configure("named-check", "-C", str(self.seed(cache, "named-check.cmake")))
self.assertNotRegex(output, r"CMake Error")
seeded = self.root / "named-check" / "CMakeCache.txt"
self.assertEqual(configure_seed.checks(seeded)["HAVE FEATURE"][0], "1")
self.assertNotIn("HAVE:INTERNAL=", seeded.read_text(encoding="utf-8"))
class ConfigureSeedPathTests(unittest.TestCase):
def setUp(self):
self.directory = tempfile.TemporaryDirectory()
self.addCleanup(self.directory.cleanup)
self.root = Path(self.directory.name)
self.cache = self.root / "CMakeCache.txt"
self.original = b"//Path to a program.\nCMAKE_AR:FILEPATH=/usr/bin/ar\n//Test HAVE_FEATURE\nHAVE_FEATURE:INTERNAL=1\n"
self.cache.write_bytes(self.original)

def run_seed(self, seed, *options):
return subprocess.run([sys.executable, str(TOOL), str(self.cache), str(seed), *options],
capture_output=True, text=True)

def assert_rejected(self, seed, *options):
result = self.run_seed(seed, *options)
self.assertEqual(result.returncode, 2, result.stderr)
self.assertEqual(result.stdout, "")
self.assertIn("seed output would overwrite the input cache", result.stderr)
self.assertNotIn("Traceback", result.stderr)
self.assertEqual(self.cache.read_bytes(), self.original)
self.assertEqual(Path(seed).read_bytes(), self.original)

def test_same_path_preserves_cache(self):
self.assert_rejected(self.cache)

def test_normalized_path_preserves_cache(self):
(self.root / "nested").mkdir()
self.assert_rejected(self.root / "nested" / ".." / self.cache.name)

def test_hard_link_preserves_cache(self):
seed = self.root / "seed.cmake"
os.link(self.cache, seed)
self.assert_rejected(seed)

def test_symbolic_link_preserves_cache(self):
seed = self.root / "seed.cmake"
try:
seed.symlink_to(self.cache)
except OSError as error:
self.skipTest(f"symbolic links unavailable: {error}")
self.assert_rejected(seed)

def test_distinct_output_preserves_cache(self):
seed = self.root / "seed.cmake"
for existing in (False, True):
with self.subTest(existing=existing):
if existing:
seed.write_text("old seed", encoding="utf-8")
result = self.run_seed(seed)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(result.stdout, "1 check results\n")
self.assertEqual(seed.read_text(encoding="utf-8"),
'set("HAVE_FEATURE" "1" CACHE INTERNAL "Test HAVE_FEATURE")\n')
self.assertEqual(self.cache.read_bytes(), self.original)

def test_filters_do_not_allow_overwriting_cache(self):
for options in (("--include", "HAVE_*"), ("--include", "NO_MATCH_*"), ("--exclude", "HAVE_*")):
with self.subTest(options=options):
self.assert_rejected(self.cache, *options)

def test_filters_preserve_cache_with_distinct_output(self):
self.original += b"//Test OTHER_CHECK\nOTHER_CHECK:INTERNAL=0\n"
self.cache.write_bytes(self.original)
seed = self.root / "seed.cmake"
feature = 'set("HAVE_FEATURE" "1" CACHE INTERNAL "Test HAVE_FEATURE")\n'
other = 'set("OTHER_CHECK" "0" CACHE INTERNAL "Test OTHER_CHECK")\n'
cases = (
(("--include", "HAVE_*", "--include", "OTHER_*"), feature + other, 2),
(("--include", "*", "--exclude", "OTHER_*"), feature, 1),
(("--include", "have_*"), "", 0),
)
for options, expected, count in cases:
with self.subTest(options=options):
result = self.run_seed(seed, *options)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(result.stdout, f"{count} check results\n")
self.assertEqual(seed.read_text(encoding="utf-8"), expected)
self.assertEqual(self.cache.read_bytes(), self.original)


@unittest.skipUnless(shutil.which("ninja") and (shutil.which("gcc") or shutil.which("cc")), "needs ninja and a C compiler")
class ConfigureSeedProject(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.directory = tempfile.TemporaryDirectory()
Expand All @@ -58,31 +130,14 @@ def seed(self, cache, name):
subprocess.run([sys.executable, str(TOOL), str(cache), str(seed)], check=True, capture_output=True)
return seed

def test_seeded_configure_runs_no_check_and_gets_the_same_results(self):
self.assertRegex(self.fresh, r"Looking for|Performing Test")
output = self.configure("seeded", "-C", str(self.seed(self.root / "fresh" / "CMakeCache.txt", "seed.cmake")))
self.assertNotRegex(output, r"Looking for|Performing Test")
fresh = configure_seed.checks(self.root / "fresh" / "CMakeCache.txt")
self.assertEqual(configure_seed.checks(self.root / "seeded" / "CMakeCache.txt"), fresh)
self.assertEqual(fresh["MINI_HAVE_STDIO_H"][0], "1")
self.assertEqual(fresh["MINI_FAILS"][0], "")

class ConfigureSeedTests(ConfigureSeedProject):
def test_only_check_results_are_seeded(self):
found = configure_seed.checks(self.root / "fresh" / "CMakeCache.txt")
self.assertEqual({n for n in found if n.startswith("MINI_")},
{"MINI_HAVE_STDIO_H", "MINI_HAVE_MISSING_H", "MINI_HAVE_PRINTF", "MINI_COMPILES", "MINI_FAILS"})
self.assertNotIn("CMAKE_C_COMPILER", found)

def test_values_with_cmake_syntax_round_trip(self):
cache = self.root / "odd" / "CMakeCache.txt"
cache.parent.mkdir()
value = r'a;b "c" \d ${e} $f'
cache.write_text(f"//Test MINI_ODD\nMINI_ODD:INTERNAL={value}\n", encoding="utf-8")
self.configure("odd-seeded", "-C", str(self.seed(cache, "odd.cmake")))
seeded = (self.root / "odd-seeded" / "CMakeCache.txt").read_text(encoding="utf-8")
self.assertIn(f"MINI_ODD:INTERNAL={value}\n", seeded)
self.assertEqual(configure_seed.checks(self.root / "odd-seeded" / "CMakeCache.txt")["MINI_ODD"][0], value)

def test_cache_without_checks_writes_an_empty_seed(self):
cache = self.root / "empty" / "CMakeCache.txt"
cache.parent.mkdir()
Expand Down Expand Up @@ -112,6 +167,36 @@ def test_seed_filters_leave_excluded_checks_to_the_new_configure(self):
self.assertEqual(seed.read_text(encoding="utf-8"), "")


class ConfigureSeedRoundTripTests(ConfigureSeedProject):
def test_check_names_with_spaces_round_trip_through_cmake(self):
cache = self.root / "named-check.txt"
cache.write_text('//Test HAVE FEATURE\nHAVE FEATURE:INTERNAL=1\n', encoding="utf-8")
output = self.configure("named-check", "-C", str(self.seed(cache, "named-check.cmake")))
self.assertNotRegex(output, r"CMake Error")
seeded = self.root / "named-check" / "CMakeCache.txt"
self.assertEqual(configure_seed.checks(seeded)["HAVE FEATURE"][0], "1")
self.assertNotIn("HAVE:INTERNAL=", seeded.read_text(encoding="utf-8"))

def test_seeded_configure_runs_no_check_and_gets_the_same_results(self):
self.assertRegex(self.fresh, r"Looking for|Performing Test")
output = self.configure("seeded", "-C", str(self.seed(self.root / "fresh" / "CMakeCache.txt", "seed.cmake")))
self.assertNotRegex(output, r"Looking for|Performing Test")
fresh = configure_seed.checks(self.root / "fresh" / "CMakeCache.txt")
self.assertEqual(configure_seed.checks(self.root / "seeded" / "CMakeCache.txt"), fresh)
self.assertEqual(fresh["MINI_HAVE_STDIO_H"][0], "1")
self.assertEqual(fresh["MINI_FAILS"][0], "")

def test_values_with_cmake_syntax_round_trip(self):
cache = self.root / "odd" / "CMakeCache.txt"
cache.parent.mkdir()
value = r'a;b "c" \d ${e} $f'
cache.write_text(f"//Test MINI_ODD\nMINI_ODD:INTERNAL={value}\n", encoding="utf-8")
self.configure("odd-seeded", "-C", str(self.seed(cache, "odd.cmake")))
seeded = (self.root / "odd-seeded" / "CMakeCache.txt").read_text(encoding="utf-8")
self.assertIn(f"MINI_ODD:INTERNAL={value}\n", seeded)
self.assertEqual(configure_seed.checks(self.root / "odd-seeded" / "CMakeCache.txt")["MINI_ODD"][0], value)



class ChecksParsingTests(unittest.TestCase):
def test_checks_reads_cache_with_utf8_bom(self):
Expand Down
Loading