blob: ed3df422cf8cde6a6a9e67e239f16433d54ed390 [file] [log] [blame] [edit]
#!/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()