gn_helpers_unittest.py 10 KB

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