Fix for x86_64 build fail
[platform/upstream/connectedhomeip.git] / third_party / pigweed / repo / pw_build / py / python_runner_test.py
1 #!/usr/bin/env python3
2 # Copyright 2020 The Pigweed Authors
3 #
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
6 # the License at
7 #
8 #     https://www.apache.org/licenses/LICENSE-2.0
9 #
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
14 # the License.
15 """Tests for the Python runner."""
16
17 import os
18 from pathlib import Path
19 import platform
20 import tempfile
21 import unittest
22
23 from pw_build.python_runner import ExpressionError, GnPaths, Label, TargetInfo
24 from pw_build.python_runner import expand_expressions
25
26 ROOT = Path(r'C:\gn_root' if platform.system() == 'Windows' else '/gn_root')
27
28 TEST_PATHS = GnPaths(
29     ROOT,
30     ROOT / 'out',
31     ROOT / 'some' / 'cwd',
32     '//toolchains/cool:ToolChain',
33 )
34
35
36 class LabelTest(unittest.TestCase):
37     """Tests GN label parsing."""
38     def setUp(self):
39         self._paths_and_toolchain_name = [
40             (TEST_PATHS, 'ToolChain'),
41             (GnPaths(*TEST_PATHS[:3], ''), ''),
42         ]
43
44     def test_root(self):
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'))
53
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'))
63
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'))
73
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'))
83
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'))
89             self.assertEqual(
90                 label.out_dir,
91                 ROOT.joinpath('out', toolchain, 'obj/some/cwd/tgt'))
92             self.assertEqual(
93                 label.gen_dir,
94                 ROOT.joinpath('out', toolchain, 'gen/some/cwd/tgt'))
95
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'))
105
106
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')
115
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')
120
121
122 NINJA_EXECUTABLE = '''\
123 defines =
124 framework_dirs =
125 include_dirs = -I../fake_module/public
126 cflags = -g3 -Og -fdiagnostics-color -g -fno-common -Wall -Wextra -Werror
127 cflags_c =
128 cflags_cc = -fno-rtti -Wnon-virtual-dtor -std=c++17 -Wno-register
129 target_output_name = this_is_a_test
130
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
133
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
136   libs =
137   frameworks =
138   output_extension =
139   output_dir = host_clang_debug/obj/fake_module/test
140 '''
141
142 _SOURCE_SET_TEMPLATE = '''\
143 defines =
144 framework_dirs =
145 include_dirs = -I../fake_module/public
146 cflags = -g3 -Og -fdiagnostics-color -g -fno-common -Wall -Wextra -Werror
147 cflags_c =
148 cflags_cc = -fno-rtti -Wnon-virtual-dtor -std=c++17 -Wno-register
149 target_output_name = this_is_a_test
150
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
153
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
156   libs =
157   frameworks =
158   output_extension =
159   output_dir = host_clang_debug/obj/fake_module
160 '''
161
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:'
167
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)
170
171
172 def _create_ninja_files(source_set: str) -> tuple:
173     tempdir = tempfile.TemporaryDirectory(prefix='pw_build_test_')
174
175     module = Path(tempdir.name, 'out', 'fake_toolchain', 'obj', 'fake_module')
176     os.makedirs(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')
180
181     outdir = Path(tempdir.name, 'out', 'fake_toolchain', 'obj', 'fake_module')
182
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')
187
188     return tempdir, outdir, paths
189
190
191 class TargetTest(unittest.TestCase):
192     """Tests querying GN target information."""
193     def setUp(self):
194         self._tempdir, self._outdir, self._paths = _create_ninja_files(
195             NINJA_SOURCE_SET)
196
197     def tearDown(self):
198         self._tempdir.cleanup()
199
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)
204
205     def test_source_set_object_files(self):
206         target = TargetInfo(self._paths, '//fake_module:fake_source_set')
207         self.assertTrue(target.generated)
208         self.assertEqual(
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',
212             })
213
214     def test_executable_object_files(self):
215         target = TargetInfo(self._paths, '//fake_module:fake_test')
216         self.assertEqual(
217             set(target.object_files), {
218                 self._outdir / 'fake_test.fake_test.cc.o',
219                 self._outdir / 'fake_test.fake_test_c.c.o',
220             })
221
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')
226
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)
232
233     def test_non_existent_toolchain(self):
234         target = TargetInfo(
235             self._paths, '//fake_module:fake_source_set(//not_a:toolchain)')
236         self.assertFalse(target.generated)
237         self.assertIsNone(target.artifact)
238
239
240 class StampTargetTest(TargetTest):
241     """Test with old-style .stamp files instead of phony Ninja targets."""
242     def setUp(self):
243         self._tempdir, self._outdir, self._paths = _create_ninja_files(
244             NINJA_SOURCE_SET_STAMP)
245
246
247 class ExpandExpressionsTest(unittest.TestCase):
248     """Tests expansion of expressions like <TARGET_FILE(//foo)>."""
249     def setUp(self):
250         self._tempdir, self._outdir, self._paths = _create_ninja_files(
251             NINJA_SOURCE_SET)
252
253     def tearDown(self):
254         self._tempdir.cleanup()
255
256     def _path(self, *segments: str, create: bool = False) -> str:
257         path = Path(self._outdir, *segments)
258         if create:
259             os.makedirs(path.parent)
260             path.touch()
261         else:
262             assert not path.exists()
263         return str(path)
264
265     def test_empty(self):
266         self.assertEqual(list(expand_expressions(self._paths, '')), [''])
267
268     def test_no_expressions(self):
269         self.assertEqual(list(expand_expressions(self._paths, 'foobar')),
270                          ['foobar'])
271         self.assertEqual(
272             list(expand_expressions(self._paths, '<NOT_AN_EXPRESSION()>')),
273             ['<NOT_AN_EXPRESSION()>'])
274
275     def test_incomplete_expression(self):
276         for incomplete_expression in [
277                 '<TARGET_FILE(',
278                 '<TARGET_FILE(//foo)',
279                 '<TARGET_FILE(//foo>',
280                 '<TARGET_FILE(//foo) >',
281                 '--arg=<TARGET_FILE_IF_EXISTS(//foo) Hello>',
282         ]:
283             with self.assertRaises(ExpressionError):
284                 expand_expressions(self._paths, incomplete_expression)
285
286     def test_target_file(self):
287         path = self._path('test', 'fake_test.elf')
288
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}'),
295         ]:
296             self.assertEqual(list(expand_expressions(self._paths, expr)),
297                              [expected])
298
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)>')
303
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)>')
307
308     def test_target_file_if_exists(self):
309         path = self._path('test', 'fake_test.elf', create=True)
310
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)>',
314              f'--arg={path}'),
315             ('--argument=<TARGET_FILE_IF_EXISTS(//fake_module:fake_test)>;'
316              '<TARGET_FILE_IF_EXISTS(//fake_module:fake_test)>',
317              f'--argument={path};{path}'),
318         ]:
319             self.assertEqual(list(expand_expressions(self._paths, expr)),
320                              [expected])
321
322     def test_target_file_if_exists_arg_omitted(self):
323         for expr in [
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)>',
330         ]:
331             self.assertEqual(list(expand_expressions(self._paths, expr)), [])
332
333     def test_target_file_if_exists_error_if_never_has_artifact(self):
334         for expr in [
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)>',
339         ]:
340             with self.assertRaises(ExpressionError):
341                 expand_expressions(self._paths, expr)
342
343     def test_target_objects(self):
344         self.assertEqual(
345             set(
346                 expand_expressions(
347                     self._paths,
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')
351                     })
352         self.assertEqual(
353             set(
354                 expand_expressions(
355                     self._paths, '<TARGET_OBJECTS(//fake_module:fake_test)>')),
356             {
357                 self._path('fake_test.fake_test.cc.o'),
358                 self._path('fake_test.fake_test_c.c.o')
359             })
360
361     def test_target_objects_no_objects(self):
362         self.assertEqual(
363             list(
364                 expand_expressions(
365                     self._paths,
366                     '<TARGET_OBJECTS(//fake_module:fake_no_objects)>')), [])
367
368     def test_target_objects_other_content_in_arg(self):
369         for arg in [
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)>',
377         ]:
378             with self.assertRaises(ExpressionError):
379                 expand_expressions(self._paths, arg)
380
381     def test_target_objects_non_existent_target(self):
382         with self.assertRaisesRegex(ExpressionError, 'generated'):
383             expand_expressions(self._paths, '<TARGET_OBJECTS(//not_real)>')
384
385
386 class StampExpandExpressionsTest(TargetTest):
387     """Test with old-style .stamp files instead of phony Ninja targets."""
388     def setUp(self):
389         self._tempdir, self._outdir, self._paths = _create_ninja_files(
390             NINJA_SOURCE_SET_STAMP)
391
392
393 if __name__ == '__main__':
394     unittest.main()