android_webview
apps
ash
base
build
3pp_common
android
apple
args
chromeos
cipd
config
docs
fuchsia
gn_ast
internal
ios
lacros
linux
mac
private_code_test
rust
sanitizers
skia_gold_common
toolchain
util
win
.clang-tidy
.clangd
.git-blame-ignore-revs
.gitignore
.style.yapf
BUILD.gn
DEPS
DIR_METADATA
OWNERS
OWNERS.setnoparent
OWNERS.status
PRESUBMIT.py
PRESUBMIT_test.py
README.md
action_helpers.py
action_helpers_unittest.py
add_rts_filters.py
build-ctags.sh
build_config.h
buildflag.h
buildflag_header.gni
check_gn_headers.py
check_gn_headers_unittest.py
check_gn_headers_whitelist.txt
check_return_value.py
ciopfs.sha1
clobber.py
clobber_unittest.py
compiled_action.gni
compute_build_timestamp.py
copy_test_data_ios.py
cp.py
detect_host_arch.py
dir_exists.py
dotfile_settings.gni
download_nacl_toolchains.py
env_dump.py
extract_from_cab.py
extract_partition.py
find_depot_tools.py
fix_gn_headers.py
gdb-add-index
get_landmines.py
get_symlink_targets.py
gn_editor
gn_helpers.py
gn_helpers_unittest.py
gn_logs.gni
gn_run_binary.py
install-build-deps.py
install-build-deps.sh
install-chroot.sh
landmine_utils.py
landmines.py
locale_tool.py
mac_toolchain.py
metadata.json.in
nocompile.gni
noop.py
partitioned_shared_library.gni
precompile.cc
precompile.h
print_python_deps.py
protoc_java.py
protoc_java.pydeps
redirect_stdout.py
rm.py
sample_arg_file.gn
sanitize-mac-build-log.sed
sanitize-mac-build-log.sh
sanitize-win-build-log.sed
sanitize-win-build-log.sh
shim_headers.gni
symlink.gni
symlink.py
timestamp.gni
tree_truth.sh
update-linux-sandbox.sh
vs_toolchain.py
whitespace_file.txt
write_buildflag_header.py
xcode_binaries.yaml
zip_helpers.py
zip_helpers_unittest.py
build_overrides
buildtools
cc
chrome
chromecast
chromeos
clank
codelabs
components
content
crypto
dbus
device
docs
extensions
fuchsia_web
gin
google_apis
gpu
headless
infra
internal
ios
ios_internal
ipc
media
mojo
native_client
native_client_sdk
net
pdf
ppapi
printing
remoting
rlz
sandbox
services
signing_keys
skia
sql
storage
styleguide
testing
third_party
tools
ui
url
v8
webkit
.clang-format
.clang-tidy
.clangd
.git-blame-ignore-revs
.gitallowed
.gitattributes
.gitignore
.gitmodules
.gn
.mailmap
.rustfmt.toml
.vpython3
.yapfignore
ATL_OWNERS
AUTHORS
BUILD.gn
CODE_OF_CONDUCT.md
CPPLINT.cfg
CRYPTO_OWNERS
DEPS
DIR_METADATA
LICENSE
LICENSE.chromium_os
OWNERS
PRESUBMIT.py
PRESUBMIT_test.py
PRESUBMIT_test_mocks.py
README.md
WATCHLISTS
codereview.settings

