• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2# Copyright 2016 The Chromium Authors
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6import mock
7import sys
8import textwrap
9import unittest
10
11import gn_helpers
12
13
14class UnitTest(unittest.TestCase):
15  def test_ToGNString(self):
16    test_cases = [
17        (42, '42', '42'), ('foo', '"foo"', '"foo"'), (True, 'true', 'true'),
18        (False, 'false', 'false'), ('', '""', '""'),
19        ('\\$"$\\', '"\\\\\\$\\"\\$\\\\"', '"\\\\\\$\\"\\$\\\\"'),
20        (' \t\r\n', '" $0x09$0x0D$0x0A"', '" $0x09$0x0D$0x0A"'),
21        (u'\u2713', '"$0xE2$0x9C$0x93"', '"$0xE2$0x9C$0x93"'),
22        ([], '[  ]', '[]'), ([1], '[ 1 ]', '[\n  1\n]\n'),
23        ([3, 1, 4, 1], '[ 3, 1, 4, 1 ]', '[\n  3,\n  1,\n  4,\n  1\n]\n'),
24        (['a', True, 2], '[ "a", true, 2 ]', '[\n  "a",\n  true,\n  2\n]\n'),
25        ({
26            'single': 'item'
27        }, 'single = "item"\n', 'single = "item"\n'),
28        ({
29            'kEy': 137,
30            '_42A_Zaz_': [False, True]
31        }, '_42A_Zaz_ = [ false, true ]\nkEy = 137\n',
32         '_42A_Zaz_ = [\n  false,\n  true\n]\nkEy = 137\n'),
33        ([1, 'two',
34          ['"thr,.$\\', True, False, [],
35           u'(\u2713)']], '[ 1, "two", [ "\\"thr,.\\$\\\\", true, false, ' +
36         '[  ], "($0xE2$0x9C$0x93)" ] ]', '''[
37  1,
38  "two",
39  [
40    "\\"thr,.\\$\\\\",
41    true,
42    false,
43    [],
44    "($0xE2$0x9C$0x93)"
45  ]
46]
47'''),
48        ({
49            's': 'foo',
50            'n': 42,
51            'b': True,
52            'a': [3, 'x']
53        }, 'a = [ 3, "x" ]\nb = true\nn = 42\ns = "foo"\n',
54         'a = [\n  3,\n  "x"\n]\nb = true\nn = 42\ns = "foo"\n'),
55        (
56            [[[], [[]]], []],
57            '[ [ [  ], [ [  ] ] ], [  ] ]',
58            '[\n  [\n    [],\n    [\n      []\n    ]\n  ],\n  []\n]\n',
59        ),
60        (
61            [{
62                'a': 1,
63                'c': {
64                    'z': 8
65                },
66                'b': []
67            }],
68            '[ { a = 1\nb = [  ]\nc = { z = 8 } } ]\n',
69            '[\n  {\n    a = 1\n    b = []\n    c = {\n' +
70            '      z = 8\n    }\n  }\n]\n',
71        )
72    ]
73    for obj, exp_ugly, exp_pretty in test_cases:
74      out_ugly = gn_helpers.ToGNString(obj)
75      self.assertEqual(exp_ugly, out_ugly)
76      out_pretty = gn_helpers.ToGNString(obj, pretty=True)
77      self.assertEqual(exp_pretty, out_pretty)
78
79  def test_UnescapeGNString(self):
80    # Backslash followed by a \, $, or " means the folling character without
81    # the special meaning. Backslash followed by everything else is a literal.
82    self.assertEqual(
83        gn_helpers.UnescapeGNString('\\as\\$\\\\asd\\"'),
84        '\\as$\\asd"')
85
86  def test_FromGNString(self):
87    self.assertEqual(
88        gn_helpers.FromGNString('[1, -20, true, false,["as\\"", []]]'),
89        [ 1, -20, True, False, [ 'as"', [] ] ])
90
91    with self.assertRaises(gn_helpers.GNError):
92      parser = gn_helpers.GNValueParser('123 456')
93      parser.Parse()
94
95  def test_ParseBool(self):
96    parser = gn_helpers.GNValueParser('true')
97    self.assertEqual(parser.Parse(), True)
98
99    parser = gn_helpers.GNValueParser('false')
100    self.assertEqual(parser.Parse(), False)
101
102  def test_ParseNumber(self):
103    parser = gn_helpers.GNValueParser('123')
104    self.assertEqual(parser.ParseNumber(), 123)
105
106    with self.assertRaises(gn_helpers.GNError):
107      parser = gn_helpers.GNValueParser('')
108      parser.ParseNumber()
109    with self.assertRaises(gn_helpers.GNError):
110      parser = gn_helpers.GNValueParser('a123')
111      parser.ParseNumber()
112
113  def test_ParseString(self):
114    parser = gn_helpers.GNValueParser('"asdf"')
115    self.assertEqual(parser.ParseString(), 'asdf')
116
117    with self.assertRaises(gn_helpers.GNError):
118      parser = gn_helpers.GNValueParser('')  # Empty.
119      parser.ParseString()
120    with self.assertRaises(gn_helpers.GNError):
121      parser = gn_helpers.GNValueParser('asdf')  # Unquoted.
122      parser.ParseString()
123    with self.assertRaises(gn_helpers.GNError):
124      parser = gn_helpers.GNValueParser('"trailing')  # Unterminated.
125      parser.ParseString()
126
127  def test_ParseList(self):
128    parser = gn_helpers.GNValueParser('[1,]')  # Optional end comma OK.
129    self.assertEqual(parser.ParseList(), [ 1 ])
130
131    with self.assertRaises(gn_helpers.GNError):
132      parser = gn_helpers.GNValueParser('')  # Empty.
133      parser.ParseList()
134    with self.assertRaises(gn_helpers.GNError):
135      parser = gn_helpers.GNValueParser('asdf')  # No [].
136      parser.ParseList()
137    with self.assertRaises(gn_helpers.GNError):
138      parser = gn_helpers.GNValueParser('[1, 2')  # Unterminated
139      parser.ParseList()
140    with self.assertRaises(gn_helpers.GNError):
141      parser = gn_helpers.GNValueParser('[1 2]')  # No separating comma.
142      parser.ParseList()
143
144  def test_ParseScope(self):
145    parser = gn_helpers.GNValueParser('{a = 1}')
146    self.assertEqual(parser.ParseScope(), {'a': 1})
147
148    with self.assertRaises(gn_helpers.GNError):
149      parser = gn_helpers.GNValueParser('')  # Empty.
150      parser.ParseScope()
151    with self.assertRaises(gn_helpers.GNError):
152      parser = gn_helpers.GNValueParser('asdf')  # No {}.
153      parser.ParseScope()
154    with self.assertRaises(gn_helpers.GNError):
155      parser = gn_helpers.GNValueParser('{a = 1')  # Unterminated.
156      parser.ParseScope()
157    with self.assertRaises(gn_helpers.GNError):
158      parser = gn_helpers.GNValueParser('{"a" = 1}')  # Not identifier.
159      parser.ParseScope()
160    with self.assertRaises(gn_helpers.GNError):
161      parser = gn_helpers.GNValueParser('{a = }')  # No value.
162      parser.ParseScope()
163
164  def test_FromGNArgs(self):
165    # Booleans and numbers should work; whitespace is allowed works.
166    self.assertEqual(gn_helpers.FromGNArgs('foo = true\nbar = 1\n'),
167                     {'foo': True, 'bar': 1})
168
169    # Whitespace is not required; strings should also work.
170    self.assertEqual(gn_helpers.FromGNArgs('foo="bar baz"'),
171                     {'foo': 'bar baz'})
172
173    # Comments should work (and be ignored).
174    gn_args_lines = [
175        '# Top-level comment.',
176        'foo = true',
177        'bar = 1  # In-line comment followed by whitespace.',
178        ' ',
179        'baz = false',
180    ]
181    self.assertEqual(gn_helpers.FromGNArgs('\n'.join(gn_args_lines)), {
182        'foo': True,
183        'bar': 1,
184        'baz': False
185    })
186
187    # Lists should work.
188    self.assertEqual(gn_helpers.FromGNArgs('foo=[1, 2, 3]'),
189                     {'foo': [1, 2, 3]})
190
191    # Empty strings should return an empty dict.
192    self.assertEqual(gn_helpers.FromGNArgs(''), {})
193    self.assertEqual(gn_helpers.FromGNArgs(' \n '), {})
194
195    # Comments should work everywhere (and be ignored).
196    gn_args_lines = [
197        '# Top-level comment.',
198        '',
199        '# Variable comment.',
200        'foo = true',
201        'bar = [',
202        '    # Value comment in list.',
203        '    1,',
204        '    2,',
205        ']',
206        '',
207        'baz # Comment anywhere, really',
208        '  = # also here',
209        '    4',
210    ]
211    self.assertEqual(gn_helpers.FromGNArgs('\n'.join(gn_args_lines)), {
212        'foo': True,
213        'bar': [1, 2],
214        'baz': 4
215    })
216
217    # Scope should be parsed, even empty ones.
218    gn_args_lines = [
219        'foo = {',
220        '  a = 1',
221        '  b = [',
222        '    { },',
223        '    {',
224        '      c = 1',
225        '    },',
226        '  ]',
227        '}',
228    ]
229    self.assertEqual(gn_helpers.FromGNArgs('\n'.join(gn_args_lines)),
230                     {'foo': {
231                         'a': 1,
232                         'b': [
233                             {},
234                             {
235                                 'c': 1,
236                             },
237                         ]
238                     }})
239
240    # Non-identifiers should raise an exception.
241    with self.assertRaises(gn_helpers.GNError):
242      gn_helpers.FromGNArgs('123 = true')
243
244    # References to other variables should raise an exception.
245    with self.assertRaises(gn_helpers.GNError):
246      gn_helpers.FromGNArgs('foo = bar')
247
248    # References to functions should raise an exception.
249    with self.assertRaises(gn_helpers.GNError):
250      gn_helpers.FromGNArgs('foo = exec_script("//build/baz.py")')
251
252    # Underscores in identifiers should work.
253    self.assertEqual(gn_helpers.FromGNArgs('_foo = true'),
254                     {'_foo': True})
255    self.assertEqual(gn_helpers.FromGNArgs('foo_bar = true'),
256                     {'foo_bar': True})
257    self.assertEqual(gn_helpers.FromGNArgs('foo_=true'),
258                     {'foo_': True})
259
260  def test_ReplaceImports(self):
261    # Should be a no-op on args inputs without any imports.
262    parser = gn_helpers.GNValueParser(
263        textwrap.dedent("""
264        some_arg1 = "val1"
265        some_arg2 = "val2"
266    """))
267    parser.ReplaceImports()
268    self.assertEqual(
269        parser.input,
270        textwrap.dedent("""
271        some_arg1 = "val1"
272        some_arg2 = "val2"
273    """))
274
275    # A single "import(...)" line should be replaced with the contents of the
276    # file being imported.
277    parser = gn_helpers.GNValueParser(
278        textwrap.dedent("""
279        some_arg1 = "val1"
280        import("//some/args/file.gni")
281        some_arg2 = "val2"
282    """))
283    fake_import = 'some_imported_arg = "imported_val"'
284    builtin_var = '__builtin__' if sys.version_info.major < 3 else 'builtins'
285    open_fun = '{}.open'.format(builtin_var)
286    with mock.patch(open_fun, mock.mock_open(read_data=fake_import)):
287      parser.ReplaceImports()
288    self.assertEqual(
289        parser.input,
290        textwrap.dedent("""
291        some_arg1 = "val1"
292        some_imported_arg = "imported_val"
293        some_arg2 = "val2"
294    """))
295
296    # No trailing parenthesis should raise an exception.
297    with self.assertRaises(gn_helpers.GNError):
298      parser = gn_helpers.GNValueParser(
299          textwrap.dedent('import("//some/args/file.gni"'))
300      parser.ReplaceImports()
301
302    # No double quotes should raise an exception.
303    with self.assertRaises(gn_helpers.GNError):
304      parser = gn_helpers.GNValueParser(
305          textwrap.dedent('import(//some/args/file.gni)'))
306      parser.ReplaceImports()
307
308    # A path that's not source absolute should raise an exception.
309    with self.assertRaises(gn_helpers.GNError):
310      parser = gn_helpers.GNValueParser(
311          textwrap.dedent('import("some/relative/args/file.gni")'))
312      parser.ReplaceImports()
313
314
315if __name__ == '__main__':
316  unittest.main()
317