tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

gn_helpers_unittest.py (12638B)


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