test_cdiff.py 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. """Unit test for cdiff"""
  4. import sys
  5. if sys.hexversion < 0x02050000:
  6. raise SystemExit("*** Requires python >= 2.5.0") # pragma: no cover
  7. import unittest
  8. import tempfile
  9. import subprocess
  10. import os
  11. sys.path.insert(0, '')
  12. import cdiff
  13. class Sequential(object):
  14. """A non-seekable iterator, mock of file object"""
  15. def __init__(self, items):
  16. self._items = items
  17. self._index = 0
  18. def __iter__(self):
  19. while True:
  20. try:
  21. item = self._items[self._index]
  22. except IndexError:
  23. raise StopIteration
  24. yield item
  25. self._index += 1
  26. def readline(self):
  27. try:
  28. item = self._items[self._index]
  29. except IndexError:
  30. return ''
  31. self._index += 1
  32. return item
  33. class TestPatchStream(unittest.TestCase):
  34. def test_is_empty(self):
  35. stream = cdiff.PatchStream(Sequential([]))
  36. self.assertTrue(stream.is_empty())
  37. stream = cdiff.PatchStream(Sequential(['hello', 'world']))
  38. self.assertFalse(stream.is_empty())
  39. def test_read_stream_header(self):
  40. stream = cdiff.PatchStream(Sequential([]))
  41. self.assertEqual(stream.read_stream_header(1), [])
  42. items = ['hello', 'world', 'again']
  43. stream = cdiff.PatchStream(Sequential(items))
  44. self.assertEqual(stream.read_stream_header(2), items[:2])
  45. stream = cdiff.PatchStream(Sequential(items))
  46. self.assertEqual(stream.read_stream_header(4), items[:])
  47. def test_iter_after_read_stream_header(self):
  48. items = ['hello', 'world', 'again', 'and', 'again']
  49. stream = cdiff.PatchStream(Sequential(items))
  50. _ = stream.read_stream_header(2)
  51. out = list(stream)
  52. self.assertEqual(out, items)
  53. class TestHunk(unittest.TestCase):
  54. def test_get_old_text(self):
  55. hunk = cdiff.Hunk([], '@@ -1,2 +1,2 @@', (1, 2), (1, 2))
  56. hunk.append(('-', 'foo\n'))
  57. hunk.append(('+', 'bar\n'))
  58. hunk.append((' ', 'common\n'))
  59. self.assertEqual(hunk._get_old_text(), ['foo\n', 'common\n'])
  60. def test_get_new_text(self):
  61. hunk = cdiff.Hunk([], '@@ -1,2 +1,2 @@', (1, 2), (1, 2))
  62. hunk.append(('-', 'foo\n'))
  63. hunk.append(('+', 'bar\n'))
  64. hunk.append((' ', 'common\n'))
  65. self.assertEqual(hunk._get_new_text(), ['bar\n', 'common\n'])
  66. class TestDiff(unittest.TestCase):
  67. def _init_diff(self):
  68. hunk = cdiff.Hunk(['hunk header\n'], '@@ -1,2 +1,2 @@\n',
  69. (1, 2), (1, 2))
  70. hunk.append(('-', 'hella\n'))
  71. hunk.append(('+', 'hello\n'))
  72. hunk.append((' ', 'world\n'))
  73. diff = cdiff.Diff(['header\n'], '--- old\n', '+++ new\n', [hunk])
  74. return diff
  75. def test_markup_mix(self):
  76. line = 'foo \x00-del\x01 \x00+add\x01 \x00^chg\x01 bar'
  77. base_color = 'red'
  78. diff = cdiff.Diff(None, None, None, None)
  79. self.assertEqual(
  80. diff._markup_mix(line, base_color),
  81. '\x1b[31mfoo \x1b[7m\x1b[31mdel\x1b[0m\x1b[31m '
  82. '\x1b[7m\x1b[31madd\x1b[0m\x1b[31m '
  83. '\x1b[4m\x1b[31mchg\x1b[0m\x1b[31m bar\x1b[0m')
  84. def test_markup_traditional(self):
  85. diff = self._init_diff()
  86. out = list(diff.markup_traditional())
  87. self.assertEqual(len(out), 8)
  88. sys.stdout.write('\n')
  89. for markup in out:
  90. sys.stdout.write(markup)
  91. self.assertEqual(out[0], '\x1b[36mheader\n\x1b[0m')
  92. self.assertEqual(out[1], '\x1b[33m--- old\n\x1b[0m')
  93. self.assertEqual(out[2], '\x1b[33m+++ new\n\x1b[0m')
  94. self.assertEqual(out[3], '\x1b[1;36mhunk header\n\x1b[0m')
  95. self.assertEqual(out[4], '\x1b[1;34m@@ -1,2 +1,2 @@\n\x1b[0m')
  96. self.assertEqual(
  97. out[5],
  98. '\x1b[1;31m-\x1b[0m\x1b[31mhell\x1b[4m'
  99. '\x1b[31ma\x1b[0m\x1b[31m\n\x1b[0m')
  100. self.assertEqual(
  101. out[6],
  102. '\x1b[1;32m+\x1b[0m\x1b[32mhell\x1b[4m'
  103. '\x1b[32mo\x1b[0m\x1b[32m\n\x1b[0m')
  104. self.assertEqual(out[7], '\x1b[0m world\n\x1b[0m')
  105. def test_markup_side_by_side_padded(self):
  106. diff = self._init_diff()
  107. out = list(diff.markup_side_by_side(6))
  108. self.assertEqual(len(out), 7)
  109. sys.stdout.write('\n')
  110. for markup in out:
  111. sys.stdout.write(markup)
  112. self.assertEqual(out[0], '\x1b[36mheader\n\x1b[0m')
  113. self.assertEqual(out[1], '\x1b[33m--- old\n\x1b[0m')
  114. self.assertEqual(out[2], '\x1b[33m+++ new\n\x1b[0m')
  115. self.assertEqual(out[3], '\x1b[1;36mhunk header\n\x1b[0m')
  116. self.assertEqual(out[4], '\x1b[1;34m@@ -1,2 +1,2 @@\n\x1b[0m')
  117. self.assertEqual(
  118. out[5],
  119. '\x1b[33m1\x1b[0m '
  120. '\x1b[31mhell\x1b[4m\x1b[31ma\x1b[0m\x1b[31m\x1b[0m '
  121. '\x1b[0m\x1b[33m1\x1b[0m '
  122. '\x1b[32mhell\x1b[4m\x1b[32mo\x1b[0m\x1b[32m\x1b[0m\n')
  123. self.assertEqual(
  124. out[6],
  125. '\x1b[33m2\x1b[0m '
  126. '\x1b[0mworld\x1b[0m '
  127. '\x1b[0m\x1b[33m2\x1b[0m '
  128. '\x1b[0mworld\x1b[0m\n')
  129. def test_markup_side_by_side_off_by_one(self):
  130. diff = self._init_diff()
  131. out = list(diff.markup_side_by_side(5))
  132. self.assertEqual(len(out), 7)
  133. sys.stdout.write('\n')
  134. for markup in out:
  135. sys.stdout.write(markup)
  136. self.assertEqual(out[0], '\x1b[36mheader\n\x1b[0m')
  137. self.assertEqual(out[1], '\x1b[33m--- old\n\x1b[0m')
  138. self.assertEqual(out[2], '\x1b[33m+++ new\n\x1b[0m')
  139. self.assertEqual(out[3], '\x1b[1;36mhunk header\n\x1b[0m')
  140. self.assertEqual(out[4], '\x1b[1;34m@@ -1,2 +1,2 @@\n\x1b[0m')
  141. self.assertEqual(
  142. out[5],
  143. '\x1b[33m1\x1b[0m '
  144. '\x1b[31mhell\x1b[4m\x1b[31ma\x1b[0m '
  145. '\x1b[0m\x1b[33m1\x1b[0m '
  146. '\x1b[32mhell\x1b[4m\x1b[32mo\x1b[0m\n')
  147. self.assertEqual(
  148. out[6],
  149. '\x1b[33m2\x1b[0m '
  150. '\x1b[0mworld\x1b[0m '
  151. '\x1b[0m\x1b[33m2\x1b[0m '
  152. '\x1b[0mworld\x1b[0m\n')
  153. def test_markup_side_by_side_wrapped(self):
  154. diff = self._init_diff()
  155. out = list(diff.markup_side_by_side(4))
  156. self.assertEqual(len(out), 7)
  157. sys.stdout.write('\n')
  158. for markup in out:
  159. sys.stdout.write(markup)
  160. self.assertEqual(out[0], '\x1b[36mheader\n\x1b[0m')
  161. self.assertEqual(out[1], '\x1b[33m--- old\n\x1b[0m')
  162. self.assertEqual(out[2], '\x1b[33m+++ new\n\x1b[0m')
  163. self.assertEqual(out[3], '\x1b[1;36mhunk header\n\x1b[0m')
  164. self.assertEqual(out[4], '\x1b[1;34m@@ -1,2 +1,2 @@\n\x1b[0m')
  165. self.assertEqual(
  166. out[5],
  167. '\x1b[33m1\x1b[0m '
  168. '\x1b[31mhel\x1b[0m\x1b[1;35m>\x1b[0m '
  169. '\x1b[0m\x1b[33m1\x1b[0m '
  170. '\x1b[32mhel\x1b[0m\x1b[1;35m>\x1b[0m\n')
  171. self.assertEqual(
  172. out[6],
  173. '\x1b[33m2\x1b[0m '
  174. '\x1b[0mwor\x1b[0m\x1b[1;35m>\x1b[0m '
  175. '\x1b[0m\x1b[33m2\x1b[0m '
  176. '\x1b[0mwor\x1b[0m\x1b[1;35m>\x1b[0m\n')
  177. class TestUdiff(unittest.TestCase):
  178. diff = cdiff.Udiff(None, None, None, None)
  179. def test_is_hunk_meta_normal(self):
  180. self.assertTrue(self.diff.is_hunk_meta('@@ -1 +1 @@'))
  181. self.assertTrue(self.diff.is_hunk_meta('@@ -3,7 +3,6 @@'))
  182. self.assertTrue(self.diff.is_hunk_meta('@@ -3,7 +3,6 @@ class Foo'))
  183. self.assertTrue(self.diff.is_hunk_meta('@@ -3,7 +3,6 @@ class Foo\n'))
  184. self.assertTrue(
  185. self.diff.is_hunk_meta('@@ -3,7 +3,6 @@ class Foo\r\n'))
  186. def test_is_hunk_meta_svn_prop(self):
  187. self.assertTrue(self.diff.is_hunk_meta('## -0,0 +1 ##'))
  188. self.assertTrue(self.diff.is_hunk_meta('## -0,0 +1 ##\n'))
  189. self.assertTrue(self.diff.is_hunk_meta('## -0,0 +1 ##\r\n'))
  190. def test_is_hunk_meta_neg(self):
  191. self.assertFalse(self.diff.is_hunk_meta('@@ -1 + @@'))
  192. self.assertFalse(self.diff.is_hunk_meta('@@ -this is not a hunk meta'))
  193. self.assertFalse(self.diff.is_hunk_meta('## -this is not either'))
  194. def test_parse_hunk_meta_normal(self):
  195. self.assertEqual(self.diff.parse_hunk_meta('@@ -3,7 +3,6 @@'),
  196. ((3, 7), (3, 6)))
  197. def test_parse_hunk_meta_missing(self):
  198. self.assertEqual(self.diff.parse_hunk_meta('@@ -3 +3,6 @@'),
  199. ((3, 0), (3, 6)))
  200. self.assertEqual(self.diff.parse_hunk_meta('@@ -3,7 +3 @@'),
  201. ((3, 7), (3, 0)))
  202. self.assertEqual(self.diff.parse_hunk_meta('@@ -3 +3 @@'),
  203. ((3, 0), (3, 0)))
  204. def test_parse_hunk_meta_svn_prop(self):
  205. self.assertEqual(self.diff.parse_hunk_meta('## -0,0 +1 ##'),
  206. ((0, 0), (1, 0)))
  207. def test_is_old(self):
  208. self.assertTrue(self.diff.is_old('-hello world'))
  209. self.assertTrue(self.diff.is_old('----')) # yaml
  210. def test_is_old_neg(self):
  211. self.assertFalse(self.diff.is_old('--- considered as old path'))
  212. self.assertFalse(self.diff.is_old('-------------')) # svn log --diff
  213. def test_is_new(self):
  214. self.assertTrue(self.diff.is_new('+hello world'))
  215. self.assertTrue(self.diff.is_new('++++hello world'))
  216. def test_is_new_neg(self):
  217. self.assertFalse(self.diff.is_new('+++ considered as new path'))
  218. class TestDiffParser(unittest.TestCase):
  219. def test_type_detect(self):
  220. patch = r"""\
  221. spam
  222. --- a
  223. +++ b
  224. @@ -1,2 +1,2 @@
  225. """
  226. items = patch.splitlines(True)
  227. stream = cdiff.PatchStream(Sequential(items))
  228. parser = cdiff.DiffParser(stream)
  229. self.assertEqual(parser._type, 'udiff')
  230. def test_type_detect_neg(self):
  231. patch = r"""\
  232. spam
  233. --- a
  234. +++ b
  235. spam
  236. """
  237. items = patch.splitlines(True)
  238. stream = cdiff.PatchStream(Sequential(items))
  239. self.assertRaises(RuntimeError, cdiff.DiffParser, stream)
  240. def test_parse_dangling_header(self):
  241. patch = r"""\
  242. --- a
  243. +++ b
  244. @@ -1,2 +1,2 @@
  245. -foo
  246. +bar
  247. common
  248. spam
  249. """
  250. items = patch.splitlines(True)
  251. stream = cdiff.PatchStream(Sequential(items))
  252. parser = cdiff.DiffParser(stream)
  253. self.assertRaises(RuntimeError, list, parser._parse())
  254. def test_parse_missing_new_path(self):
  255. patch = r"""\
  256. --- a
  257. +++ b
  258. @@ -1,2 +1,2 @@
  259. -foo
  260. +bar
  261. common
  262. --- c
  263. """
  264. items = patch.splitlines(True)
  265. stream = cdiff.PatchStream(Sequential(items))
  266. parser = cdiff.DiffParser(stream)
  267. self.assertRaises(AssertionError, list, parser._parse())
  268. def test_parse_missing_hunk_meta(self):
  269. patch = r"""\
  270. --- a
  271. +++ b
  272. @@ -1,2 +1,2 @@
  273. -foo
  274. +bar
  275. common
  276. --- c
  277. +++ d
  278. """
  279. items = patch.splitlines(True)
  280. stream = cdiff.PatchStream(Sequential(items))
  281. parser = cdiff.DiffParser(stream)
  282. self.assertRaises(AssertionError, list, parser._parse())
  283. def test_parse_missing_hunk_list(self):
  284. patch = r"""\
  285. --- a
  286. +++ b
  287. @@ -1,2 +1,2 @@
  288. -foo
  289. +bar
  290. common
  291. --- c
  292. +++ d
  293. @@ -1,2 +1,2 @@
  294. """
  295. items = patch.splitlines(True)
  296. stream = cdiff.PatchStream(Sequential(items))
  297. parser = cdiff.DiffParser(stream)
  298. self.assertRaises(AssertionError, list, parser._parse())
  299. def test_parse_only_in_dir(self):
  300. patch = r"""\
  301. --- a
  302. +++ b
  303. @@ -1,2 +1,2 @@
  304. -foo
  305. +bar
  306. common
  307. Only in foo: foo
  308. --- c
  309. +++ d
  310. @@ -1,2 +1,2 @@
  311. -foo
  312. +bar
  313. common
  314. """
  315. items = patch.splitlines(True)
  316. stream = cdiff.PatchStream(Sequential(items))
  317. parser = cdiff.DiffParser(stream)
  318. out = list(parser._parse())
  319. self.assertEqual(len(out), 3)
  320. self.assertEqual(len(out[1]._hunks), 0)
  321. self.assertEqual(out[1]._headers, ['Only in foo: foo\n'])
  322. self.assertEqual(len(out[2]._hunks), 1)
  323. self.assertEqual(len(out[2]._hunks[0]._hunk_list), 3)
  324. def test_parse_only_in_dir_at_last(self):
  325. patch = r"""\
  326. --- a
  327. +++ b
  328. @@ -1,2 +1,2 @@
  329. -foo
  330. +bar
  331. common
  332. Only in foo: foo
  333. """
  334. items = patch.splitlines(True)
  335. stream = cdiff.PatchStream(Sequential(items))
  336. parser = cdiff.DiffParser(stream)
  337. out = list(parser._parse())
  338. self.assertEqual(len(out), 2)
  339. self.assertEqual(len(out[1]._hunks), 0)
  340. self.assertEqual(out[1]._headers, ['Only in foo: foo\n'])
  341. def test_parse_binary_differ_diff_ru(self):
  342. patch = r"""\
  343. --- a
  344. +++ b
  345. @@ -1,2 +1,2 @@
  346. -foo
  347. +bar
  348. common
  349. Binary files a/1.pdf and b/1.pdf differ
  350. --- c
  351. +++ d
  352. @@ -1,2 +1,2 @@
  353. -foo
  354. +bar
  355. common
  356. """
  357. items = patch.splitlines(True)
  358. stream = cdiff.PatchStream(Sequential(items))
  359. parser = cdiff.DiffParser(stream)
  360. out = list(parser._parse())
  361. self.assertEqual(len(out), 3)
  362. self.assertEqual(len(out[1]._hunks), 0)
  363. self.assertEqual(out[1]._old_path, '')
  364. self.assertEqual(out[1]._new_path, '')
  365. self.assertEqual(len(out[1]._headers), 1)
  366. self.assertTrue(out[1]._headers[0].startswith('Binary files'))
  367. self.assertEqual(len(out[2]._hunks), 1)
  368. self.assertEqual(len(out[2]._hunks[0]._hunk_list), 3)
  369. def test_parse_binary_differ_git(self):
  370. patch = r"""\
  371. diff --git a/foo b/foo
  372. index 529d8a3..ad71911 100755
  373. --- a/foo
  374. +++ b/foo
  375. @@ -1,2 +1,2 @@
  376. -foo
  377. +bar
  378. common
  379. diff --git a/example.pdf b/example.pdf
  380. index 1eacfd8..3696851 100644
  381. Binary files a/example.pdf and b/example.pdf differ
  382. diff --git a/bar b/bar
  383. index 529e8a3..ad71921 100755
  384. --- a/bar
  385. +++ b/bar
  386. @@ -1,2 +1,2 @@
  387. -foo
  388. +bar
  389. common
  390. """
  391. items = patch.splitlines(True)
  392. stream = cdiff.PatchStream(Sequential(items))
  393. parser = cdiff.DiffParser(stream)
  394. out = list(parser._parse())
  395. self.assertEqual(len(out), 3)
  396. self.assertEqual(len(out[1]._hunks), 0)
  397. self.assertEqual(out[1]._old_path, '')
  398. self.assertEqual(out[1]._new_path, '')
  399. self.assertEqual(len(out[1]._headers), 3)
  400. self.assertTrue(out[1]._headers[2].startswith('Binary files'))
  401. self.assertEqual(len(out[2]._hunks), 1)
  402. self.assertEqual(len(out[2]._hunks[0]._hunk_list), 3)
  403. def test_parse_svn_prop(self):
  404. patch = r"""\
  405. --- a
  406. +++ b
  407. Added: svn:executable
  408. ## -0,0 +1 ##
  409. +*
  410. \ No newline at end of property
  411. Added: svn:keywords
  412. ## -0,0 +1 ##
  413. +Id
  414. """
  415. items = patch.splitlines(True)
  416. stream = cdiff.PatchStream(Sequential(items))
  417. parser = cdiff.DiffParser(stream)
  418. out = list(parser._parse())
  419. self.assertEqual(len(out), 1)
  420. self.assertEqual(len(out[0]._hunks), 2)
  421. hunk = out[0]._hunks[1]
  422. self.assertEqual(hunk._hunk_headers, ['Added: svn:keywords\n'])
  423. self.assertEqual(hunk._hunk_list, [('+', 'Id\n')])
  424. class TestMain(unittest.TestCase):
  425. def setUp(self):
  426. self._cwd = os.getcwd()
  427. self._ws = tempfile.mkdtemp(prefix='test_cdiff')
  428. self._non_ws = tempfile.mkdtemp(prefix='test_cdiff')
  429. cmd = ('cd %s; git init; git config user.name me; '
  430. 'git config user.email me@example.org') % self._ws
  431. subprocess.call(cmd, shell=True, stdout=subprocess.PIPE)
  432. self._change_file('init')
  433. def tearDown(self):
  434. os.chdir(self._cwd)
  435. cmd = ['/bin/rm', '-rf', self._ws, self._non_ws]
  436. subprocess.call(cmd)
  437. def _change_file(self, text):
  438. cmd = ['/bin/sh', '-c',
  439. 'cd %s; echo "%s" > foo' % (self._ws, text)]
  440. subprocess.call(cmd)
  441. def _commit_file(self):
  442. cmd = ['/bin/sh', '-c',
  443. 'cd %s; git add foo; git commit foo -m update' % self._ws]
  444. subprocess.call(cmd, stdout=subprocess.PIPE)
  445. def test_read_diff(self):
  446. sys.argv = sys.argv[:1]
  447. self._change_file('read_diff')
  448. os.chdir(self._ws)
  449. ret = cdiff.main()
  450. os.chdir(self._cwd)
  451. self.assertEqual(ret, 0)
  452. def test_read_diff_neg(self):
  453. sys.argv = sys.argv[:1]
  454. os.chdir(self._non_ws)
  455. ret = cdiff.main()
  456. os.chdir(self._cwd)
  457. self.assertNotEqual(ret, 0)
  458. def test_read_log(self):
  459. sys.argv = [sys.argv[0], '--log']
  460. self._change_file('read_log')
  461. self._commit_file()
  462. os.chdir(self._ws)
  463. ret = cdiff.main()
  464. os.chdir(self._cwd)
  465. self.assertEqual(ret, 0)
  466. def test_read_log_neg(self):
  467. sys.argv = [sys.argv[0], '--log']
  468. os.chdir(self._non_ws)
  469. ret = cdiff.main()
  470. os.chdir(self._cwd)
  471. self.assertNotEqual(ret, 0)
  472. if __name__ == '__main__':
  473. unittest.main()
  474. # vim:set et sts=4 sw=4 tw=80: