Skip to content

Commit 79e62f1

Browse files
committed
create tests
1 parent 665eba4 commit 79e62f1

File tree

2 files changed

+355
-0
lines changed

2 files changed

+355
-0
lines changed

tools/cmake/common/__init__.py

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
# Copyright (c) Meta Platforms, Inc. and affiliates.
2+
# All rights reserved.
3+
#
4+
# This source code is licensed under the BSD-style license found in the
5+
# LICENSE file in the root directory of this source tree.
6+
7+
import os
8+
import shutil
9+
import subprocess
10+
import tempfile
11+
import unittest
12+
from functools import cache
13+
from typing import Any, Dict, List, Optional
14+
15+
# Files to copy from this directory into the temporary workspaces.
16+
TESTABLE_CMAKE_FILES = [
17+
"preset.cmake",
18+
]
19+
20+
21+
# If KEEP_WORKSPACE is set, then keep the workspace instead of deleting it. Useful
22+
# when debugging tests.
23+
@cache
24+
def _keep_workspace() -> bool:
25+
keep_workspace_env = os.environ.get("KEEP_WORKSPACE")
26+
if keep_workspace_env is None:
27+
return False
28+
return keep_workspace_env.lower() not in ("false", "0", "no", "n")
29+
30+
31+
# Create a file tree in the current working directory (cwd). The structure of the
32+
# tree maps to the structure of the file tree. The key of the tree is the name
33+
# of the folder or file. If the value is dict, it creates a folder. If the value
34+
# is a string, it creates a file.
35+
#
36+
# Example:
37+
#
38+
# {
39+
# "README.md": "this is a read me file",
40+
# "build": {
41+
# "cmake": {
42+
# "utils.cmake": "this is a cmake file",
43+
# }
44+
# }
45+
# }
46+
# Results in:
47+
#
48+
# ├── README.md
49+
# └── build
50+
# └── cmake
51+
# └── utils.cmake
52+
#
53+
def _create_file_tree(tree: Dict[Any, Any], cwd: str) -> None:
54+
for name, value in tree.items():
55+
if isinstance(value, str):
56+
file_path = os.path.join(cwd, name)
57+
assert not os.path.exists(file_path), f"file already exists: {file_path}"
58+
os.makedirs(cwd, exist_ok=True)
59+
with open(file_path, "w") as new_file:
60+
new_file.write(value)
61+
elif isinstance(value, dict):
62+
new_cwd = os.path.join(cwd, name)
63+
os.makedirs(new_cwd, exist_ok=True)
64+
_create_file_tree(tree=value, cwd=new_cwd)
65+
else:
66+
raise AssertionError("invalid tree value", value)
67+
68+
69+
# Get the key/value pair listed in a CMakeCache.txt file.
70+
@cache
71+
def _list_cmake_cache(cache_path: str) -> Dict[str, str]:
72+
result = {}
73+
with open(cache_path, "r") as cache_file:
74+
for line in cache_file:
75+
line = line.strip()
76+
if "=" in line:
77+
key, value = line.split("=", 1)
78+
if ":" in key:
79+
key, _ = key.split(":")
80+
result[key.strip()] = value.strip()
81+
return result
82+
83+
84+
class CMakeTestCase(unittest.TestCase):
85+
86+
def tearDown(self) -> None:
87+
super().tearDown()
88+
89+
if self.workspace and not _keep_workspace():
90+
shutil.rmtree(self.workspace)
91+
self.assertFalse(os.path.exists(self.workspace))
92+
93+
def create_workspace(self, tree: Dict[Any, Any]) -> None:
94+
self.workspace = tempfile.mkdtemp()
95+
if _keep_workspace():
96+
print("created workspace", self.workspace)
97+
98+
# Copy testable tree
99+
this_file_dir = os.path.dirname(os.path.abspath(__file__))
100+
for testable_cmake_file in TESTABLE_CMAKE_FILES:
101+
source_path = os.path.join(this_file_dir, testable_cmake_file)
102+
assert os.path.exists(
103+
source_path
104+
), f"{testable_cmake_file} does not exist in {source_path}"
105+
destination_path = os.path.join(self.workspace, testable_cmake_file)
106+
os.makedirs(os.path.dirname(destination_path), exist_ok=True)
107+
shutil.copy(source_path, destination_path)
108+
109+
_create_file_tree(tree=tree, cwd=self.workspace)
110+
111+
def assert_file_content(self, relativePath: str, expectedContent: str) -> None:
112+
path = os.path.join(self.workspace, relativePath)
113+
self.assertTrue(os.path.exists(path), f"expected path does not exist: {path}")
114+
115+
with open(path, "r") as path_file:
116+
self.assertEqual(path_file.read(), expectedContent)
117+
118+
def run_cmake(
119+
self,
120+
cmake_args: Optional[List[str]] = None,
121+
error_contains: Optional[str] = None,
122+
):
123+
cmake_args = (cmake_args or []) + ["--no-warn-unused-cli"]
124+
125+
result = subprocess.run(
126+
["cmake", *cmake_args, "-S", ".", "-B", "cmake-out"],
127+
cwd=self.workspace,
128+
stdout=subprocess.DEVNULL,
129+
stderr=subprocess.PIPE if error_contains else None,
130+
check=False,
131+
)
132+
133+
if error_contains is not None:
134+
self.assertNotEqual(result.returncode, 0)
135+
self.assertTrue(error_contains in result.stderr.decode("utf-8"))
136+
else:
137+
self.assertEqual(result.returncode, 0)
138+
self.assertTrue(os.path.exists(os.path.join(self.workspace, "cmake-out")))
139+
140+
def assert_cmake_cache(self, key: str, expected: str):
141+
cache = _list_cmake_cache(
142+
os.path.join(self.workspace, "cmake-out", "CMakeCache.txt")
143+
)
144+
self.assertEqual(cache[key], expected, f"invalid value for {key}")

