| #!/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 unittestimport mock |
| |
| import gn_helpers |
| |
| |
| classUnitTest(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_prettyin 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<3else'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() |
| |
| if sys.platform.startswith('win32'): |
| parser= gn_helpers.GNValueParser( |
| textwrap.dedent(""" |
| some_arg1 = "val1" |
| import("/c:/some/args/file.gni") |
| some_arg2 = "val2" |
| """)) |
| 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" |
| """)) |
| |
| # A path that's not source absolute should raise an exception. |
| with self.assertRaises(gn_helpers.GNError): |
| parser= gn_helpers.GNValueParser( |
| textwrap.dedent('import("c:/some/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() |