PageRenderTime 52ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 1ms

/src/com/facebook/buck/json/buck_parser/buck_test.py

https://gitlab.com/smartether/buck
Python | 715 lines | 665 code | 42 blank | 8 comment | 20 complexity | 41e22b31fd9710032c342fd3a34c53e7 MD5 | raw file
  1. from .buck import (
  2. BuildFileContext,
  3. LazyBuildEnvPartial,
  4. flatten_dicts,
  5. get_mismatched_args,
  6. subdir_glob,
  7. )
  8. from .glob_mercurial import _load_manifest_trie, glob_mercurial_manifest
  9. from .glob_watchman import format_watchman_query_params
  10. from .glob_internal import path_component_contains_dot, glob_internal
  11. from pathlib import Path, PurePosixPath, PureWindowsPath
  12. import itertools
  13. import os
  14. import shutil
  15. import tempfile
  16. import unittest
  17. class FakePathMixin(object):
  18. def glob(self, pattern):
  19. # Python glob supports unix paths on windows out of the box
  20. norm_pattern = pattern.replace('\\', '/')
  21. return self.glob_results.get(norm_pattern)
  22. def is_file(self):
  23. return True
  24. class FakePosixPath(FakePathMixin, PurePosixPath):
  25. pass
  26. class FakeWindowsPath(FakePathMixin, PureWindowsPath):
  27. pass
  28. def fake_path(fake_path_class, path, glob_results={}):
  29. # Path does magic in __new__ with its args; it's hard to add more without
  30. # changing that class. So we use a wrapper function to diddle with
  31. # FakePath's members.
  32. result = fake_path_class(path)
  33. result.glob_results = {}
  34. for pattern, paths in glob_results.iteritems():
  35. result.glob_results[pattern] = [result / fake_path_class(p) for p in paths]
  36. return result
  37. class TestBuckPlatform(unittest.TestCase):
  38. def test_lazy_build_env_partial(self):
  39. def cobol_binary(
  40. name,
  41. deps=[],
  42. build_env=None):
  43. return (name, deps, build_env)
  44. testLazy = LazyBuildEnvPartial(cobol_binary)
  45. testLazy.build_env = {}
  46. self.assertEqual(
  47. ('HAL', [1, 2, 3], {}),
  48. testLazy.invoke(name='HAL', deps=[1, 2, 3]))
  49. testLazy.build_env = {'abc': 789}
  50. self.assertEqual(
  51. ('HAL', [1, 2, 3], {'abc': 789}),
  52. testLazy.invoke(name='HAL', deps=[1, 2, 3]))
  53. class TestBuckGlobMixin(object):
  54. def do_glob(self, *args, **kwargs):
  55. # subclasses can override this to test a different glob implementation
  56. return glob_internal(*args, **kwargs)
  57. def test_glob_includes_simple(self):
  58. search_base = self.fake_path(
  59. 'foo',
  60. glob_results={'*.java': ['A.java', 'B.java']})
  61. self.assertGlobMatches(
  62. ['A.java', 'B.java'],
  63. self.do_glob(
  64. includes=['*.java'],
  65. excludes=[],
  66. project_root_relative_excludes=[],
  67. include_dotfiles=False,
  68. search_base=search_base,
  69. project_root='.'))
  70. def test_glob_includes_sort(self):
  71. search_base = self.fake_path(
  72. 'foo',
  73. glob_results={'*.java': ['A.java', 'E.java', 'D.java', 'C.java', 'B.java']})
  74. self.assertGlobMatches(
  75. ['A.java', 'B.java', 'C.java', 'D.java', 'E.java'],
  76. self.do_glob(
  77. includes=['*.java'],
  78. excludes=[],
  79. project_root_relative_excludes=[],
  80. include_dotfiles=False,
  81. search_base=search_base,
  82. project_root='.'))
  83. def test_glob_includes_multi(self):
  84. search_base = self.fake_path(
  85. 'foo',
  86. glob_results={
  87. 'bar/*.java': ['bar/A.java', 'bar/B.java'],
  88. 'baz/*.java': ['baz/C.java', 'baz/D.java'],
  89. })
  90. self.assertGlobMatches(
  91. ['bar/A.java', 'bar/B.java', 'baz/C.java', 'baz/D.java'],
  92. self.do_glob(
  93. includes=['bar/*.java', 'baz/*.java'],
  94. excludes=[],
  95. project_root_relative_excludes=[],
  96. include_dotfiles=False,
  97. search_base=search_base,
  98. project_root='.'))
  99. def test_glob_excludes_double_star(self):
  100. search_base = self.fake_path(
  101. 'foo',
  102. glob_results={
  103. '**/*.java': ['A.java', 'B.java', 'Test.java'],
  104. })
  105. self.assertGlobMatches(
  106. ['A.java', 'B.java'],
  107. self.do_glob(
  108. includes=['**/*.java'],
  109. excludes=['**/*Test.java'],
  110. project_root_relative_excludes=[],
  111. include_dotfiles=False,
  112. search_base=search_base,
  113. project_root='.'))
  114. def test_glob_excludes_multi(self):
  115. search_base = self.fake_path(
  116. 'foo',
  117. glob_results={
  118. 'bar/*.java': ['bar/A.java', 'bar/B.java'],
  119. 'baz/*.java': ['baz/C.java', 'baz/D.java'],
  120. })
  121. self.assertGlobMatches(
  122. ['bar/B.java', 'baz/D.java'],
  123. self.do_glob(
  124. includes=['bar/*.java', 'baz/*.java'],
  125. excludes=['*/[AC].java'],
  126. project_root_relative_excludes=[],
  127. include_dotfiles=False,
  128. search_base=search_base,
  129. project_root='.'))
  130. def test_glob_excludes_relative(self):
  131. search_base = self.fake_path(
  132. 'foo',
  133. glob_results={
  134. '**/*.java': ['foo/A.java', 'foo/bar/B.java', 'bar/C.java'],
  135. })
  136. self.assertGlobMatches(
  137. ['foo/A.java', 'foo/bar/B.java'],
  138. self.do_glob(
  139. includes=['**/*.java'],
  140. excludes=['bar/*.java'],
  141. project_root_relative_excludes=[],
  142. include_dotfiles=False,
  143. search_base=search_base,
  144. project_root='.'))
  145. def test_glob_project_root_relative_excludes_relative(self):
  146. search_base = self.fake_path(
  147. 'foo',
  148. glob_results={
  149. '**/*.java': ['foo/A.java', 'foo/bar/B.java', 'bar/C.java'],
  150. })
  151. self.assertGlobMatches(
  152. ['bar/C.java'],
  153. self.do_glob(
  154. includes=['**/*.java'],
  155. excludes=[],
  156. project_root_relative_excludes=['foo/foo/**'],
  157. include_dotfiles=False,
  158. search_base=search_base,
  159. project_root='.'))
  160. def test_glob_includes_skips_dotfiles(self):
  161. search_base = self.fake_path(
  162. 'foo',
  163. glob_results={'*.java': ['A.java', '.B.java']})
  164. self.assertGlobMatches(
  165. ['A.java'],
  166. self.do_glob(
  167. includes=['*.java'],
  168. excludes=[],
  169. project_root_relative_excludes=[],
  170. include_dotfiles=False,
  171. search_base=search_base,
  172. project_root='.'))
  173. def test_glob_includes_skips_dot_directories(self):
  174. search_base = self.fake_path(
  175. 'foo',
  176. glob_results={'*.java': ['A.java', '.test/B.java']})
  177. self.assertGlobMatches(
  178. ['A.java'],
  179. self.do_glob(
  180. includes=['*.java'],
  181. excludes=[],
  182. project_root_relative_excludes=[],
  183. include_dotfiles=False,
  184. search_base=search_base,
  185. project_root='.'))
  186. def test_glob_includes_does_not_skip_dotfiles_if_include_dotfiles(self):
  187. search_base = self.fake_path(
  188. 'foo',
  189. glob_results={'*.java': ['A.java', '.B.java']})
  190. self.assertGlobMatches(
  191. ['.B.java', 'A.java'],
  192. self.do_glob(
  193. includes=['*.java'],
  194. excludes=[],
  195. project_root_relative_excludes=[],
  196. include_dotfiles=True,
  197. search_base=search_base,
  198. project_root='.'))
  199. def test_explicit_exclude_with_file_separator_excludes(self):
  200. search_base = self.fake_path(
  201. 'foo',
  202. glob_results={'java/**/*.java': ['java/Include.java', 'java/Exclude.java']})
  203. self.assertGlobMatches(
  204. ['java/Include.java'],
  205. self.do_glob(
  206. includes=['java/**/*.java'],
  207. excludes=['java/Exclude.java'],
  208. project_root_relative_excludes=[],
  209. include_dotfiles=False,
  210. search_base=search_base,
  211. project_root='.'))
  212. class TestBuckSubdirGlobMixin(object):
  213. def do_subdir_glob(self, *args, **kwargs):
  214. # subclasses can override this to test a different glob implementation
  215. return subdir_glob(*args, **kwargs)
  216. def test_subdir_glob(self):
  217. build_env = BuildFileContext(
  218. self.fake_path(''), None, None, None, None, [], None, None, None, None, False,
  219. False, False)
  220. search_base = self.fake_path(
  221. 'foo',
  222. glob_results={
  223. 'lib/bar/*.h': ['lib/bar/A.h', 'lib/bar/B.h'],
  224. 'lib/baz/*.h': ['lib/baz/C.h', 'lib/baz/D.h'],
  225. })
  226. self.assertGlobMatches(
  227. {
  228. 'bar/B.h': 'lib/bar/B.h',
  229. 'bar/A.h': 'lib/bar/A.h',
  230. 'baz/D.h': 'lib/baz/D.h',
  231. 'baz/C.h': 'lib/baz/C.h',
  232. },
  233. self.do_subdir_glob([
  234. ('lib', 'bar/*.h'),
  235. ('lib', 'baz/*.h')],
  236. build_env=build_env,
  237. search_base=search_base))
  238. def test_subdir_glob_with_prefix(self):
  239. build_env = BuildFileContext(
  240. self.fake_path(''), None, None, None, None, [], None, None, None, None, False,
  241. False, False)
  242. search_base = self.fake_path(
  243. 'foo',
  244. glob_results={
  245. 'lib/bar/*.h': ['lib/bar/A.h', 'lib/bar/B.h'],
  246. })
  247. self.assertGlobMatches(
  248. {
  249. 'Prefix/bar/B.h': 'lib/bar/B.h',
  250. 'Prefix/bar/A.h': 'lib/bar/A.h',
  251. },
  252. self.do_subdir_glob([('lib', 'bar/*.h')],
  253. prefix='Prefix',
  254. build_env=build_env,
  255. search_base=search_base))
  256. class TestBuckPosix(TestBuckGlobMixin, TestBuckSubdirGlobMixin, unittest.TestCase):
  257. @staticmethod
  258. def fake_path(*args, **kwargs):
  259. return fake_path(FakePosixPath, *args, **kwargs)
  260. def assertGlobMatches(self, expected, actual):
  261. self.assertEqual(expected, actual)
  262. class TestBuckWindows(TestBuckGlobMixin, TestBuckSubdirGlobMixin, unittest.TestCase):
  263. @staticmethod
  264. def fake_path(*args, **kwargs):
  265. return fake_path(FakeWindowsPath, *args, **kwargs)
  266. def assertGlobMatches(self, expected, actual):
  267. # Fix the path separator to make test writing easier
  268. fixed_expected = None
  269. if isinstance(expected, list):
  270. fixed_expected = []
  271. for path in expected:
  272. fixed_expected.append(path.replace('/', '\\'))
  273. else:
  274. fixed_expected = {}
  275. for key, value in expected.items():
  276. fixed_expected.update({key.replace('/', '\\'): value.replace('/', '\\')})
  277. self.assertEqual(fixed_expected, actual)
  278. # Mercurial manifest / status globbing tests
  279. class FakeStatus(object):
  280. def __init__(self, removed=None, deleted=None, added=None, unknown=None):
  281. self.removed = removed or []
  282. self.deleted = deleted or []
  283. self.added = added or []
  284. self.unknown = unknown or []
  285. class TestMercurialManifestGlob(TestBuckGlobMixin, unittest.TestCase):
  286. fake_status = None
  287. fake_manifest = None
  288. def setUp(self):
  289. # clear the memoization cache for _load_manifest_trie
  290. _load_manifest_trie._cache.clear()
  291. def status(self, removed=None, deleted=None, added=None, unknown=None):
  292. class StatusContext(object):
  293. def __init__(self, test):
  294. self.test = test
  295. def __enter__(self):
  296. _load_manifest_trie._cache.clear()
  297. self.orig_status = self.test.fake_status
  298. self.test.fake_status = FakeStatus(
  299. removed, deleted, added, unknown)
  300. def __exit__(self, *exc):
  301. self.test.fake_status = self.orig_status
  302. return StatusContext(self)
  303. def fake_path(self, *args, **kwargs):
  304. fp = fake_path(FakePosixPath, *args, **kwargs)
  305. # produce a mercurial manifest from the test data
  306. self.fake_manifest = [
  307. './' + str(p) for paths in fp.glob_results.values() for p in paths]
  308. return fp
  309. def assertGlobMatches(self, expected, actual):
  310. self.assertEqual(expected, actual)
  311. def do_glob(self, includes, excludes, project_root_relative_excludes,
  312. include_dotfiles, search_base, project_root):
  313. repo_info = (
  314. project_root,
  315. self.fake_manifest or [],
  316. self.fake_status or FakeStatus()
  317. )
  318. return glob_mercurial_manifest(
  319. includes, excludes, project_root_relative_excludes,
  320. include_dotfiles, search_base, project_root, repo_info)
  321. def test_status_added(self):
  322. search_base = self.fake_path(
  323. 'foo',
  324. glob_results={'*.java': ['A.java', 'B.java']})
  325. self.assertGlobMatches(
  326. ['A.java', 'B.java'],
  327. self.do_glob(
  328. includes=['*.java'],
  329. excludes=[],
  330. project_root_relative_excludes=[],
  331. include_dotfiles=False,
  332. search_base=search_base,
  333. project_root='.'))
  334. with self.status(added=['./foo/C.java']):
  335. self.assertGlobMatches(
  336. ['A.java', 'B.java', 'C.java'],
  337. self.do_glob(
  338. includes=['*.java'],
  339. excludes=[],
  340. project_root_relative_excludes=[],
  341. include_dotfiles=False,
  342. search_base=search_base,
  343. project_root='.'))
  344. def test_status_deleted(self):
  345. search_base = self.fake_path(
  346. 'foo',
  347. glob_results={'*.java': ['A.java', 'B.java']})
  348. self.assertGlobMatches(
  349. ['A.java', 'B.java'],
  350. self.do_glob(
  351. includes=['*.java'],
  352. excludes=[],
  353. project_root_relative_excludes=[],
  354. include_dotfiles=False,
  355. search_base=search_base,
  356. project_root='.'))
  357. with self.status(deleted=['./foo/A.java']):
  358. self.assertGlobMatches(
  359. ['B.java'],
  360. self.do_glob(
  361. includes=['*.java'],
  362. excludes=[],
  363. project_root_relative_excludes=[],
  364. include_dotfiles=False,
  365. search_base=search_base,
  366. project_root='.'))
  367. def test_status_unknown(self):
  368. search_base = self.fake_path(
  369. 'foo',
  370. glob_results={'*.java': ['A.java', 'B.java']})
  371. self.assertGlobMatches(
  372. ['A.java', 'B.java'],
  373. self.do_glob(
  374. includes=['*.java'],
  375. excludes=[],
  376. project_root_relative_excludes=[],
  377. include_dotfiles=False,
  378. search_base=search_base,
  379. project_root='.'))
  380. with self.status(unknown=['./foo/C.java']):
  381. self.assertGlobMatches(
  382. ['A.java', 'B.java', 'C.java'],
  383. self.do_glob(
  384. includes=['*.java'],
  385. excludes=[],
  386. project_root_relative_excludes=[],
  387. include_dotfiles=False,
  388. search_base=search_base,
  389. project_root='.'))
  390. def test_status_removed(self):
  391. search_base = self.fake_path(
  392. 'foo',
  393. glob_results={'*.java': ['A.java', 'B.java']})
  394. self.assertGlobMatches(
  395. ['A.java', 'B.java'],
  396. self.do_glob(
  397. includes=['*.java'],
  398. excludes=[],
  399. project_root_relative_excludes=[],
  400. include_dotfiles=False,
  401. search_base=search_base,
  402. project_root='.'))
  403. with self.status(removed=['./foo/A.java']):
  404. self.assertGlobMatches(
  405. ['B.java'],
  406. self.do_glob(
  407. includes=['*.java'],
  408. excludes=[],
  409. project_root_relative_excludes=[],
  410. include_dotfiles=False,
  411. search_base=search_base,
  412. project_root='.'))
  413. class TestBuck(unittest.TestCase):
  414. def test_glob_double_star_integration(self):
  415. d = tempfile.mkdtemp()
  416. try:
  417. subdir = os.path.join(d, 'b', 'a', 'c', 'a')
  418. os.makedirs(subdir)
  419. f = open(os.path.join(subdir, 'A.java'), 'w')
  420. f.close()
  421. f = open(os.path.join(subdir, 'B.java'), 'w')
  422. f.close()
  423. f = open(os.path.join(subdir, 'Test.java'), 'w')
  424. f.close()
  425. f = open(os.path.join(subdir, '.tmp.java'), 'w')
  426. f.close()
  427. os.makedirs(os.path.join(subdir, 'NotAFile.java'))
  428. self.assertEquals(
  429. [
  430. os.path.join('b', 'a', 'c', 'a', 'A.java'),
  431. os.path.join('b', 'a', 'c', 'a', 'B.java'),
  432. ],
  433. glob_internal(
  434. includes=['b/a/**/*.java'],
  435. excludes=['**/*Test.java'],
  436. project_root_relative_excludes=[],
  437. include_dotfiles=False,
  438. search_base=Path(d),
  439. project_root=Path(d)))
  440. finally:
  441. shutil.rmtree(d)
  442. def test_case_preserved(self):
  443. d = tempfile.mkdtemp()
  444. try:
  445. subdir = os.path.join(d, 'java')
  446. os.makedirs(subdir)
  447. open(os.path.join(subdir, 'Main.java'), 'w').close()
  448. self.assertEquals(
  449. [
  450. os.path.join('java', 'Main.java'),
  451. ],
  452. glob_internal(
  453. includes=['java/Main.java'],
  454. excludes=[],
  455. project_root_relative_excludes=[],
  456. include_dotfiles=False,
  457. search_base=Path(d),
  458. project_root=Path(d)))
  459. finally:
  460. shutil.rmtree(d)
  461. def test_watchman_query_params_includes(self):
  462. query_params = format_watchman_query_params(
  463. ['**/*.java'],
  464. [],
  465. False,
  466. '/path/to/glob',
  467. False)
  468. self.assertEquals(
  469. {
  470. 'relative_root': '/path/to/glob',
  471. 'path': [''],
  472. 'fields': ['name'],
  473. 'expression': [
  474. 'allof',
  475. ['anyof', ['type', 'f'], ['type', 'l']],
  476. 'exists',
  477. ['anyof', ['match', '**/*.java', 'wholename', {}]],
  478. ]
  479. },
  480. query_params)
  481. def test_watchman_query_params_includes_and_excludes(self):
  482. query_params = format_watchman_query_params(
  483. ['**/*.java'],
  484. ['**/*Test.java'],
  485. False,
  486. '/path/to/glob',
  487. False)
  488. self.assertEquals(
  489. {
  490. 'relative_root': '/path/to/glob',
  491. 'path': [''],
  492. 'fields': ['name'],
  493. 'expression': [
  494. 'allof',
  495. ['anyof', ['type', 'f'], ['type', 'l']],
  496. ['not', ['anyof', ['match', '**/*Test.java', 'wholename', {}]]],
  497. 'exists',
  498. ['anyof', ['match', '**/*.java', 'wholename', {}]],
  499. ]
  500. },
  501. query_params)
  502. def test_watchman_query_params_glob_generator(self):
  503. query_params = format_watchman_query_params(
  504. ['**/*.java'],
  505. ['**/*Test.java'],
  506. False,
  507. '/path/to/glob',
  508. True)
  509. self.assertEquals(
  510. {
  511. 'relative_root': '/path/to/glob',
  512. 'glob': ['**/*.java'],
  513. 'fields': ['name'],
  514. 'expression': [
  515. 'allof',
  516. ['anyof', ['type', 'f'], ['type', 'l']],
  517. ['not', ['anyof', ['match', '**/*Test.java', 'wholename', {}]]],
  518. ]
  519. },
  520. query_params)
  521. def test_flatten_dicts_overrides_earlier_keys_with_later_ones(self):
  522. base = {
  523. 'a': 'foo',
  524. 'b': 'bar',
  525. }
  526. override = {
  527. 'a': 'baz',
  528. }
  529. override2 = {
  530. 'a': 42,
  531. 'c': 'new',
  532. }
  533. self.assertEquals(
  534. {
  535. 'a': 'baz',
  536. 'b': 'bar',
  537. },
  538. flatten_dicts(base, override))
  539. self.assertEquals(
  540. {
  541. 'a': 42,
  542. 'b': 'bar',
  543. 'c': 'new',
  544. },
  545. flatten_dicts(base, override, override2)
  546. )
  547. # assert none of the input dicts were changed:
  548. self.assertEquals(
  549. {
  550. 'a': 'foo',
  551. 'b': 'bar',
  552. },
  553. base
  554. )
  555. self.assertEquals(
  556. {
  557. 'a': 'baz',
  558. },
  559. override
  560. )
  561. self.assertEquals(
  562. {
  563. 'a': 42,
  564. 'c': 'new',
  565. },
  566. override2
  567. )
  568. def test_path_component_contains_dot(self):
  569. self.assertFalse(path_component_contains_dot(Path('')))
  570. self.assertFalse(path_component_contains_dot(Path('foo')))
  571. self.assertFalse(path_component_contains_dot(Path('foo/bar')))
  572. self.assertTrue(path_component_contains_dot(Path('.foo/bar')))
  573. self.assertTrue(path_component_contains_dot(Path('foo/.bar')))
  574. self.assertTrue(path_component_contains_dot(Path('.foo/.bar')))
  575. class TestMemoized(unittest.TestCase):
  576. def _makeone(self, func, *args, **kwargs):
  577. from .util import memoized
  578. return memoized(*args, **kwargs)(func)
  579. def test_cache_none(self):
  580. decorated = self._makeone(
  581. lambda _retval=iter([None, 'foo']): next(_retval))
  582. uncached = decorated()
  583. cached = decorated()
  584. self.assertEqual(uncached, cached)
  585. self.assertTrue(cached is None)
  586. def test_no_deepcopy(self):
  587. decorated = self._makeone(
  588. lambda: [],
  589. deepcopy=False,
  590. )
  591. initial = decorated()
  592. cached = decorated()
  593. self.assertTrue(initial is cached)
  594. def test_deepcopy(self):
  595. decorated = self._makeone(
  596. lambda: [{}],
  597. )
  598. initial = decorated()
  599. cached = decorated()
  600. self.assertTrue(initial is not cached)
  601. initial[0]['foo'] = 'bar'
  602. self.assertTrue(cached[0] == {})
  603. def test_cachekey(self):
  604. decorated = self._makeone(
  605. # note that in Python 2 without hash randomisation, 'bar' and 'baz' will collide in
  606. # a small dictionary, as their hash keys differ by 8.
  607. lambda foo, bar='baz', baz='bar', _retval=itertools.count(): next(_retval)
  608. )
  609. initial = decorated(42, baz='spam', bar='eggs')
  610. cached = decorated(42, bar='eggs', baz='spam')
  611. different_keyword_values = decorated(42, bar='eric', baz='idle')
  612. self.assertEqual(initial, cached)
  613. self.assertNotEqual(initial, different_keyword_values)
  614. def test_custom_cachekey(self):
  615. decorated = self._makeone(
  616. lambda foo, bar='baz', _retval=itertools.count(): next(_retval),
  617. keyfunc=lambda foo, **kwargs: foo,
  618. )
  619. initial = decorated(42, bar='spam')
  620. cached = decorated(42, bar='ignored')
  621. different_foo = decorated(81, bar='spam')
  622. self.assertEqual(initial, cached)
  623. self.assertNotEqual(initial, different_foo)
  624. def test_missing_foo(self):
  625. def fn(foo, bar=1, baz=None):
  626. pass
  627. missing, extra = get_mismatched_args(fn, [], {})
  628. self.assertEqual(missing, ['foo'])
  629. self.assertEqual(extra, [])
  630. def test_extra_kwargs(self):
  631. def fn(foo, bar=1, baz=None):
  632. pass
  633. missing, extra = get_mismatched_args(fn, [], {'parrot': 'dead', 'trout': 'slapped'})
  634. self.assertEqual(missing, ['foo'])
  635. self.assertEqual(extra, ['parrot', 'trout'])
  636. def test_foo_as_kwarg(self):
  637. def fn(foo, bar=1, baz=None):
  638. pass
  639. missing, extra = get_mismatched_args(fn, [], {'foo': 'value'})
  640. self.assertEqual(missing, [])
  641. self.assertEqual(extra, [])
  642. if __name__ == '__main__':
  643. unittest.main()