add_header_test.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479
  1. #!/usr/bin/env python3
  2. # Copyright 2021 The Chromium Authors. All rights reserved.
  3. # Use of this source code is governed by a BSD-style license that can be
  4. # found in the LICENSE file.
  5. import random
  6. import unittest
  7. import add_header
  8. class DecoratedFilenameTest(unittest.TestCase):
  9. def testCHeaderClassification(self):
  10. self.assertTrue(add_header.IsCSystemHeader('<stdlib.h>'))
  11. self.assertFalse(add_header.IsCSystemHeader('<type_traits>'))
  12. self.assertFalse(add_header.IsCSystemHeader('"moo.h"'))
  13. def testCXXHeaderClassification(self):
  14. self.assertFalse(add_header.IsCXXSystemHeader('<stdlib.h>'))
  15. self.assertTrue(add_header.IsCXXSystemHeader('<type_traits>'))
  16. self.assertFalse(add_header.IsCXXSystemHeader('"moo.h"'))
  17. def testUserHeaderClassification(self):
  18. self.assertFalse(add_header.IsUserHeader('<stdlib.h>'))
  19. self.assertFalse(add_header.IsUserHeader('<type_traits>'))
  20. self.assertTrue(add_header.IsUserHeader('"moo.h"'))
  21. def testClassifyHeader(self):
  22. self.assertEqual(add_header.ClassifyHeader('<stdlib.h>'),
  23. add_header._HEADER_TYPE_C_SYSTEM)
  24. self.assertEqual(add_header.ClassifyHeader('<type_traits>'),
  25. add_header._HEADER_TYPE_CXX_SYSTEM)
  26. self.assertEqual(add_header.ClassifyHeader('"moo.h"'),
  27. add_header._HEADER_TYPE_USER)
  28. self.assertEqual(add_header.ClassifyHeader('invalid'),
  29. add_header._HEADER_TYPE_INVALID)
  30. class FindIncludesTest(unittest.TestCase):
  31. def testEmpty(self):
  32. begin, end = add_header.FindIncludes([])
  33. self.assertEqual(begin, -1)
  34. self.assertEqual(end, -1)
  35. def testNoIncludes(self):
  36. begin, end = add_header.FindIncludes(['a'])
  37. self.assertEqual(begin, -1)
  38. self.assertEqual(end, -1)
  39. def testOneInclude(self):
  40. begin, end = add_header.FindIncludes(['#include <algorithm>'])
  41. self.assertEqual(begin, 0)
  42. self.assertEqual(end, 1)
  43. def testIncludeWithInlineComment(self):
  44. begin, end = add_header.FindIncludes(
  45. ['#include "moo.h" // TODO: Add more sounds.'])
  46. self.assertEqual(begin, 0)
  47. self.assertEqual(end, 1)
  48. def testNewlinesBetweenIncludes(self):
  49. begin, end = add_header.FindIncludes(
  50. ['#include <utility>', '', '#include "moo.h"'])
  51. self.assertEqual(begin, 0)
  52. self.assertEqual(end, 3)
  53. def testCommentsBetweenIncludes(self):
  54. begin, end = add_header.FindIncludes([
  55. '#include <utility>', '// TODO: Add goat support.', '#include "moo.h"'
  56. ])
  57. self.assertEqual(begin, 0)
  58. self.assertEqual(end, 3)
  59. def testEmptyLinesNotIncluded(self):
  60. begin, end = add_header.FindIncludes(
  61. ['', '#include <utility>', '', '#include "moo.h"', ''])
  62. self.assertEqual(begin, 1)
  63. self.assertEqual(end, 4)
  64. def testCommentsNotIncluded(self):
  65. begin, end = add_header.FindIncludes([
  66. '// Cow module.', '#include <utility>', '// For cow speech synthesis.',
  67. '#include "moo.h"', '// TODO: Add Linux audio support.'
  68. ])
  69. self.assertEqual(begin, 1)
  70. self.assertEqual(end, 4)
  71. def testNonIncludesLinesBeforeIncludesIgnored(self):
  72. begin, end = add_header.FindIncludes(
  73. ['#ifndef COW_H_', '#define COW_H_', '#include "moo.h"'])
  74. self.assertEqual(begin, 2)
  75. self.assertEqual(end, 3)
  76. def testNonIncludesLinesAfterIncludesTerminates(self):
  77. begin, end = add_header.FindIncludes([
  78. '#include "moo.h"', '#ifndef COW_MESSAGES_H_', '#define COW_MESSAGE_H_'
  79. ])
  80. self.assertEqual(begin, 0)
  81. self.assertEqual(end, 1)
  82. class IncludeTest(unittest.TestCase):
  83. def testToSource(self):
  84. self.assertEqual(
  85. add_header.Include('<moo.h>', 'include', [], None).ToSource(),
  86. ['#include <moo.h>'])
  87. def testIncludeWithPreambleToSource(self):
  88. self.assertEqual(
  89. add_header.Include('"moo.h"', 'include', ['// preamble'],
  90. None).ToSource(),
  91. ['// preamble', '#include "moo.h"'])
  92. def testIncludeWithInlineCommentToSource(self):
  93. self.assertEqual(
  94. add_header.Include('"moo.h"', 'include', [],
  95. ' inline comment').ToSource(),
  96. ['#include "moo.h" // inline comment'])
  97. def testIncludeWithPreambleAndInlineCommentToSource(self):
  98. # Make sure whitespace is vaguely normalized too.
  99. self.assertEqual(
  100. add_header.Include('"moo.h"', 'include', [
  101. '// preamble with trailing space ',
  102. ], ' inline comment with trailing space ').ToSource(), [
  103. '// preamble with trailing space',
  104. '#include "moo.h" // inline comment with trailing space'
  105. ])
  106. def testImportToSource(self):
  107. self.assertEqual(
  108. add_header.Include('"moo.h"', 'import', [], None).ToSource(),
  109. ['#import "moo.h"'])
  110. class ParseIncludesTest(unittest.TestCase):
  111. def testInvalid(self):
  112. self.assertIsNone(add_header.ParseIncludes(['invalid']))
  113. def testInclude(self):
  114. includes = add_header.ParseIncludes(['#include "moo.h"'])
  115. self.assertEqual(len(includes), 1)
  116. self.assertEqual(includes[0].decorated_name, '"moo.h"')
  117. self.assertEqual(includes[0].directive, 'include')
  118. self.assertEqual(includes[0].preamble, [])
  119. self.assertIsNone(includes[0].inline_comment)
  120. self.assertEqual(includes[0].header_type, add_header._HEADER_TYPE_USER)
  121. self.assertFalse(includes[0].is_primary_header)
  122. def testIncludeSurroundedByWhitespace(self):
  123. includes = add_header.ParseIncludes([' #include "moo.h" '])
  124. self.assertEqual(len(includes), 1)
  125. self.assertEqual(includes[0].decorated_name, '"moo.h"')
  126. self.assertEqual(includes[0].directive, 'include')
  127. self.assertEqual(includes[0].preamble, [])
  128. self.assertIsNone(includes[0].inline_comment)
  129. self.assertEqual(includes[0].header_type, add_header._HEADER_TYPE_USER)
  130. self.assertFalse(includes[0].is_primary_header)
  131. def testImport(self):
  132. includes = add_header.ParseIncludes(['#import "moo.h"'])
  133. self.assertEqual(len(includes), 1)
  134. self.assertEqual(includes[0].decorated_name, '"moo.h"')
  135. self.assertEqual(includes[0].directive, 'import')
  136. self.assertEqual(includes[0].preamble, [])
  137. self.assertIsNone(includes[0].inline_comment)
  138. self.assertEqual(includes[0].header_type, add_header._HEADER_TYPE_USER)
  139. self.assertFalse(includes[0].is_primary_header)
  140. def testIncludeWithPreamble(self):
  141. includes = add_header.ParseIncludes(
  142. ['// preamble comment ', '#include "moo.h"'])
  143. self.assertEqual(len(includes), 1)
  144. self.assertEqual(includes[0].decorated_name, '"moo.h"')
  145. self.assertEqual(includes[0].directive, 'include')
  146. self.assertEqual(includes[0].preamble, ['// preamble comment '])
  147. self.assertIsNone(includes[0].inline_comment)
  148. self.assertEqual(includes[0].header_type, add_header._HEADER_TYPE_USER)
  149. self.assertFalse(includes[0].is_primary_header)
  150. def testIncludeWithInvalidPreamble(self):
  151. self.assertIsNone(
  152. add_header.ParseIncludes(['// orphan comment', '', '#include "moo.h"']))
  153. def testIncludeWIthInlineComment(self):
  154. includes = add_header.ParseIncludes(['#include "moo.h"// For SFX '])
  155. self.assertEqual(len(includes), 1)
  156. self.assertEqual(includes[0].decorated_name, '"moo.h"')
  157. self.assertEqual(includes[0].directive, 'include')
  158. self.assertEqual(includes[0].preamble, [])
  159. self.assertEqual(includes[0].inline_comment, ' For SFX ')
  160. self.assertEqual(includes[0].header_type, add_header._HEADER_TYPE_USER)
  161. self.assertFalse(includes[0].is_primary_header)
  162. def testIncludeWithInlineCommentAndPreamble(self):
  163. includes = add_header.ParseIncludes(
  164. ['// preamble comment ', '#include "moo.h" // For SFX '])
  165. self.assertEqual(len(includes), 1)
  166. self.assertEqual(includes[0].decorated_name, '"moo.h"')
  167. self.assertEqual(includes[0].directive, 'include')
  168. self.assertEqual(includes[0].preamble, ['// preamble comment '])
  169. self.assertEqual(includes[0].inline_comment, ' For SFX ')
  170. self.assertEqual(includes[0].header_type, add_header._HEADER_TYPE_USER)
  171. self.assertFalse(includes[0].is_primary_header)
  172. def testMultipleIncludes(self):
  173. includes = add_header.ParseIncludes([
  174. '#include <time.h>', '', '#include "moo.h" // For SFX ',
  175. '// TODO: Implement death ray.', '#import "goat.h"'
  176. ])
  177. self.assertEqual(len(includes), 3)
  178. self.assertEqual(includes[0].decorated_name, '<time.h>')
  179. self.assertEqual(includes[0].directive, 'include')
  180. self.assertEqual(includes[0].preamble, [])
  181. self.assertIsNone(includes[0].inline_comment)
  182. self.assertEqual(includes[0].header_type, add_header._HEADER_TYPE_C_SYSTEM)
  183. self.assertFalse(includes[0].is_primary_header)
  184. self.assertEqual(includes[1].decorated_name, '"moo.h"')
  185. self.assertEqual(includes[1].directive, 'include')
  186. self.assertEqual(includes[1].preamble, [])
  187. self.assertEqual(includes[1].inline_comment, ' For SFX ')
  188. self.assertEqual(includes[1].header_type, add_header._HEADER_TYPE_USER)
  189. self.assertFalse(includes[1].is_primary_header)
  190. self.assertEqual(includes[2].decorated_name, '"goat.h"')
  191. self.assertEqual(includes[2].directive, 'import')
  192. self.assertEqual(includes[2].preamble, ['// TODO: Implement death ray.'])
  193. self.assertIsNone(includes[2].inline_comment)
  194. self.assertEqual(includes[2].header_type, add_header._HEADER_TYPE_USER)
  195. self.assertFalse(includes[2].is_primary_header)
  196. class MarkPrimaryIncludeTest(unittest.TestCase):
  197. def _extract_primary_name(self, includes):
  198. for include in includes:
  199. if include.is_primary_header:
  200. return include.decorated_name
  201. def testNoOpOnHeader(self):
  202. includes = [add_header.Include('"cow.h"', 'include', [], None)]
  203. add_header.MarkPrimaryInclude(includes, 'cow.h')
  204. self.assertIsNone(self._extract_primary_name(includes))
  205. def testSystemHeaderNotMatched(self):
  206. includes = [add_header.Include('<cow.h>', 'include', [], None)]
  207. add_header.MarkPrimaryInclude(includes, 'cow.cc')
  208. self.assertIsNone(self._extract_primary_name(includes))
  209. def testExactMatch(self):
  210. includes = [
  211. add_header.Include('"cow.h"', 'include', [], None),
  212. add_header.Include('"cow_posix.h"', 'include', [], None),
  213. ]
  214. add_header.MarkPrimaryInclude(includes, 'cow.cc')
  215. self.assertEqual(self._extract_primary_name(includes), '"cow.h"')
  216. def testFuzzyMatch(self):
  217. includes = [add_header.Include('"cow.h"', 'include', [], None)]
  218. add_header.MarkPrimaryInclude(includes, 'cow_linux_unittest.cc')
  219. self.assertEqual(self._extract_primary_name(includes), '"cow.h"')
  220. def testFuzzymatchInReverse(self):
  221. includes = [add_header.Include('"cow.h"', 'include', [], None)]
  222. add_header.MarkPrimaryInclude(includes, 'cow_uitest_aura.cc')
  223. self.assertEqual(self._extract_primary_name(includes), '"cow.h"')
  224. def testFuzzyMatchDoesntMatchDifferentSuffixes(self):
  225. includes = [add_header.Include('"cow_posix.h"', 'include', [], None)]
  226. add_header.MarkPrimaryInclude(includes, 'cow_windows.cc')
  227. self.assertIsNone(self._extract_primary_name(includes))
  228. def testMarksMostSpecific(self):
  229. includes = [
  230. add_header.Include('"cow.h"', 'include', [], None),
  231. add_header.Include('"cow_posix.h"', 'include', [], None),
  232. ]
  233. add_header.MarkPrimaryInclude(includes, 'cow_posix.cc')
  234. self.assertEqual(self._extract_primary_name(includes), '"cow_posix.h"')
  235. def testFullPathMatch(self):
  236. includes = [add_header.Include('"zfs/impl/cow.h"', 'include', [], None)]
  237. add_header.MarkPrimaryInclude(includes, 'zfs/impl/cow.cc')
  238. self.assertEqual(self._extract_primary_name(includes), '"zfs/impl/cow.h"')
  239. def testTopmostDirectoryDoesNotMatch(self):
  240. includes = [add_header.Include('"animal/impl/cow.h"', 'include', [], None)]
  241. add_header.MarkPrimaryInclude(includes, 'zfs/impl/cow.cc')
  242. self.assertIsNone(self._extract_primary_name(includes))
  243. def testSubstantiallySimilarPaths(self):
  244. includes = [
  245. add_header.Include('"farm/public/animal/cow.h"', 'include', [], None)
  246. ]
  247. add_header.MarkPrimaryInclude(includes, 'farm/animal/cow.cc')
  248. self.assertEqual(self._extract_primary_name(includes),
  249. '"farm/public/animal/cow.h"')
  250. def testSubstantiallySimilarPathsAndExactMatch(self):
  251. includes = [
  252. add_header.Include('"ui/gfx/ipc/geometry/gfx_param_traits.h"',
  253. 'include', [], None),
  254. add_header.Include('"ui/gfx/ipc/gfx_param_traits.h"', 'include', [],
  255. None),
  256. ]
  257. add_header.MarkPrimaryInclude(includes, 'ui/gfx/ipc/gfx_param_traits.cc')
  258. self.assertEqual(self._extract_primary_name(includes),
  259. '"ui/gfx/ipc/gfx_param_traits.h"')
  260. def testNoMatchingSubdirectories(self):
  261. includes = [add_header.Include('"base/zfs/cow.h"', 'include', [], None)]
  262. add_header.MarkPrimaryInclude(includes, 'base/animal/cow.cc')
  263. self.assertIsNone(self._extract_primary_name(includes))
  264. class SerializeIncludesTest(unittest.TestCase):
  265. def testSystemHeaders(self):
  266. source = add_header.SerializeIncludes([
  267. add_header.Include('<stdlib.h>', 'include', [], None),
  268. add_header.Include('<map>', 'include', [], None),
  269. ])
  270. self.assertEqual(source, ['#include <stdlib.h>', '', '#include <map>'])
  271. def testUserHeaders(self):
  272. source = add_header.SerializeIncludes([
  273. add_header.Include('"goat.h"', 'include', [], None),
  274. add_header.Include('"moo.h"', 'include', [], None),
  275. ])
  276. self.assertEqual(source, ['#include "goat.h"', '#include "moo.h"'])
  277. def testSystemAndUserHeaders(self):
  278. source = add_header.SerializeIncludes([
  279. add_header.Include('<stdlib.h>', 'include', [], None),
  280. add_header.Include('<map>', 'include', [], None),
  281. add_header.Include('"moo.h"', 'include', [], None),
  282. ])
  283. self.assertEqual(
  284. source,
  285. ['#include <stdlib.h>', '', '#include <map>', '', '#include "moo.h"'])
  286. def testPrimaryAndSystemHeaders(self):
  287. primary_header = add_header.Include('"cow.h"', 'include', [], None)
  288. primary_header.is_primary_header = True
  289. source = add_header.SerializeIncludes([
  290. primary_header,
  291. add_header.Include('<stdlib.h>', 'include', [], None),
  292. add_header.Include('<map>', 'include', [], None),
  293. ])
  294. self.assertEqual(
  295. source,
  296. ['#include "cow.h"', '', '#include <stdlib.h>', '', '#include <map>'])
  297. def testPrimaryAndUserHeaders(self):
  298. primary_header = add_header.Include('"cow.h"', 'include', [], None)
  299. primary_header.is_primary_header = True
  300. source = add_header.SerializeIncludes([
  301. primary_header,
  302. add_header.Include('"moo.h"', 'include', [], None),
  303. ])
  304. self.assertEqual(source, ['#include "cow.h"', '', '#include "moo.h"'])
  305. def testPrimarySystemAndUserHeaders(self):
  306. primary_header = add_header.Include('"cow.h"', 'include', [], None)
  307. primary_header.is_primary_header = True
  308. source = add_header.SerializeIncludes([
  309. primary_header,
  310. add_header.Include('<stdlib.h>', 'include', [], None),
  311. add_header.Include('<map>', 'include', [], None),
  312. add_header.Include('"moo.h"', 'include', [], None),
  313. ])
  314. self.assertEqual(source, [
  315. '#include "cow.h"', '', '#include <stdlib.h>', '', '#include <map>', '',
  316. '#include "moo.h"'
  317. ])
  318. def testSpecialHeaders(self):
  319. includes = []
  320. primary_header = add_header.Include('"cow.h"', 'include', [], None)
  321. primary_header.is_primary_header = True
  322. includes.append(primary_header)
  323. includes.append(add_header.Include('<winsock2.h>', 'include', [], None))
  324. includes.append(add_header.Include('<windows.h>', 'include', [], None))
  325. includes.append(add_header.Include('<ws2tcpip.h>', 'include', [], None))
  326. includes.append(add_header.Include('<shobjidl.h>', 'include', [], None))
  327. includes.append(add_header.Include('<atlbase.h>', 'include', [], None))
  328. includes.append(add_header.Include('<ole2.h>', 'include', [], None))
  329. includes.append(add_header.Include('<unknwn.h>', 'include', [], None))
  330. includes.append(add_header.Include('<objbase.h>', 'include', [], None))
  331. includes.append(add_header.Include('<tchar.h>', 'include', [], None))
  332. includes.append(add_header.Include('<string.h>', 'include', [], None))
  333. includes.append(add_header.Include('<stddef.h>', 'include', [], None))
  334. includes.append(add_header.Include('<stdio.h>', 'include', [], None))
  335. includes.append(add_header.Include('"moo.h"', 'include', [], None))
  336. random.shuffle(includes)
  337. source = add_header.SerializeIncludes(includes)
  338. self.assertEqual(source, [
  339. '#include "cow.h"', '', '#include <winsock2.h>', '#include <windows.h>',
  340. '#include <ws2tcpip.h>', '#include <shobjidl.h>',
  341. '#include <atlbase.h>', '#include <ole2.h>', '#include <unknwn.h>',
  342. '#include <objbase.h>', '#include <tchar.h>', '#include <stddef.h>',
  343. '#include <stdio.h>', '#include <string.h>', '', '#include "moo.h"'
  344. ])
  345. class AddHeaderToSourceTest(unittest.TestCase):
  346. def testAddInclude(self):
  347. source = add_header.AddHeaderToSource(
  348. 'cow.cc', '\n'.join([
  349. '// Copyright info here.', '', '#include <utility>',
  350. '// For cow speech synthesis.',
  351. '#include "moo.h" // TODO: Add Linux audio support.',
  352. '#include <time.h>', '#include "cow.h"', 'namespace bovine {', '',
  353. '// TODO: Implement.', '} // namespace bovine'
  354. ]), '<memory>')
  355. self.assertEqual(
  356. source, '\n'.join([
  357. '// Copyright info here.', '', '#include "cow.h"', '',
  358. '#include <time.h>', '', '#include <memory>', '#include <utility>',
  359. '', '// For cow speech synthesis.',
  360. '#include "moo.h" // TODO: Add Linux audio support.',
  361. 'namespace bovine {', '', '// TODO: Implement.',
  362. '} // namespace bovine', ''
  363. ]))
  364. def testAlreadyIncluded(self):
  365. # To make sure the original source is returned unmodified, the input source
  366. # intentionally scrambles the #include order.
  367. source = '\n'.join([
  368. '// Copyright info here.', '', '#include "moo.h"', '#include <utility>',
  369. '#include <memory>', '#include "cow.h"', 'namespace bovine {', '',
  370. '// TODO: Implement.', '} // namespace bovine'
  371. ])
  372. self.assertEqual(add_header.AddHeaderToSource('cow.cc', source, '<memory>'),
  373. None)
  374. def testConditionalIncludesLeftALone(self):
  375. # TODO(dcheng): Conditional header handling could probably be more clever.
  376. # But for the moment, this is probably Good Enough.
  377. source = add_header.AddHeaderToSource(
  378. 'cow.cc', '\n'.join([
  379. '// Copyright info here.', '', '#include "cow.h"',
  380. '#include <utility>', '// For cow speech synthesis.',
  381. '#include "moo.h" // TODO: Add Linux audio support.',
  382. '#if defined(USE_AURA)', '#include <memory>',
  383. '#endif // defined(USE_AURA)'
  384. ]), '<memory>')
  385. self.assertEqual(
  386. source, '\n'.join([
  387. '// Copyright info here.', '', '#include "cow.h"', '',
  388. '#include <memory>', '#include <utility>', '',
  389. '// For cow speech synthesis.',
  390. '#include "moo.h" // TODO: Add Linux audio support.',
  391. '#if defined(USE_AURA)', '#include <memory>',
  392. '#endif // defined(USE_AURA)', ''
  393. ]))
  394. def testRemoveInclude(self):
  395. source = add_header.AddHeaderToSource(
  396. 'cow.cc',
  397. '\n'.join([
  398. '// Copyright info here.', '', '#include <memory>',
  399. '#include <utility>', '// For cow speech synthesis.',
  400. '#include "moo.h" // TODO: Add Linux audio support.',
  401. '#include <time.h>', '#include "cow.h"', 'namespace bovine {', '',
  402. '// TODO: Implement.', '} // namespace bovine'
  403. ]),
  404. '<utility>',
  405. remove=True)
  406. self.assertEqual(
  407. source, '\n'.join([
  408. '// Copyright info here.', '', '#include "cow.h"', '',
  409. '#include <time.h>', '', '#include <memory>', '',
  410. '// For cow speech synthesis.',
  411. '#include "moo.h" // TODO: Add Linux audio support.',
  412. 'namespace bovine {', '', '// TODO: Implement.',
  413. '} // namespace bovine', ''
  414. ]))
  415. if __name__ == '__main__':
  416. unittest.main()