2 # Copyright 2020 The Pigweed Authors
4 # Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 # use this file except in compliance with the License. You may obtain a copy of
8 # https://www.apache.org/licenses/LICENSE-2.0
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 # License for the specific language governing permissions and limitations under
15 """Tests for the Python runner."""
18 from pathlib import Path
23 from pw_build.python_runner import ExpressionError, GnPaths, Label, TargetInfo
24 from pw_build.python_runner import expand_expressions
26 ROOT = Path(r'C:\gn_root' if platform.system() == 'Windows' else '/gn_root')
31 ROOT / 'some' / 'cwd',
32 '//toolchains/cool:ToolChain',
36 class LabelTest(unittest.TestCase):
37 """Tests GN label parsing."""
39 self._paths_and_toolchain_name = [
40 (TEST_PATHS, 'ToolChain'),
41 (GnPaths(*TEST_PATHS[:3], ''), ''),
45 for paths, toolchain in self._paths_and_toolchain_name:
46 label = Label(paths, '//')
47 self.assertEqual(label.name, '')
48 self.assertEqual(label.dir, ROOT)
49 self.assertEqual(label.out_dir,
50 ROOT.joinpath('out', toolchain, 'obj'))
51 self.assertEqual(label.gen_dir,
52 ROOT.joinpath('out', toolchain, 'gen'))
54 def test_absolute(self):
55 for paths, toolchain in self._paths_and_toolchain_name:
56 label = Label(paths, '//foo/bar:baz')
57 self.assertEqual(label.name, 'baz')
58 self.assertEqual(label.dir, ROOT.joinpath('foo/bar'))
59 self.assertEqual(label.out_dir,
60 ROOT.joinpath('out', toolchain, 'obj/foo/bar'))
61 self.assertEqual(label.gen_dir,
62 ROOT.joinpath('out', toolchain, 'gen/foo/bar'))
64 def test_absolute_implicit_target(self):
65 for paths, toolchain in self._paths_and_toolchain_name:
66 label = Label(paths, '//foo/bar')
67 self.assertEqual(label.name, 'bar')
68 self.assertEqual(label.dir, ROOT.joinpath('foo/bar'))
69 self.assertEqual(label.out_dir,
70 ROOT.joinpath('out', toolchain, 'obj/foo/bar'))
71 self.assertEqual(label.gen_dir,
72 ROOT.joinpath('out', toolchain, 'gen/foo/bar'))
74 def test_relative(self):
75 for paths, toolchain in self._paths_and_toolchain_name:
76 label = Label(paths, ':tgt')
77 self.assertEqual(label.name, 'tgt')
78 self.assertEqual(label.dir, ROOT.joinpath('some/cwd'))
79 self.assertEqual(label.out_dir,
80 ROOT.joinpath('out', toolchain, 'obj/some/cwd'))
81 self.assertEqual(label.gen_dir,
82 ROOT.joinpath('out', toolchain, 'gen/some/cwd'))
84 def test_relative_subdir(self):
85 for paths, toolchain in self._paths_and_toolchain_name:
86 label = Label(paths, 'tgt')
87 self.assertEqual(label.name, 'tgt')
88 self.assertEqual(label.dir, ROOT.joinpath('some/cwd/tgt'))
91 ROOT.joinpath('out', toolchain, 'obj/some/cwd/tgt'))
94 ROOT.joinpath('out', toolchain, 'gen/some/cwd/tgt'))
96 def test_relative_parent_dir(self):
97 for paths, toolchain in self._paths_and_toolchain_name:
98 label = Label(paths, '..:tgt')
99 self.assertEqual(label.name, 'tgt')
100 self.assertEqual(label.dir, ROOT.joinpath('some'))
101 self.assertEqual(label.out_dir,
102 ROOT.joinpath('out', toolchain, 'obj/some'))
103 self.assertEqual(label.gen_dir,
104 ROOT.joinpath('out', toolchain, 'gen/some'))
107 class ResolvePathTest(unittest.TestCase):
108 """Tests GN path resolution."""
109 def test_resolve_absolute(self):
110 self.assertEqual(TEST_PATHS.resolve('//'), TEST_PATHS.root)
111 self.assertEqual(TEST_PATHS.resolve('//foo/bar'),
112 TEST_PATHS.root / 'foo' / 'bar')
113 self.assertEqual(TEST_PATHS.resolve('//foo/../baz'),
114 TEST_PATHS.root / 'baz')
116 def test_resolve_relative(self):
117 self.assertEqual(TEST_PATHS.resolve(''), TEST_PATHS.cwd)
118 self.assertEqual(TEST_PATHS.resolve('foo'), TEST_PATHS.cwd / 'foo')
119 self.assertEqual(TEST_PATHS.resolve('..'), TEST_PATHS.root / 'some')
122 NINJA_EXECUTABLE = '''\
125 include_dirs = -I../fake_module/public
126 cflags = -g3 -Og -fdiagnostics-color -g -fno-common -Wall -Wextra -Werror
128 cflags_cc = -fno-rtti -Wnon-virtual-dtor -std=c++17 -Wno-register
129 target_output_name = this_is_a_test
131 build fake_toolchain/obj/fake_module/fake_test.fake_test.cc.o: fake_toolchain_cxx ../fake_module/fake_test.cc
132 build fake_toolchain/obj/fake_module/fake_test.fake_test_c.c.o: fake_toolchain_cc ../fake_module/fake_test_c.c
134 build fake_toolchain/obj/fake_module/test/fake_test.elf: fake_toolchain_link fake_toolchain/obj/fake_module/fake_test.fake_test.cc.o fake_toolchain/obj/fake_module/fake_test.fake_test_c.c.o
135 ldflags = -Og -fdiagnostics-color
139 output_dir = host_clang_debug/obj/fake_module/test
142 _SOURCE_SET_TEMPLATE = '''\
145 include_dirs = -I../fake_module/public
146 cflags = -g3 -Og -fdiagnostics-color -g -fno-common -Wall -Wextra -Werror
148 cflags_cc = -fno-rtti -Wnon-virtual-dtor -std=c++17 -Wno-register
149 target_output_name = this_is_a_test
151 build fake_toolchain/obj/fake_module/fake_source_set.file_a.cc.o: fake_toolchain_cxx ../fake_module/file_a.cc
152 build fake_toolchain/obj/fake_module/fake_source_set.file_b.c.o: fake_toolchain_cc ../fake_module/file_b.c
154 build {path} fake_toolchain/obj/fake_module/fake_source_set.file_a.cc.o fake_toolchain/obj/fake_module/fake_source_set.file_b.c.o
155 ldflags = -Og -fdiagnostics-color -Wno-error=deprecated
159 output_dir = host_clang_debug/obj/fake_module
162 # GN originally used empty .stamp files to mark the completion of a group of
163 # dependencies. GN switched to using 'phony' Ninja targets instead, which don't
164 # require creating a new file.
165 _PHONY_BUILD_PATH = 'fake_toolchain/phony/fake_module/fake_source_set: phony'
166 _STAMP_BUILD_PATH = 'fake_toolchain/obj/fake_module/fake_source_set.stamp:'
168 NINJA_SOURCE_SET = _SOURCE_SET_TEMPLATE.format(path=_PHONY_BUILD_PATH)
169 NINJA_SOURCE_SET_STAMP = _SOURCE_SET_TEMPLATE.format(path=_STAMP_BUILD_PATH)
172 def _create_ninja_files(source_set: str) -> tuple:
173 tempdir = tempfile.TemporaryDirectory(prefix='pw_build_test_')
175 module = Path(tempdir.name, 'out', 'fake_toolchain', 'obj', 'fake_module')
177 module.joinpath('fake_test.ninja').write_text(NINJA_EXECUTABLE)
178 module.joinpath('fake_source_set.ninja').write_text(source_set)
179 module.joinpath('fake_no_objects.ninja').write_text('\n')
181 outdir = Path(tempdir.name, 'out', 'fake_toolchain', 'obj', 'fake_module')
183 paths = GnPaths(root=Path(tempdir.name),
184 build=Path(tempdir.name, 'out'),
185 cwd=Path(tempdir.name, 'some', 'module'),
186 toolchain='//tools:fake_toolchain')
188 return tempdir, outdir, paths
191 class TargetTest(unittest.TestCase):
192 """Tests querying GN target information."""
194 self._tempdir, self._outdir, self._paths = _create_ninja_files(
198 self._tempdir.cleanup()
200 def test_source_set_artifact(self):
201 target = TargetInfo(self._paths, '//fake_module:fake_source_set')
202 self.assertTrue(target.generated)
203 self.assertIsNone(target.artifact)
205 def test_source_set_object_files(self):
206 target = TargetInfo(self._paths, '//fake_module:fake_source_set')
207 self.assertTrue(target.generated)
209 set(target.object_files), {
210 self._outdir / 'fake_source_set.file_a.cc.o',
211 self._outdir / 'fake_source_set.file_b.c.o',
214 def test_executable_object_files(self):
215 target = TargetInfo(self._paths, '//fake_module:fake_test')
217 set(target.object_files), {
218 self._outdir / 'fake_test.fake_test.cc.o',
219 self._outdir / 'fake_test.fake_test_c.c.o',
222 def test_executable_artifact(self):
223 target = TargetInfo(self._paths, '//fake_module:fake_test')
224 self.assertEqual(target.artifact,
225 self._outdir / 'test' / 'fake_test.elf')
227 def test_non_existent_target(self):
228 target = TargetInfo(self._paths,
229 '//fake_module:definitely_not_a_real_target')
230 self.assertFalse(target.generated)
231 self.assertIsNone(target.artifact)
233 def test_non_existent_toolchain(self):
235 self._paths, '//fake_module:fake_source_set(//not_a:toolchain)')
236 self.assertFalse(target.generated)
237 self.assertIsNone(target.artifact)
240 class StampTargetTest(TargetTest):
241 """Test with old-style .stamp files instead of phony Ninja targets."""
243 self._tempdir, self._outdir, self._paths = _create_ninja_files(
244 NINJA_SOURCE_SET_STAMP)
247 class ExpandExpressionsTest(unittest.TestCase):
248 """Tests expansion of expressions like <TARGET_FILE(//foo)>."""
250 self._tempdir, self._outdir, self._paths = _create_ninja_files(
254 self._tempdir.cleanup()
256 def _path(self, *segments: str, create: bool = False) -> str:
257 path = Path(self._outdir, *segments)
259 os.makedirs(path.parent)
262 assert not path.exists()
265 def test_empty(self):
266 self.assertEqual(list(expand_expressions(self._paths, '')), [''])
268 def test_no_expressions(self):
269 self.assertEqual(list(expand_expressions(self._paths, 'foobar')),
272 list(expand_expressions(self._paths, '<NOT_AN_EXPRESSION()>')),
273 ['<NOT_AN_EXPRESSION()>'])
275 def test_incomplete_expression(self):
276 for incomplete_expression in [
278 '<TARGET_FILE(//foo)',
279 '<TARGET_FILE(//foo>',
280 '<TARGET_FILE(//foo) >',
281 '--arg=<TARGET_FILE_IF_EXISTS(//foo) Hello>',
283 with self.assertRaises(ExpressionError):
284 expand_expressions(self._paths, incomplete_expression)
286 def test_target_file(self):
287 path = self._path('test', 'fake_test.elf')
289 for expr, expected in [
290 ('<TARGET_FILE(//fake_module:fake_test)>', path),
291 ('--arg=<TARGET_FILE(//fake_module:fake_test)>', f'--arg={path}'),
292 ('--argument=<TARGET_FILE(//fake_module:fake_test)>;'
293 '<TARGET_FILE(//fake_module:fake_test)>',
294 f'--argument={path};{path}'),
296 self.assertEqual(list(expand_expressions(self._paths, expr)),
299 def test_target_objects_no_target_file(self):
300 with self.assertRaisesRegex(ExpressionError, 'no output file'):
301 expand_expressions(self._paths,
302 '<TARGET_FILE(//fake_module:fake_source_set)>')
304 def test_target_file_non_existent_target(self):
305 with self.assertRaisesRegex(ExpressionError, 'generated'):
306 expand_expressions(self._paths, '<TARGET_FILE(//not_real:abc123)>')
308 def test_target_file_if_exists(self):
309 path = self._path('test', 'fake_test.elf', create=True)
311 for expr, expected in [
312 ('<TARGET_FILE_IF_EXISTS(//fake_module:fake_test)>', path),
313 ('--arg=<TARGET_FILE_IF_EXISTS(//fake_module:fake_test)>',
315 ('--argument=<TARGET_FILE_IF_EXISTS(//fake_module:fake_test)>;'
316 '<TARGET_FILE_IF_EXISTS(//fake_module:fake_test)>',
317 f'--argument={path};{path}'),
319 self.assertEqual(list(expand_expressions(self._paths, expr)),
322 def test_target_file_if_exists_arg_omitted(self):
324 '<TARGET_FILE_IF_EXISTS(//fake_module:fake_test)>',
325 '<TARGET_FILE_IF_EXISTS(//fake_module:fake_test(fake)>',
326 '<TARGET_FILE_IF_EXISTS(//not_a_module:nothing)>',
327 '--arg=<TARGET_FILE_IF_EXISTS(//fake_module:fake_test)>',
328 '--argument=<TARGET_FILE_IF_EXISTS(//fake_module:fake_test)>;'
329 '<TARGET_FILE_IF_EXISTS(//fake_module:fake_test)>',
331 self.assertEqual(list(expand_expressions(self._paths, expr)), [])
333 def test_target_file_if_exists_error_if_never_has_artifact(self):
335 '<TARGET_FILE_IF_EXISTS(//fake_module:fake_source_set)>'
336 'bar=<TARGET_FILE_IF_EXISTS(//fake_module:fake_source_set)>'
337 '<TARGET_FILE_IF_EXISTS(//fake_module:fake_no_objects)>',
338 '--foo=<TARGET_FILE_IF_EXISTS(//fake_module:fake_no_objects)>',
340 with self.assertRaises(ExpressionError):
341 expand_expressions(self._paths, expr)
343 def test_target_objects(self):
348 '<TARGET_OBJECTS(//fake_module:fake_source_set)>')), {
349 self._path('fake_source_set.file_a.cc.o'),
350 self._path('fake_source_set.file_b.c.o')
355 self._paths, '<TARGET_OBJECTS(//fake_module:fake_test)>')),
357 self._path('fake_test.fake_test.cc.o'),
358 self._path('fake_test.fake_test_c.c.o')
361 def test_target_objects_no_objects(self):
366 '<TARGET_OBJECTS(//fake_module:fake_no_objects)>')), [])
368 def test_target_objects_other_content_in_arg(self):
370 '--foo=<TARGET_OBJECTS(//fake_module:fake_no_objects)>',
371 '<TARGET_OBJECTS(//fake_module:fake_no_objects)>bar',
372 '--foo<TARGET_OBJECTS(//fake_module:fake_no_objects)>bar',
373 '<TARGET_OBJECTS(//fake_module:fake_no_objects)>'
374 '<TARGET_OBJECTS(//fake_module:fake_no_objects)>',
375 '<TARGET_OBJECTS(//fake_module:fake_source_set)>'
376 '<TARGET_OBJECTS(//fake_module:fake_source_set)>',
378 with self.assertRaises(ExpressionError):
379 expand_expressions(self._paths, arg)
381 def test_target_objects_non_existent_target(self):
382 with self.assertRaisesRegex(ExpressionError, 'generated'):
383 expand_expressions(self._paths, '<TARGET_OBJECTS(//not_real)>')
386 class StampExpandExpressionsTest(TargetTest):
387 """Test with old-style .stamp files instead of phony Ninja targets."""
389 self._tempdir, self._outdir, self._paths = _create_ninja_files(
390 NINJA_SOURCE_SET_STAMP)
393 if __name__ == '__main__':