tools/cmake/common/preset_test.py

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
# Copyright (c) Meta Platforms, Inc. and affiliates.
2+
# All rights reserved.
3+
#
4+
# This source code is licensed under the BSD-style license found in the
5+
# LICENSE file in the root directory of this source tree.
6+
7+
import os
8+
9+
from tools.cmake.common import CMakeTestCase, TESTABLE_CMAKE_FILES
10+
11+
12+
class TestPreset(CMakeTestCase):
13+
14+
def test_create_workspace(self):
15+
self.create_workspace(
16+
{
17+
".gitignore": ".DS_Store",
18+
"CMakeLists.txt": "move fast",
19+
"README.md": "Meta Platforms",
20+
"example": {
21+
"CMakeLists.txt": "move faster",
22+
"cmake": {
23+
"README.md": "godspeed you!",
24+
},
25+
},
26+
}
27+
)
28+
29+
self.assertIsNotNone(self.workspace)
30+
self.assert_file_content("CMakeLists.txt", "move fast")
31+
self.assert_file_content("README.md", "Meta Platforms")
32+
self.assert_file_content(".gitignore", ".DS_Store")
33+
self.assert_file_content("example/CMakeLists.txt", "move faster")
34+
self.assert_file_content("example/cmake/README.md", "godspeed you!")
35+
36+
# Test implicitly copied cmake files
37+
this_file_dir = os.path.dirname(os.path.abspath(__file__))
38+
self.assertTrue(len(TESTABLE_CMAKE_FILES) > 0)
39+
for testable_cmake_file in TESTABLE_CMAKE_FILES:
40+
with open(
41+
os.path.join(this_file_dir, testable_cmake_file), "r"
42+
) as source_file:
43+
self.assert_file_content(testable_cmake_file, source_file.read())
44+
45+
def test_set_option(self):
46+
_cmake_lists_txt = """
47+
cmake_minimum_required(VERSION 3.24)
48+
project(test_preset)
49+
add_subdirectory(numbers)
50+
set(SECRET_MESSAGE "move fast" CACHE STRING "")
51+
"""
52+
_numbers_cmake_lists_txt = """
53+
set(PI 3.14 CACHE STRING "")
54+
"""
55+
56+
self.create_workspace(
57+
{
58+
"CMakeLists.txt": _cmake_lists_txt,
59+
"numbers": {
60+
"CMakeLists.txt": _numbers_cmake_lists_txt,
61+
},
62+
}
63+
)
64+
self.run_cmake()
65+
self.assert_cmake_cache("SECRET_MESSAGE", "move fast")
66+
self.assert_cmake_cache("PI", "3.14")
67+
68+
def test_define_overridable_config_invalid_name(self):
69+
_cmake_lists_txt = """
70+
cmake_minimum_required(VERSION 3.24)
71+
project(test_preset)
72+
include(${PROJECT_SOURCE_DIR}/preset.cmake)
73+
define_overridable_config(IAM_AN_INVALID_NAME "test example" "default value")
74+
"""
75+
self.create_workspace({"CMakeLists.txt": _cmake_lists_txt})
76+
self.run_cmake(
77+
error_contains="Config name 'IAM_AN_INVALID_NAME' must start with EXECUTORCH_"
78+
)
79+
80+
def test_define_overridable_config_default(self):
81+
_cmake_lists_txt = """
82+
cmake_minimum_required(VERSION 3.24)
83+
project(test_preset)
84+
include(${PROJECT_SOURCE_DIR}/preset.cmake)
85+
add_subdirectory(example)
86+
"""
87+
_example_cmake_lists_txt = """
88+
define_overridable_config(EXECUTORCH_TEST_MESSAGE "test message" "default value")
89+
"""
90+
self.create_workspace(
91+
{
92+
"CMakeLists.txt": _cmake_lists_txt,
93+
"example": {
94+
"CMakeLists.txt": _example_cmake_lists_txt,
95+
},
96+
}
97+
)
98+
self.run_cmake()
99+
self.assert_cmake_cache("EXECUTORCH_TEST_MESSAGE", "default value")
100+
101+
def test_define_overridable_config_cli_override(self):
102+
_cmake_lists_txt = """
103+
cmake_minimum_required(VERSION 3.24)
104+
project(test_preset)
105+
include(${PROJECT_SOURCE_DIR}/preset.cmake)
106+
add_subdirectory(example)
107+
"""
108+
_example_cmake_lists_txt = """
109+
define_overridable_config(EXECUTORCH_TEST_MESSAGE "test message" "default value")
110+
"""
111+
self.create_workspace(
112+
{
113+
"CMakeLists.txt": _cmake_lists_txt,
114+
"example": {
115+
"CMakeLists.txt": _example_cmake_lists_txt,
116+
},
117+
}
118+
)
119+
self.run_cmake(cmake_args=["-DEXECUTORCH_TEST_MESSAGE='cli value'"])
120+
self.assert_cmake_cache("EXECUTORCH_TEST_MESSAGE", "cli value")
121+
122+
def test_define_overridable_config_set_override_before(self):
123+
_cmake_lists_txt = """
124+
cmake_minimum_required(VERSION 3.24)
125+
project(test_preset)
126+
include(${PROJECT_SOURCE_DIR}/preset.cmake)
127+
set(EXECUTORCH_TEST_MESSAGE "set value")
128+
add_subdirectory(example)
129+
"""
130+
_example_cmake_lists_txt = """
131+
define_overridable_config(EXECUTORCH_TEST_MESSAGE "test message" "default value")
132+
"""
133+
self.create_workspace(
134+
{
135+
"CMakeLists.txt": _cmake_lists_txt,
136+
"example": {
137+
"CMakeLists.txt": _example_cmake_lists_txt,
138+
},
139+
}
140+
)
141+
self.run_cmake()
142+
self.assert_cmake_cache("EXECUTORCH_TEST_MESSAGE", "set value")
143+
144+
def testdefine_overridable_config_set_override_after(self):
145+
_cmake_lists_txt = """
146+
cmake_minimum_required(VERSION 3.24)
147+
project(test_preset)
148+
include(${PROJECT_SOURCE_DIR}/preset.cmake)
149+
add_subdirectory(example)
150+
set(EXECUTORCH_TEST_MESSAGE "set value")
151+
"""
152+
_example_cmake_lists_txt = """
153+
define_overridable_config(EXECUTORCH_TEST_MESSAGE "test message" "default value")
154+
"""
155+
self.create_workspace(
156+
{
157+
"CMakeLists.txt": _cmake_lists_txt,
158+
"example": {
159+
"CMakeLists.txt": _example_cmake_lists_txt,
160+
},
161+
}
162+
)
163+
self.run_cmake()
164+
# Setting the value after should not affect the cache.
165+
self.assert_cmake_cache("EXECUTORCH_TEST_MESSAGE", "default value")
166+
167+
def test_define_overridable_config_set_override_after_with_cache(self):
168+
_cmake_lists_txt = """
169+
cmake_minimum_required(VERSION 3.24)
170+
project(test_preset)
171+
include(${PROJECT_SOURCE_DIR}/preset.cmake)
172+
add_subdirectory(example)
173+
set(EXECUTORCH_TEST_MESSAGE "set value" CACHE STRING "")
174+
"""
175+
_example_cmake_lists_txt = """
176+
define_overridable_config(EXECUTORCH_TEST_MESSAGE "test message" "default value")
177+
"""
178+
self.create_workspace(
179+
{
180+
"CMakeLists.txt": _cmake_lists_txt,
181+
"example": {
182+
"CMakeLists.txt": _example_cmake_lists_txt,
183+
},
184+
}
185+
)
186+
self.run_cmake()
187+
# Setting the value after should not affect the cache.
188+
self.assert_cmake_cache("EXECUTORCH_TEST_MESSAGE", "default value")
189+
190+
def test_define_overridable_config_cli_override_with_set_override(self):
191+
_cmake_lists_txt = """
192+
cmake_minimum_required(VERSION 3.24)
193+
project(test_preset)
194+
include(${PROJECT_SOURCE_DIR}/preset.cmake)
195+
set(EXECUTORCH_TEST_MESSAGE "set value")
196+
add_subdirectory(example)
197+
"""
198+
_example_cmake_lists_txt = """
199+
define_overridable_config(EXECUTORCH_TEST_MESSAGE "test message" "default value")
200+
"""
201+
self.create_workspace(
202+
{
203+
"CMakeLists.txt": _cmake_lists_txt,
204+
"example": {
205+
"CMakeLists.txt": _example_cmake_lists_txt,
206+
},
207+
}
208+
)
209+
self.run_cmake(cmake_args=["-DEXECUTORCH_TEST_MESSAGE='cli value'"])
210+
# If an option is set through cmake, it should NOT be overridable from the CLI.
211+
self.assert_cmake_cache("EXECUTORCH_TEST_MESSAGE", "set value")

0 commit comments

Comments
 (0)