You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
395 lines
16 KiB
395 lines
16 KiB
#!/usr/bin/env python3
|
|
# Copyright 2020 The Pigweed Authors
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
|
# use this file except in compliance with the License. You may obtain a copy of
|
|
# the License at
|
|
#
|
|
# https://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations under
|
|
# the License.
|
|
"""Tests for the Python runner."""
|
|
|
|
import os
|
|
from pathlib import Path
|
|
import platform
|
|
import tempfile
|
|
import unittest
|
|
|
|
from pw_build.python_runner import ExpressionError, GnPaths, Label, TargetInfo
|
|
from pw_build.python_runner import expand_expressions
|
|
|
|
ROOT = Path(r'C:\gn_root' if platform.system() == 'Windows' else '/gn_root')
|
|
|
|
TEST_PATHS = GnPaths(
|
|
ROOT,
|
|
ROOT / 'out',
|
|
ROOT / 'some' / 'cwd',
|
|
'//toolchains/cool:ToolChain',
|
|
)
|
|
|
|
|
|
class LabelTest(unittest.TestCase):
|
|
"""Tests GN label parsing."""
|
|
def setUp(self):
|
|
self._paths_and_toolchain_name = [
|
|
(TEST_PATHS, 'ToolChain'),
|
|
(GnPaths(*TEST_PATHS[:3], ''), ''),
|
|
]
|
|
|
|
def test_root(self):
|
|
for paths, toolchain in self._paths_and_toolchain_name:
|
|
label = Label(paths, '//')
|
|
self.assertEqual(label.name, '')
|
|
self.assertEqual(label.dir, ROOT)
|
|
self.assertEqual(label.out_dir,
|
|
ROOT.joinpath('out', toolchain, 'obj'))
|
|
self.assertEqual(label.gen_dir,
|
|
ROOT.joinpath('out', toolchain, 'gen'))
|
|
|
|
def test_absolute(self):
|
|
for paths, toolchain in self._paths_and_toolchain_name:
|
|
label = Label(paths, '//foo/bar:baz')
|
|
self.assertEqual(label.name, 'baz')
|
|
self.assertEqual(label.dir, ROOT.joinpath('foo/bar'))
|
|
self.assertEqual(label.out_dir,
|
|
ROOT.joinpath('out', toolchain, 'obj/foo/bar'))
|
|
self.assertEqual(label.gen_dir,
|
|
ROOT.joinpath('out', toolchain, 'gen/foo/bar'))
|
|
|
|
def test_absolute_implicit_target(self):
|
|
for paths, toolchain in self._paths_and_toolchain_name:
|
|
label = Label(paths, '//foo/bar')
|
|
self.assertEqual(label.name, 'bar')
|
|
self.assertEqual(label.dir, ROOT.joinpath('foo/bar'))
|
|
self.assertEqual(label.out_dir,
|
|
ROOT.joinpath('out', toolchain, 'obj/foo/bar'))
|
|
self.assertEqual(label.gen_dir,
|
|
ROOT.joinpath('out', toolchain, 'gen/foo/bar'))
|
|
|
|
def test_relative(self):
|
|
for paths, toolchain in self._paths_and_toolchain_name:
|
|
label = Label(paths, ':tgt')
|
|
self.assertEqual(label.name, 'tgt')
|
|
self.assertEqual(label.dir, ROOT.joinpath('some/cwd'))
|
|
self.assertEqual(label.out_dir,
|
|
ROOT.joinpath('out', toolchain, 'obj/some/cwd'))
|
|
self.assertEqual(label.gen_dir,
|
|
ROOT.joinpath('out', toolchain, 'gen/some/cwd'))
|
|
|
|
def test_relative_subdir(self):
|
|
for paths, toolchain in self._paths_and_toolchain_name:
|
|
label = Label(paths, 'tgt')
|
|
self.assertEqual(label.name, 'tgt')
|
|
self.assertEqual(label.dir, ROOT.joinpath('some/cwd/tgt'))
|
|
self.assertEqual(
|
|
label.out_dir,
|
|
ROOT.joinpath('out', toolchain, 'obj/some/cwd/tgt'))
|
|
self.assertEqual(
|
|
label.gen_dir,
|
|
ROOT.joinpath('out', toolchain, 'gen/some/cwd/tgt'))
|
|
|
|
def test_relative_parent_dir(self):
|
|
for paths, toolchain in self._paths_and_toolchain_name:
|
|
label = Label(paths, '..:tgt')
|
|
self.assertEqual(label.name, 'tgt')
|
|
self.assertEqual(label.dir, ROOT.joinpath('some'))
|
|
self.assertEqual(label.out_dir,
|
|
ROOT.joinpath('out', toolchain, 'obj/some'))
|
|
self.assertEqual(label.gen_dir,
|
|
ROOT.joinpath('out', toolchain, 'gen/some'))
|
|
|
|
|
|
class ResolvePathTest(unittest.TestCase):
|
|
"""Tests GN path resolution."""
|
|
def test_resolve_absolute(self):
|
|
self.assertEqual(TEST_PATHS.resolve('//'), TEST_PATHS.root)
|
|
self.assertEqual(TEST_PATHS.resolve('//foo/bar'),
|
|
TEST_PATHS.root / 'foo' / 'bar')
|
|
self.assertEqual(TEST_PATHS.resolve('//foo/../baz'),
|
|
TEST_PATHS.root / 'baz')
|
|
|
|
def test_resolve_relative(self):
|
|
self.assertEqual(TEST_PATHS.resolve(''), TEST_PATHS.cwd)
|
|
self.assertEqual(TEST_PATHS.resolve('foo'), TEST_PATHS.cwd / 'foo')
|
|
self.assertEqual(TEST_PATHS.resolve('..'), TEST_PATHS.root / 'some')
|
|
|
|
|
|
NINJA_EXECUTABLE = '''\
|
|
defines =
|
|
framework_dirs =
|
|
include_dirs = -I../fake_module/public
|
|
cflags = -g3 -Og -fdiagnostics-color -g -fno-common -Wall -Wextra -Werror
|
|
cflags_c =
|
|
cflags_cc = -fno-rtti -Wnon-virtual-dtor -std=c++17 -Wno-register
|
|
target_output_name = this_is_a_test
|
|
|
|
build fake_toolchain/obj/fake_module/fake_test.fake_test.cc.o: fake_toolchain_cxx ../fake_module/fake_test.cc
|
|
build fake_toolchain/obj/fake_module/fake_test.fake_test_c.c.o: fake_toolchain_cc ../fake_module/fake_test_c.c
|
|
|
|
build fake_toolchain/obj/fake_module/test/fake_test.elf fake_toolchain/obj/fake_module/test/fake_test.map: 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
|
|
ldflags = -Og -fdiagnostics-color
|
|
libs =
|
|
frameworks =
|
|
output_extension =
|
|
output_dir = host_clang_debug/obj/fake_module/test
|
|
'''
|
|
|
|
_SOURCE_SET_TEMPLATE = '''\
|
|
defines =
|
|
framework_dirs =
|
|
include_dirs = -I../fake_module/public
|
|
cflags = -g3 -Og -fdiagnostics-color -g -fno-common -Wall -Wextra -Werror
|
|
cflags_c =
|
|
cflags_cc = -fno-rtti -Wnon-virtual-dtor -std=c++17 -Wno-register
|
|
target_output_name = this_is_a_test
|
|
|
|
build fake_toolchain/obj/fake_module/fake_source_set.file_a.cc.o: fake_toolchain_cxx ../fake_module/file_a.cc
|
|
build fake_toolchain/obj/fake_module/fake_source_set.file_b.c.o: fake_toolchain_cc ../fake_module/file_b.c
|
|
|
|
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
|
|
ldflags = -Og -fdiagnostics-color -Wno-error=deprecated
|
|
libs =
|
|
frameworks =
|
|
output_extension =
|
|
output_dir = host_clang_debug/obj/fake_module
|
|
'''
|
|
|
|
# GN originally used empty .stamp files to mark the completion of a group of
|
|
# dependencies. GN switched to using 'phony' Ninja targets instead, which don't
|
|
# require creating a new file.
|
|
_PHONY_BUILD_PATH = 'fake_toolchain/phony/fake_module/fake_source_set: phony'
|
|
_STAMP_BUILD_PATH = 'fake_toolchain/obj/fake_module/fake_source_set.stamp:'
|
|
|
|
NINJA_SOURCE_SET = _SOURCE_SET_TEMPLATE.format(path=_PHONY_BUILD_PATH)
|
|
NINJA_SOURCE_SET_STAMP = _SOURCE_SET_TEMPLATE.format(path=_STAMP_BUILD_PATH)
|
|
|
|
|
|
def _create_ninja_files(source_set: str) -> tuple:
|
|
tempdir = tempfile.TemporaryDirectory(prefix='pw_build_test_')
|
|
|
|
module = Path(tempdir.name, 'out', 'fake_toolchain', 'obj', 'fake_module')
|
|
os.makedirs(module)
|
|
module.joinpath('fake_test.ninja').write_text(NINJA_EXECUTABLE)
|
|
module.joinpath('fake_source_set.ninja').write_text(source_set)
|
|
module.joinpath('fake_no_objects.ninja').write_text('\n')
|
|
|
|
outdir = Path(tempdir.name, 'out', 'fake_toolchain', 'obj', 'fake_module')
|
|
|
|
paths = GnPaths(root=Path(tempdir.name),
|
|
build=Path(tempdir.name, 'out'),
|
|
cwd=Path(tempdir.name, 'some', 'module'),
|
|
toolchain='//tools:fake_toolchain')
|
|
|
|
return tempdir, outdir, paths
|
|
|
|
|
|
class TargetTest(unittest.TestCase):
|
|
"""Tests querying GN target information."""
|
|
def setUp(self):
|
|
self._tempdir, self._outdir, self._paths = _create_ninja_files(
|
|
NINJA_SOURCE_SET)
|
|
|
|
def tearDown(self):
|
|
self._tempdir.cleanup()
|
|
|
|
def test_source_set_artifact(self):
|
|
target = TargetInfo(self._paths, '//fake_module:fake_source_set')
|
|
self.assertTrue(target.generated)
|
|
self.assertIsNone(target.artifact)
|
|
|
|
def test_source_set_object_files(self):
|
|
target = TargetInfo(self._paths, '//fake_module:fake_source_set')
|
|
self.assertTrue(target.generated)
|
|
self.assertEqual(
|
|
set(target.object_files), {
|
|
self._outdir / 'fake_source_set.file_a.cc.o',
|
|
self._outdir / 'fake_source_set.file_b.c.o',
|
|
})
|
|
|
|
def test_executable_object_files(self):
|
|
target = TargetInfo(self._paths, '//fake_module:fake_test')
|
|
self.assertEqual(
|
|
set(target.object_files), {
|
|
self._outdir / 'fake_test.fake_test.cc.o',
|
|
self._outdir / 'fake_test.fake_test_c.c.o',
|
|
})
|
|
|
|
def test_executable_artifact(self):
|
|
target = TargetInfo(self._paths, '//fake_module:fake_test')
|
|
self.assertEqual(target.artifact,
|
|
self._outdir / 'test' / 'fake_test.elf')
|
|
|
|
def test_non_existent_target(self):
|
|
target = TargetInfo(self._paths,
|
|
'//fake_module:definitely_not_a_real_target')
|
|
self.assertFalse(target.generated)
|
|
self.assertIsNone(target.artifact)
|
|
|
|
def test_non_existent_toolchain(self):
|
|
target = TargetInfo(
|
|
self._paths, '//fake_module:fake_source_set(//not_a:toolchain)')
|
|
self.assertFalse(target.generated)
|
|
self.assertIsNone(target.artifact)
|
|
|
|
|
|
class StampTargetTest(TargetTest):
|
|
"""Test with old-style .stamp files instead of phony Ninja targets."""
|
|
def setUp(self):
|
|
self._tempdir, self._outdir, self._paths = _create_ninja_files(
|
|
NINJA_SOURCE_SET_STAMP)
|
|
|
|
|
|
class ExpandExpressionsTest(unittest.TestCase):
|
|
"""Tests expansion of expressions like <TARGET_FILE(//foo)>."""
|
|
def setUp(self):
|
|
self._tempdir, self._outdir, self._paths = _create_ninja_files(
|
|
NINJA_SOURCE_SET)
|
|
|
|
def tearDown(self):
|
|
self._tempdir.cleanup()
|
|
|
|
def _path(self, *segments: str, create: bool = False) -> str:
|
|
path = Path(self._outdir, *segments)
|
|
if create:
|
|
os.makedirs(path.parent)
|
|
path.touch()
|
|
else:
|
|
assert not path.exists()
|
|
return str(path)
|
|
|
|
def test_empty(self):
|
|
self.assertEqual(list(expand_expressions(self._paths, '')), [''])
|
|
|
|
def test_no_expressions(self):
|
|
self.assertEqual(list(expand_expressions(self._paths, 'foobar')),
|
|
['foobar'])
|
|
self.assertEqual(
|
|
list(expand_expressions(self._paths, '<NOT_AN_EXPRESSION()>')),
|
|
['<NOT_AN_EXPRESSION()>'])
|
|
|
|
def test_incomplete_expression(self):
|
|
for incomplete_expression in [
|
|
'<TARGET_FILE(',
|
|
'<TARGET_FILE(//foo)',
|
|
'<TARGET_FILE(//foo>',
|
|
'<TARGET_FILE(//foo) >',
|
|
'--arg=<TARGET_FILE_IF_EXISTS(//foo) Hello>',
|
|
]:
|
|
with self.assertRaises(ExpressionError):
|
|
expand_expressions(self._paths, incomplete_expression)
|
|
|
|
def test_target_file(self):
|
|
path = self._path('test', 'fake_test.elf')
|
|
|
|
for expr, expected in [
|
|
('<TARGET_FILE(//fake_module:fake_test)>', path),
|
|
('--arg=<TARGET_FILE(//fake_module:fake_test)>', f'--arg={path}'),
|
|
('--argument=<TARGET_FILE(//fake_module:fake_test)>;'
|
|
'<TARGET_FILE(//fake_module:fake_test)>',
|
|
f'--argument={path};{path}'),
|
|
]:
|
|
self.assertEqual(list(expand_expressions(self._paths, expr)),
|
|
[expected])
|
|
|
|
def test_target_objects_no_target_file(self):
|
|
with self.assertRaisesRegex(ExpressionError, 'no output file'):
|
|
expand_expressions(self._paths,
|
|
'<TARGET_FILE(//fake_module:fake_source_set)>')
|
|
|
|
def test_target_file_non_existent_target(self):
|
|
with self.assertRaisesRegex(ExpressionError, 'generated'):
|
|
expand_expressions(self._paths, '<TARGET_FILE(//not_real:abc123)>')
|
|
|
|
def test_target_file_if_exists(self):
|
|
path = self._path('test', 'fake_test.elf', create=True)
|
|
|
|
for expr, expected in [
|
|
('<TARGET_FILE_IF_EXISTS(//fake_module:fake_test)>', path),
|
|
('--arg=<TARGET_FILE_IF_EXISTS(//fake_module:fake_test)>',
|
|
f'--arg={path}'),
|
|
('--argument=<TARGET_FILE_IF_EXISTS(//fake_module:fake_test)>;'
|
|
'<TARGET_FILE_IF_EXISTS(//fake_module:fake_test)>',
|
|
f'--argument={path};{path}'),
|
|
]:
|
|
self.assertEqual(list(expand_expressions(self._paths, expr)),
|
|
[expected])
|
|
|
|
def test_target_file_if_exists_arg_omitted(self):
|
|
for expr in [
|
|
'<TARGET_FILE_IF_EXISTS(//fake_module:fake_test)>',
|
|
'<TARGET_FILE_IF_EXISTS(//fake_module:fake_test(fake)>',
|
|
'<TARGET_FILE_IF_EXISTS(//not_a_module:nothing)>',
|
|
'--arg=<TARGET_FILE_IF_EXISTS(//fake_module:fake_test)>',
|
|
'--argument=<TARGET_FILE_IF_EXISTS(//fake_module:fake_test)>;'
|
|
'<TARGET_FILE_IF_EXISTS(//fake_module:fake_test)>',
|
|
]:
|
|
self.assertEqual(list(expand_expressions(self._paths, expr)), [])
|
|
|
|
def test_target_file_if_exists_error_if_never_has_artifact(self):
|
|
for expr in [
|
|
'<TARGET_FILE_IF_EXISTS(//fake_module:fake_source_set)>'
|
|
'bar=<TARGET_FILE_IF_EXISTS(//fake_module:fake_source_set)>'
|
|
'<TARGET_FILE_IF_EXISTS(//fake_module:fake_no_objects)>',
|
|
'--foo=<TARGET_FILE_IF_EXISTS(//fake_module:fake_no_objects)>',
|
|
]:
|
|
with self.assertRaises(ExpressionError):
|
|
expand_expressions(self._paths, expr)
|
|
|
|
def test_target_objects(self):
|
|
self.assertEqual(
|
|
set(
|
|
expand_expressions(
|
|
self._paths,
|
|
'<TARGET_OBJECTS(//fake_module:fake_source_set)>')), {
|
|
self._path('fake_source_set.file_a.cc.o'),
|
|
self._path('fake_source_set.file_b.c.o')
|
|
})
|
|
self.assertEqual(
|
|
set(
|
|
expand_expressions(
|
|
self._paths, '<TARGET_OBJECTS(//fake_module:fake_test)>')),
|
|
{
|
|
self._path('fake_test.fake_test.cc.o'),
|
|
self._path('fake_test.fake_test_c.c.o')
|
|
})
|
|
|
|
def test_target_objects_no_objects(self):
|
|
self.assertEqual(
|
|
list(
|
|
expand_expressions(
|
|
self._paths,
|
|
'<TARGET_OBJECTS(//fake_module:fake_no_objects)>')), [])
|
|
|
|
def test_target_objects_other_content_in_arg(self):
|
|
for arg in [
|
|
'--foo=<TARGET_OBJECTS(//fake_module:fake_no_objects)>',
|
|
'<TARGET_OBJECTS(//fake_module:fake_no_objects)>bar',
|
|
'--foo<TARGET_OBJECTS(//fake_module:fake_no_objects)>bar',
|
|
'<TARGET_OBJECTS(//fake_module:fake_no_objects)>'
|
|
'<TARGET_OBJECTS(//fake_module:fake_no_objects)>',
|
|
'<TARGET_OBJECTS(//fake_module:fake_source_set)>'
|
|
'<TARGET_OBJECTS(//fake_module:fake_source_set)>',
|
|
]:
|
|
with self.assertRaises(ExpressionError):
|
|
expand_expressions(self._paths, arg)
|
|
|
|
def test_target_objects_non_existent_target(self):
|
|
with self.assertRaisesRegex(ExpressionError, 'generated'):
|
|
expand_expressions(self._paths, '<TARGET_OBJECTS(//not_real)>')
|
|
|
|
|
|
class StampExpandExpressionsTest(TargetTest):
|
|
"""Test with old-style .stamp files instead of phony Ninja targets."""
|
|
def setUp(self):
|
|
self._tempdir, self._outdir, self._paths = _create_ninja_files(
|
|
NINJA_SOURCE_SET_STAMP)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|