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