Tests: Cover cmake_path(IS_PREFIX) path semantics

The IS_PREFIX test asserted four cases: duplicate separators collapsing,
a non-normalized '..' in the prefix, and two NORMALIZE cases.  None of
the rules a caller is most likely to get wrong were covered.

Add assertions for the non-strict self-prefix, trailing separators on
either operand, '.' components, the sibling-prefix trap, the absence of
relative-to-absolute reconciliation including the root directory, and
the empty path.  Add a host-path-model block covering backslashes, case
sensitivity of both ordinary components and the root-name, drive-
relative paths, and UNC root-names.
This commit is contained in:
Mickaël Germain
2026-09-10 07:09:39 -04:00
committed by Brad King
parent 59a096f73b
commit 3d53f7901b
+150
View File
@@ -24,4 +24,154 @@ if (NOT output)
list (APPEND errors "'${path} is not prefix of '/a/c/../b'")
endif()
# The test is not strict: a path is a prefix of itself.
set (prefix "/a/b")
cmake_path(IS_PREFIX prefix "/a/b" output)
if (NOT output)
list (APPEND errors "'${prefix}' is not prefix of '/a/b'")
endif()
# A trailing separator is an empty component, so it is significant on the
# prefix, which then needs a further component to consume it.
cmake_path(IS_PREFIX prefix "/a/b/" output)
if (NOT output)
list (APPEND errors "'${prefix}' is not prefix of '/a/b/'")
endif()
set (prefix "/a/b/")
cmake_path(IS_PREFIX prefix "/a/b" output)
if (output)
list (APPEND errors "'${prefix}' is prefix of '/a/b'")
endif()
cmake_path(IS_PREFIX prefix "/a/b/c" output)
if (NOT output)
list (APPEND errors "'${prefix}' is not prefix of '/a/b/c'")
endif()
# Without NORMALIZE, '.' and '..' are ordinary components, so a '..' escape
# is still a lexical prefix and a '.' component defeats the match.
set (prefix "/a/b")
cmake_path(IS_PREFIX prefix "/a/b/../../etc" output)
if (NOT output)
list (APPEND errors "'${prefix}' is not prefix of '/a/b/../../etc'")
endif()
cmake_path(IS_PREFIX prefix "/a/./b//c" output)
if (output)
list (APPEND errors "'${prefix}' is prefix of '/a/./b//c'")
endif()
# Components are compared whole, so a sibling sharing a textual prefix does
# not match.
cmake_path(IS_PREFIX prefix "/a/bc" output)
if (output)
list (APPEND errors "'${prefix}' is prefix of '/a/bc'")
endif()
# Relative paths are not made absolute, and the root directory is not an
# exception to that rule.
set (prefix "/a")
cmake_path(IS_PREFIX prefix "b/c" output)
if (output)
list (APPEND errors "'${prefix}' is prefix of 'b/c'")
endif()
set (prefix "b")
cmake_path(IS_PREFIX prefix "b/c" output)
if (NOT output)
list (APPEND errors "'${prefix}' is not prefix of 'b/c'")
endif()
set (prefix "/")
cmake_path(IS_PREFIX prefix "b/c" output)
if (output)
list (APPEND errors "'${prefix}' is prefix of 'b/c'")
endif()
cmake_path(IS_PREFIX prefix "/a/b" output)
if (NOT output)
list (APPEND errors "'${prefix}' is not prefix of '/a/b'")
endif()
# A '.' prefix matches only a path that spells '.' too.
set (prefix ".")
cmake_path(IS_PREFIX prefix "a/b" output)
if (output)
list (APPEND errors "'${prefix}' is prefix of 'a/b'")
endif()
cmake_path(IS_PREFIX prefix "./a/b" output)
if (NOT output)
list (APPEND errors "'${prefix}' is not prefix of './a/b'")
endif()
# The empty path is a prefix of every path, including itself.
set (prefix "")
cmake_path(IS_PREFIX prefix "/a/b" output)
if (NOT output)
list (APPEND errors "the empty path is not prefix of '/a/b'")
endif()
cmake_path(IS_PREFIX prefix "" output)
if (NOT output)
list (APPEND errors "the empty path is not prefix of itself")
endif()
set (prefix "/a")
cmake_path(IS_PREFIX prefix "" output)
if (output)
list (APPEND errors "'${prefix}' is prefix of the empty path")
endif()
# Comparison follows the host path model.
if(WIN32)
# Backslashes are separators, and are not converted.
set (prefix "C:\\a")
cmake_path(IS_PREFIX prefix "C:\\a\\b" output)
if (NOT output)
list (APPEND errors "'${prefix}' is not prefix of 'C:\\a\\b'")
endif()
# Comparison is case-sensitive even where the filesystem is not. The
# second case isolates the root-name, since ordinary components are
# case-sensitive under every path model.
set (prefix "c:/a")
cmake_path(IS_PREFIX prefix "C:/A/b" output)
if (output)
list (APPEND errors "'${prefix}' is prefix of 'C:/A/b'")
endif()
cmake_path(IS_PREFIX prefix "C:/a/b" output)
if (output)
list (APPEND errors "'${prefix}' is prefix of 'C:/a/b'")
endif()
# A drive-relative path has a root-name but no root-directory, so it is
# relative and is not reconciled against an absolute prefix.
set (prefix "C:/")
cmake_path(IS_PREFIX prefix "C:foo" output)
if (output)
list (APPEND errors "'${prefix}' is prefix of 'C:foo'")
endif()
else()
# Backslashes are ordinary filename characters.
set (prefix "C:\\a")
cmake_path(IS_PREFIX prefix "C:\\a\\b" output)
if (output)
list (APPEND errors "'${prefix}' is prefix of 'C:\\a\\b'")
endif()
endif()
if(WIN32 OR CYGWIN)
# '//host/share' is a root-name, not a doubled separator.
set (prefix "//host/share")
cmake_path(IS_PREFIX prefix "//host/share/a" output)
if (NOT output)
list (APPEND errors "'${prefix}' is not prefix of '//host/share/a'")
endif()
set (prefix "/a")
cmake_path(IS_PREFIX prefix "//a/b" output)
if (output)
list (APPEND errors "'${prefix}' is prefix of '//a/b'")
endif()
else()
# A doubled leading separator collapses.
set (prefix "/a")
cmake_path(IS_PREFIX prefix "//a/b" output)
if (NOT output)
list (APPEND errors "'${prefix}' is not prefix of '//a/b'")
endif()
endif()
check_errors (IS_PREFIX ${errors})