Bug: b/293657720 Change-Id: I1292aa87a408bdc4d4388325690696e2286c191e Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5017598 Auto-Submit: Andrew Grieve <agrieve@chromium.org> Reviewed-by: Junji Watanabe <jwata@google.com> Commit-Queue: Andrew Grieve <agrieve@chromium.org> Cr-Commit-Position: refs/heads/main@{#1222703}
354 lines
12 KiB
Python
Executable File
354 lines
12 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# Copyright 2016 The Chromium Authors
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
|
|
import os
|
|
import pathlib
|
|
import shutil
|
|
import sys
|
|
import tempfile
|
|
import textwrap
|
|
import unittest
|
|
from unittest import mock
|
|
|
|
import gn_helpers
|
|
|
|
|
|
class UnitTest(unittest.TestCase):
|
|
def test_ToGNString(self):
|
|
test_cases = [
|
|
(42, '42', '42'), ('foo', '"foo"', '"foo"'), (True, 'true', 'true'),
|
|
(False, 'false', 'false'), ('', '""', '""'),
|
|
('\\$"$\\', '"\\\\\\$\\"\\$\\\\"', '"\\\\\\$\\"\\$\\\\"'),
|
|
(' \t\r\n', '" $0x09$0x0D$0x0A"', '" $0x09$0x0D$0x0A"'),
|
|
(u'\u2713', '"$0xE2$0x9C$0x93"', '"$0xE2$0x9C$0x93"'),
|
|
([], '[ ]', '[]'), ([1], '[ 1 ]', '[\n 1\n]\n'),
|
|
([3, 1, 4, 1], '[ 3, 1, 4, 1 ]', '[\n 3,\n 1,\n 4,\n 1\n]\n'),
|
|
(['a', True, 2], '[ "a", true, 2 ]', '[\n "a",\n true,\n 2\n]\n'),
|
|
({
|
|
'single': 'item'
|
|
}, 'single = "item"\n', 'single = "item"\n'),
|
|
({
|
|
'kEy': 137,
|
|
'_42A_Zaz_': [False, True]
|
|
}, '_42A_Zaz_ = [ false, true ]\nkEy = 137\n',
|
|
'_42A_Zaz_ = [\n false,\n true\n]\nkEy = 137\n'),
|
|
([1, 'two',
|
|
['"thr,.$\\', True, False, [],
|
|
u'(\u2713)']], '[ 1, "two", [ "\\"thr,.\\$\\\\", true, false, ' +
|
|
'[ ], "($0xE2$0x9C$0x93)" ] ]', '''[
|
|
1,
|
|
"two",
|
|
[
|
|
"\\"thr,.\\$\\\\",
|
|
true,
|
|
false,
|
|
[],
|
|
"($0xE2$0x9C$0x93)"
|
|
]
|
|
]
|
|
'''),
|
|
({
|
|
's': 'foo',
|
|
'n': 42,
|
|
'b': True,
|
|
'a': [3, 'x']
|
|
}, 'a = [ 3, "x" ]\nb = true\nn = 42\ns = "foo"\n',
|
|
'a = [\n 3,\n "x"\n]\nb = true\nn = 42\ns = "foo"\n'),
|
|
(
|
|
[[[], [[]]], []],
|
|
'[ [ [ ], [ [ ] ] ], [ ] ]',
|
|
'[\n [\n [],\n [\n []\n ]\n ],\n []\n]\n',
|
|
),
|
|
(
|
|
[{
|
|
'a': 1,
|
|
'c': {
|
|
'z': 8
|
|
},
|
|
'b': []
|
|
}],
|
|
'[ { a = 1\nb = [ ]\nc = { z = 8 } } ]\n',
|
|
'[\n {\n a = 1\n b = []\n c = {\n' +
|
|
' z = 8\n }\n }\n]\n',
|
|
)
|
|
]
|
|
for obj, exp_ugly, exp_pretty in test_cases:
|
|
out_ugly = gn_helpers.ToGNString(obj)
|
|
self.assertEqual(exp_ugly, out_ugly)
|
|
out_pretty = gn_helpers.ToGNString(obj, pretty=True)
|
|
self.assertEqual(exp_pretty, out_pretty)
|
|
|
|
def test_UnescapeGNString(self):
|
|
# Backslash followed by a \, $, or " means the folling character without
|
|
# the special meaning. Backslash followed by everything else is a literal.
|
|
self.assertEqual(
|
|
gn_helpers.UnescapeGNString('\\as\\$\\\\asd\\"'),
|
|
'\\as$\\asd"')
|
|
|
|
def test_FromGNString(self):
|
|
self.assertEqual(
|
|
gn_helpers.FromGNString('[1, -20, true, false,["as\\"", []]]'),
|
|
[ 1, -20, True, False, [ 'as"', [] ] ])
|
|
|
|
with self.assertRaises(gn_helpers.GNError):
|
|
parser = gn_helpers.GNValueParser('123 456')
|
|
parser.Parse()
|
|
|
|
def test_ParseBool(self):
|
|
parser = gn_helpers.GNValueParser('true')
|
|
self.assertEqual(parser.Parse(), True)
|
|
|
|
parser = gn_helpers.GNValueParser('false')
|
|
self.assertEqual(parser.Parse(), False)
|
|
|
|
def test_ParseNumber(self):
|
|
parser = gn_helpers.GNValueParser('123')
|
|
self.assertEqual(parser.ParseNumber(), 123)
|
|
|
|
with self.assertRaises(gn_helpers.GNError):
|
|
parser = gn_helpers.GNValueParser('')
|
|
parser.ParseNumber()
|
|
with self.assertRaises(gn_helpers.GNError):
|
|
parser = gn_helpers.GNValueParser('a123')
|
|
parser.ParseNumber()
|
|
|
|
def test_ParseString(self):
|
|
parser = gn_helpers.GNValueParser('"asdf"')
|
|
self.assertEqual(parser.ParseString(), 'asdf')
|
|
|
|
with self.assertRaises(gn_helpers.GNError):
|
|
parser = gn_helpers.GNValueParser('') # Empty.
|
|
parser.ParseString()
|
|
with self.assertRaises(gn_helpers.GNError):
|
|
parser = gn_helpers.GNValueParser('asdf') # Unquoted.
|
|
parser.ParseString()
|
|
with self.assertRaises(gn_helpers.GNError):
|
|
parser = gn_helpers.GNValueParser('"trailing') # Unterminated.
|
|
parser.ParseString()
|
|
|
|
def test_ParseList(self):
|
|
parser = gn_helpers.GNValueParser('[1,]') # Optional end comma OK.
|
|
self.assertEqual(parser.ParseList(), [ 1 ])
|
|
|
|
with self.assertRaises(gn_helpers.GNError):
|
|
parser = gn_helpers.GNValueParser('') # Empty.
|
|
parser.ParseList()
|
|
with self.assertRaises(gn_helpers.GNError):
|
|
parser = gn_helpers.GNValueParser('asdf') # No [].
|
|
parser.ParseList()
|
|
with self.assertRaises(gn_helpers.GNError):
|
|
parser = gn_helpers.GNValueParser('[1, 2') # Unterminated
|
|
parser.ParseList()
|
|
with self.assertRaises(gn_helpers.GNError):
|
|
parser = gn_helpers.GNValueParser('[1 2]') # No separating comma.
|
|
parser.ParseList()
|
|
|
|
def test_ParseScope(self):
|
|
parser = gn_helpers.GNValueParser('{a = 1}')
|
|
self.assertEqual(parser.ParseScope(), {'a': 1})
|
|
|
|
with self.assertRaises(gn_helpers.GNError):
|
|
parser = gn_helpers.GNValueParser('') # Empty.
|
|
parser.ParseScope()
|
|
with self.assertRaises(gn_helpers.GNError):
|
|
parser = gn_helpers.GNValueParser('asdf') # No {}.
|
|
parser.ParseScope()
|
|
with self.assertRaises(gn_helpers.GNError):
|
|
parser = gn_helpers.GNValueParser('{a = 1') # Unterminated.
|
|
parser.ParseScope()
|
|
with self.assertRaises(gn_helpers.GNError):
|
|
parser = gn_helpers.GNValueParser('{"a" = 1}') # Not identifier.
|
|
parser.ParseScope()
|
|
with self.assertRaises(gn_helpers.GNError):
|
|
parser = gn_helpers.GNValueParser('{a = }') # No value.
|
|
parser.ParseScope()
|
|
|
|
def test_FromGNArgs(self):
|
|
# Booleans and numbers should work; whitespace is allowed works.
|
|
self.assertEqual(gn_helpers.FromGNArgs('foo = true\nbar = 1\n'),
|
|
{'foo': True, 'bar': 1})
|
|
|
|
# Whitespace is not required; strings should also work.
|
|
self.assertEqual(gn_helpers.FromGNArgs('foo="bar baz"'),
|
|
{'foo': 'bar baz'})
|
|
|
|
# Comments should work (and be ignored).
|
|
gn_args_lines = [
|
|
'# Top-level comment.',
|
|
'foo = true',
|
|
'bar = 1 # In-line comment followed by whitespace.',
|
|
' ',
|
|
'baz = false',
|
|
]
|
|
self.assertEqual(gn_helpers.FromGNArgs('\n'.join(gn_args_lines)), {
|
|
'foo': True,
|
|
'bar': 1,
|
|
'baz': False
|
|
})
|
|
|
|
# Lists should work.
|
|
self.assertEqual(gn_helpers.FromGNArgs('foo=[1, 2, 3]'),
|
|
{'foo': [1, 2, 3]})
|
|
|
|
# Empty strings should return an empty dict.
|
|
self.assertEqual(gn_helpers.FromGNArgs(''), {})
|
|
self.assertEqual(gn_helpers.FromGNArgs(' \n '), {})
|
|
|
|
# Comments should work everywhere (and be ignored).
|
|
gn_args_lines = [
|
|
'# Top-level comment.',
|
|
'',
|
|
'# Variable comment.',
|
|
'foo = true',
|
|
'bar = [',
|
|
' # Value comment in list.',
|
|
' 1,',
|
|
' 2,',
|
|
']',
|
|
'',
|
|
'baz # Comment anywhere, really',
|
|
' = # also here',
|
|
' 4',
|
|
]
|
|
self.assertEqual(gn_helpers.FromGNArgs('\n'.join(gn_args_lines)), {
|
|
'foo': True,
|
|
'bar': [1, 2],
|
|
'baz': 4
|
|
})
|
|
|
|
# Scope should be parsed, even empty ones.
|
|
gn_args_lines = [
|
|
'foo = {',
|
|
' a = 1',
|
|
' b = [',
|
|
' { },',
|
|
' {',
|
|
' c = 1',
|
|
' },',
|
|
' ]',
|
|
'}',
|
|
]
|
|
self.assertEqual(gn_helpers.FromGNArgs('\n'.join(gn_args_lines)),
|
|
{'foo': {
|
|
'a': 1,
|
|
'b': [
|
|
{},
|
|
{
|
|
'c': 1,
|
|
},
|
|
]
|
|
}})
|
|
|
|
# Non-identifiers should raise an exception.
|
|
with self.assertRaises(gn_helpers.GNError):
|
|
gn_helpers.FromGNArgs('123 = true')
|
|
|
|
# References to other variables should raise an exception.
|
|
with self.assertRaises(gn_helpers.GNError):
|
|
gn_helpers.FromGNArgs('foo = bar')
|
|
|
|
# References to functions should raise an exception.
|
|
with self.assertRaises(gn_helpers.GNError):
|
|
gn_helpers.FromGNArgs('foo = exec_script("//build/baz.py")')
|
|
|
|
# Underscores in identifiers should work.
|
|
self.assertEqual(gn_helpers.FromGNArgs('_foo = true'),
|
|
{'_foo': True})
|
|
self.assertEqual(gn_helpers.FromGNArgs('foo_bar = true'),
|
|
{'foo_bar': True})
|
|
self.assertEqual(gn_helpers.FromGNArgs('foo_=true'),
|
|
{'foo_': True})
|
|
|
|
def test_ReplaceImports(self):
|
|
# Should be a no-op on args inputs without any imports.
|
|
parser = gn_helpers.GNValueParser(
|
|
textwrap.dedent("""
|
|
some_arg1 = "val1"
|
|
some_arg2 = "val2"
|
|
"""))
|
|
parser.ReplaceImports()
|
|
self.assertEqual(
|
|
parser.input,
|
|
textwrap.dedent("""
|
|
some_arg1 = "val1"
|
|
some_arg2 = "val2"
|
|
"""))
|
|
|
|
# A single "import(...)" line should be replaced with the contents of the
|
|
# file being imported.
|
|
parser = gn_helpers.GNValueParser(
|
|
textwrap.dedent("""
|
|
some_arg1 = "val1"
|
|
import("//some/args/file.gni")
|
|
some_arg2 = "val2"
|
|
"""))
|
|
fake_import = 'some_imported_arg = "imported_val"'
|
|
builtin_var = '__builtin__' if sys.version_info.major < 3 else 'builtins'
|
|
open_fun = '{}.open'.format(builtin_var)
|
|
with mock.patch(open_fun, mock.mock_open(read_data=fake_import)):
|
|
parser.ReplaceImports()
|
|
self.assertEqual(
|
|
parser.input,
|
|
textwrap.dedent("""
|
|
some_arg1 = "val1"
|
|
some_imported_arg = "imported_val"
|
|
some_arg2 = "val2"
|
|
"""))
|
|
|
|
# No trailing parenthesis should raise an exception.
|
|
with self.assertRaises(gn_helpers.GNError):
|
|
parser = gn_helpers.GNValueParser(
|
|
textwrap.dedent('import("//some/args/file.gni"'))
|
|
parser.ReplaceImports()
|
|
|
|
# No double quotes should raise an exception.
|
|
with self.assertRaises(gn_helpers.GNError):
|
|
parser = gn_helpers.GNValueParser(
|
|
textwrap.dedent('import(//some/args/file.gni)'))
|
|
parser.ReplaceImports()
|
|
|
|
# A path that's not source absolute should raise an exception.
|
|
with self.assertRaises(gn_helpers.GNError):
|
|
parser = gn_helpers.GNValueParser(
|
|
textwrap.dedent('import("some/relative/args/file.gni")'))
|
|
parser.ReplaceImports()
|
|
|
|
def test_CreateBuildCommand(self):
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
suffix = '.bat' if sys.platform.startswith('win32') else ''
|
|
self.assertEqual(f'autoninja{suffix}',
|
|
gn_helpers.CreateBuildCommand(temp_dir)[0])
|
|
|
|
siso_deps = pathlib.Path(temp_dir) / '.siso_deps'
|
|
siso_deps.touch()
|
|
self.assertEqual(f'autoninja{suffix}',
|
|
gn_helpers.CreateBuildCommand(temp_dir)[0])
|
|
|
|
with mock.patch('shutil.which', lambda x: None):
|
|
cmd = gn_helpers.CreateBuildCommand(temp_dir)
|
|
self.assertIn('third_party', cmd[0])
|
|
self.assertIn(f'{os.sep}siso', cmd[0])
|
|
self.assertEqual(['ninja', '-C', temp_dir], cmd[1:])
|
|
|
|
ninja_deps = pathlib.Path(temp_dir) / '.ninja_deps'
|
|
ninja_deps.touch()
|
|
|
|
with self.assertRaisesRegex(Exception, 'Found both'):
|
|
gn_helpers.CreateBuildCommand(temp_dir)
|
|
|
|
siso_deps.unlink()
|
|
self.assertEqual(f'autoninja{suffix}',
|
|
gn_helpers.CreateBuildCommand(temp_dir)[0])
|
|
|
|
with mock.patch('shutil.which', lambda x: None):
|
|
cmd = gn_helpers.CreateBuildCommand(temp_dir)
|
|
self.assertIn('third_party', cmd[0])
|
|
self.assertIn(f'{os.sep}ninja', cmd[0])
|
|
self.assertEqual(['-C', temp_dir], cmd[1:])
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|