test_server.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944
  1. # test_server.py -- Tests for the git server
  2. # Copyright (C) 2010 Google, Inc.
  3. #
  4. # This program is free software; you can redistribute it and/or
  5. # modify it under the terms of the GNU General Public License
  6. # as published by the Free Software Foundation; version 2
  7. # or (at your option) any later version of the License.
  8. #
  9. # This program is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. # GNU General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU General Public License
  15. # along with this program; if not, write to the Free Software
  16. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
  17. # MA 02110-1301, USA.
  18. """Tests for the smart protocol server."""
  19. from io import BytesIO
  20. import os
  21. import tempfile
  22. from dulwich.errors import (
  23. GitProtocolError,
  24. NotGitRepository,
  25. UnexpectedCommandError,
  26. HangupException,
  27. )
  28. from dulwich.objects import (
  29. Commit,
  30. Tag,
  31. )
  32. from dulwich.object_store import (
  33. MemoryObjectStore,
  34. )
  35. from dulwich.repo import (
  36. MemoryRepo,
  37. Repo,
  38. )
  39. from dulwich.server import (
  40. Backend,
  41. DictBackend,
  42. FileSystemBackend,
  43. Handler,
  44. MultiAckGraphWalkerImpl,
  45. MultiAckDetailedGraphWalkerImpl,
  46. _split_proto_line,
  47. serve_command,
  48. _find_shallow,
  49. ProtocolGraphWalker,
  50. ReceivePackHandler,
  51. SingleAckGraphWalkerImpl,
  52. UploadPackHandler,
  53. update_server_info,
  54. )
  55. from dulwich.tests import TestCase
  56. from dulwich.tests.utils import (
  57. make_commit,
  58. make_object,
  59. skipIfPY3,
  60. )
  61. from dulwich.protocol import (
  62. ZERO_SHA,
  63. )
  64. ONE = '1' * 40
  65. TWO = '2' * 40
  66. THREE = '3' * 40
  67. FOUR = '4' * 40
  68. FIVE = '5' * 40
  69. SIX = '6' * 40
  70. class TestProto(object):
  71. def __init__(self):
  72. self._output = []
  73. self._received = {0: [], 1: [], 2: [], 3: []}
  74. def set_output(self, output_lines):
  75. self._output = output_lines
  76. def read_pkt_line(self):
  77. if self._output:
  78. data = self._output.pop(0)
  79. if data is not None:
  80. return '%s\n' % data.rstrip()
  81. else:
  82. # flush-pkt ('0000').
  83. return None
  84. else:
  85. raise HangupException()
  86. def write_sideband(self, band, data):
  87. self._received[band].append(data)
  88. def write_pkt_line(self, data):
  89. self._received[0].append(data)
  90. def get_received_line(self, band=0):
  91. lines = self._received[band]
  92. return lines.pop(0)
  93. class TestGenericHandler(Handler):
  94. def __init__(self):
  95. Handler.__init__(self, Backend(), None)
  96. @classmethod
  97. def capabilities(cls):
  98. return ('cap1', 'cap2', 'cap3')
  99. @classmethod
  100. def required_capabilities(cls):
  101. return ('cap2',)
  102. class HandlerTestCase(TestCase):
  103. def setUp(self):
  104. super(HandlerTestCase, self).setUp()
  105. self._handler = TestGenericHandler()
  106. def assertSucceeds(self, func, *args, **kwargs):
  107. try:
  108. func(*args, **kwargs)
  109. except GitProtocolError as e:
  110. self.fail(e)
  111. def test_capability_line(self):
  112. self.assertEqual('cap1 cap2 cap3', self._handler.capability_line())
  113. def test_set_client_capabilities(self):
  114. set_caps = self._handler.set_client_capabilities
  115. self.assertSucceeds(set_caps, ['cap2'])
  116. self.assertSucceeds(set_caps, ['cap1', 'cap2'])
  117. # different order
  118. self.assertSucceeds(set_caps, ['cap3', 'cap1', 'cap2'])
  119. # error cases
  120. self.assertRaises(GitProtocolError, set_caps, ['capxxx', 'cap2'])
  121. self.assertRaises(GitProtocolError, set_caps, ['cap1', 'cap3'])
  122. # ignore innocuous but unknown capabilities
  123. self.assertRaises(GitProtocolError, set_caps, ['cap2', 'ignoreme'])
  124. self.assertFalse('ignoreme' in self._handler.capabilities())
  125. self._handler.innocuous_capabilities = lambda: ('ignoreme',)
  126. self.assertSucceeds(set_caps, ['cap2', 'ignoreme'])
  127. def test_has_capability(self):
  128. self.assertRaises(GitProtocolError, self._handler.has_capability, 'cap')
  129. caps = self._handler.capabilities()
  130. self._handler.set_client_capabilities(caps)
  131. for cap in caps:
  132. self.assertTrue(self._handler.has_capability(cap))
  133. self.assertFalse(self._handler.has_capability('capxxx'))
  134. @skipIfPY3
  135. class UploadPackHandlerTestCase(TestCase):
  136. def setUp(self):
  137. super(UploadPackHandlerTestCase, self).setUp()
  138. self._repo = MemoryRepo.init_bare([], {})
  139. backend = DictBackend({'/': self._repo})
  140. self._handler = UploadPackHandler(
  141. backend, ['/', 'host=lolcathost'], TestProto())
  142. def test_progress(self):
  143. caps = self._handler.required_capabilities()
  144. self._handler.set_client_capabilities(caps)
  145. self._handler.progress('first message')
  146. self._handler.progress('second message')
  147. self.assertEqual('first message',
  148. self._handler.proto.get_received_line(2))
  149. self.assertEqual('second message',
  150. self._handler.proto.get_received_line(2))
  151. self.assertRaises(IndexError, self._handler.proto.get_received_line, 2)
  152. def test_no_progress(self):
  153. caps = list(self._handler.required_capabilities()) + ['no-progress']
  154. self._handler.set_client_capabilities(caps)
  155. self._handler.progress('first message')
  156. self._handler.progress('second message')
  157. self.assertRaises(IndexError, self._handler.proto.get_received_line, 2)
  158. def test_get_tagged(self):
  159. refs = {
  160. 'refs/tags/tag1': ONE,
  161. 'refs/tags/tag2': TWO,
  162. 'refs/heads/master': FOUR, # not a tag, no peeled value
  163. }
  164. # repo needs to peel this object
  165. self._repo.object_store.add_object(make_commit(id=FOUR))
  166. self._repo.refs._update(refs)
  167. peeled = {
  168. 'refs/tags/tag1': '1234' * 10,
  169. 'refs/tags/tag2': '5678' * 10,
  170. }
  171. self._repo.refs._update_peeled(peeled)
  172. caps = list(self._handler.required_capabilities()) + ['include-tag']
  173. self._handler.set_client_capabilities(caps)
  174. self.assertEqual({'1234' * 10: ONE, '5678' * 10: TWO},
  175. self._handler.get_tagged(refs, repo=self._repo))
  176. # non-include-tag case
  177. caps = self._handler.required_capabilities()
  178. self._handler.set_client_capabilities(caps)
  179. self.assertEqual({}, self._handler.get_tagged(refs, repo=self._repo))
  180. @skipIfPY3
  181. class FindShallowTests(TestCase):
  182. def setUp(self):
  183. self._store = MemoryObjectStore()
  184. def make_commit(self, **attrs):
  185. commit = make_commit(**attrs)
  186. self._store.add_object(commit)
  187. return commit
  188. def make_linear_commits(self, n, message=''):
  189. commits = []
  190. parents = []
  191. for _ in range(n):
  192. commits.append(self.make_commit(parents=parents, message=message))
  193. parents = [commits[-1].id]
  194. return commits
  195. def assertSameElements(self, expected, actual):
  196. self.assertEqual(set(expected), set(actual))
  197. def test_linear(self):
  198. c1, c2, c3 = self.make_linear_commits(3)
  199. self.assertEqual((set([c3.id]), set([])),
  200. _find_shallow(self._store, [c3.id], 0))
  201. self.assertEqual((set([c2.id]), set([c3.id])),
  202. _find_shallow(self._store, [c3.id], 1))
  203. self.assertEqual((set([c1.id]), set([c2.id, c3.id])),
  204. _find_shallow(self._store, [c3.id], 2))
  205. self.assertEqual((set([]), set([c1.id, c2.id, c3.id])),
  206. _find_shallow(self._store, [c3.id], 3))
  207. def test_multiple_independent(self):
  208. a = self.make_linear_commits(2, message='a')
  209. b = self.make_linear_commits(2, message='b')
  210. c = self.make_linear_commits(2, message='c')
  211. heads = [a[1].id, b[1].id, c[1].id]
  212. self.assertEqual((set([a[0].id, b[0].id, c[0].id]), set(heads)),
  213. _find_shallow(self._store, heads, 1))
  214. def test_multiple_overlapping(self):
  215. # Create the following commit tree:
  216. # 1--2
  217. # \
  218. # 3--4
  219. c1, c2 = self.make_linear_commits(2)
  220. c3 = self.make_commit(parents=[c1.id])
  221. c4 = self.make_commit(parents=[c3.id])
  222. # 1 is shallow along the path from 4, but not along the path from 2.
  223. self.assertEqual((set([c1.id]), set([c1.id, c2.id, c3.id, c4.id])),
  224. _find_shallow(self._store, [c2.id, c4.id], 2))
  225. def test_merge(self):
  226. c1 = self.make_commit()
  227. c2 = self.make_commit()
  228. c3 = self.make_commit(parents=[c1.id, c2.id])
  229. self.assertEqual((set([c1.id, c2.id]), set([c3.id])),
  230. _find_shallow(self._store, [c3.id], 1))
  231. def test_tag(self):
  232. c1, c2 = self.make_linear_commits(2)
  233. tag = make_object(Tag, name='tag', message='',
  234. tagger='Tagger <test@example.com>',
  235. tag_time=12345, tag_timezone=0,
  236. object=(Commit, c2.id))
  237. self._store.add_object(tag)
  238. self.assertEqual((set([c1.id]), set([c2.id])),
  239. _find_shallow(self._store, [tag.id], 1))
  240. class TestUploadPackHandler(UploadPackHandler):
  241. @classmethod
  242. def required_capabilities(self):
  243. return ()
  244. @skipIfPY3
  245. class ReceivePackHandlerTestCase(TestCase):
  246. def setUp(self):
  247. super(ReceivePackHandlerTestCase, self).setUp()
  248. self._repo = MemoryRepo.init_bare([], {})
  249. backend = DictBackend({'/': self._repo})
  250. self._handler = ReceivePackHandler(
  251. backend, ['/', 'host=lolcathost'], TestProto())
  252. def test_apply_pack_del_ref(self):
  253. refs = {
  254. 'refs/heads/master': TWO,
  255. 'refs/heads/fake-branch': ONE}
  256. self._repo.refs._update(refs)
  257. update_refs = [[ONE, ZERO_SHA, 'refs/heads/fake-branch'], ]
  258. status = self._handler._apply_pack(update_refs)
  259. self.assertEqual(status[0][0], 'unpack')
  260. self.assertEqual(status[0][1], 'ok')
  261. self.assertEqual(status[1][0], 'refs/heads/fake-branch')
  262. self.assertEqual(status[1][1], 'ok')
  263. @skipIfPY3
  264. class ProtocolGraphWalkerEmptyTestCase(TestCase):
  265. def setUp(self):
  266. super(ProtocolGraphWalkerEmptyTestCase, self).setUp()
  267. self._repo = MemoryRepo.init_bare([], {})
  268. backend = DictBackend({'/': self._repo})
  269. self._walker = ProtocolGraphWalker(
  270. TestUploadPackHandler(backend, ['/', 'host=lolcats'], TestProto()),
  271. self._repo.object_store, self._repo.get_peeled)
  272. def test_empty_repository(self):
  273. # The server should wait for a flush packet.
  274. self._walker.proto.set_output([])
  275. self.assertRaises(HangupException, self._walker.determine_wants, {})
  276. self.assertEqual(None, self._walker.proto.get_received_line())
  277. self._walker.proto.set_output([None])
  278. self.assertEqual([], self._walker.determine_wants({}))
  279. self.assertEqual(None, self._walker.proto.get_received_line())
  280. @skipIfPY3
  281. class ProtocolGraphWalkerTestCase(TestCase):
  282. def setUp(self):
  283. super(ProtocolGraphWalkerTestCase, self).setUp()
  284. # Create the following commit tree:
  285. # 3---5
  286. # /
  287. # 1---2---4
  288. commits = [
  289. make_commit(id=ONE, parents=[], commit_time=111),
  290. make_commit(id=TWO, parents=[ONE], commit_time=222),
  291. make_commit(id=THREE, parents=[ONE], commit_time=333),
  292. make_commit(id=FOUR, parents=[TWO], commit_time=444),
  293. make_commit(id=FIVE, parents=[THREE], commit_time=555),
  294. ]
  295. self._repo = MemoryRepo.init_bare(commits, {})
  296. backend = DictBackend({'/': self._repo})
  297. self._walker = ProtocolGraphWalker(
  298. TestUploadPackHandler(backend, ['/', 'host=lolcats'], TestProto()),
  299. self._repo.object_store, self._repo.get_peeled)
  300. def test_all_wants_satisfied_no_haves(self):
  301. self._walker.set_wants([ONE])
  302. self.assertFalse(self._walker.all_wants_satisfied([]))
  303. self._walker.set_wants([TWO])
  304. self.assertFalse(self._walker.all_wants_satisfied([]))
  305. self._walker.set_wants([THREE])
  306. self.assertFalse(self._walker.all_wants_satisfied([]))
  307. def test_all_wants_satisfied_have_root(self):
  308. self._walker.set_wants([ONE])
  309. self.assertTrue(self._walker.all_wants_satisfied([ONE]))
  310. self._walker.set_wants([TWO])
  311. self.assertTrue(self._walker.all_wants_satisfied([ONE]))
  312. self._walker.set_wants([THREE])
  313. self.assertTrue(self._walker.all_wants_satisfied([ONE]))
  314. def test_all_wants_satisfied_have_branch(self):
  315. self._walker.set_wants([TWO])
  316. self.assertTrue(self._walker.all_wants_satisfied([TWO]))
  317. # wrong branch
  318. self._walker.set_wants([THREE])
  319. self.assertFalse(self._walker.all_wants_satisfied([TWO]))
  320. def test_all_wants_satisfied(self):
  321. self._walker.set_wants([FOUR, FIVE])
  322. # trivial case: wants == haves
  323. self.assertTrue(self._walker.all_wants_satisfied([FOUR, FIVE]))
  324. # cases that require walking the commit tree
  325. self.assertTrue(self._walker.all_wants_satisfied([ONE]))
  326. self.assertFalse(self._walker.all_wants_satisfied([TWO]))
  327. self.assertFalse(self._walker.all_wants_satisfied([THREE]))
  328. self.assertTrue(self._walker.all_wants_satisfied([TWO, THREE]))
  329. def test_split_proto_line(self):
  330. allowed = ('want', 'done', None)
  331. self.assertEqual(('want', ONE),
  332. _split_proto_line('want %s\n' % ONE, allowed))
  333. self.assertEqual(('want', TWO),
  334. _split_proto_line('want %s\n' % TWO, allowed))
  335. self.assertRaises(GitProtocolError, _split_proto_line,
  336. 'want xxxx\n', allowed)
  337. self.assertRaises(UnexpectedCommandError, _split_proto_line,
  338. 'have %s\n' % THREE, allowed)
  339. self.assertRaises(GitProtocolError, _split_proto_line,
  340. 'foo %s\n' % FOUR, allowed)
  341. self.assertRaises(GitProtocolError, _split_proto_line, 'bar', allowed)
  342. self.assertEqual(('done', None), _split_proto_line('done\n', allowed))
  343. self.assertEqual((None, None), _split_proto_line('', allowed))
  344. def test_determine_wants(self):
  345. self._walker.proto.set_output([None])
  346. self.assertEqual([], self._walker.determine_wants({}))
  347. self.assertEqual(None, self._walker.proto.get_received_line())
  348. self._walker.proto.set_output([
  349. 'want %s multi_ack' % ONE,
  350. 'want %s' % TWO,
  351. None,
  352. ])
  353. heads = {
  354. 'refs/heads/ref1': ONE,
  355. 'refs/heads/ref2': TWO,
  356. 'refs/heads/ref3': THREE,
  357. }
  358. self._repo.refs._update(heads)
  359. self.assertEqual([ONE, TWO], self._walker.determine_wants(heads))
  360. self._walker.advertise_refs = True
  361. self.assertEqual([], self._walker.determine_wants(heads))
  362. self._walker.advertise_refs = False
  363. self._walker.proto.set_output(['want %s multi_ack' % FOUR, None])
  364. self.assertRaises(GitProtocolError, self._walker.determine_wants, heads)
  365. self._walker.proto.set_output([None])
  366. self.assertEqual([], self._walker.determine_wants(heads))
  367. self._walker.proto.set_output(['want %s multi_ack' % ONE, 'foo', None])
  368. self.assertRaises(GitProtocolError, self._walker.determine_wants, heads)
  369. self._walker.proto.set_output(['want %s multi_ack' % FOUR, None])
  370. self.assertRaises(GitProtocolError, self._walker.determine_wants, heads)
  371. def test_determine_wants_advertisement(self):
  372. self._walker.proto.set_output([None])
  373. # advertise branch tips plus tag
  374. heads = {
  375. 'refs/heads/ref4': FOUR,
  376. 'refs/heads/ref5': FIVE,
  377. 'refs/heads/tag6': SIX,
  378. }
  379. self._repo.refs._update(heads)
  380. self._repo.refs._update_peeled(heads)
  381. self._repo.refs._update_peeled({'refs/heads/tag6': FIVE})
  382. self._walker.determine_wants(heads)
  383. lines = []
  384. while True:
  385. line = self._walker.proto.get_received_line()
  386. if line is None:
  387. break
  388. # strip capabilities list if present
  389. if '\x00' in line:
  390. line = line[:line.index('\x00')]
  391. lines.append(line.rstrip())
  392. self.assertEqual([
  393. '%s refs/heads/ref4' % FOUR,
  394. '%s refs/heads/ref5' % FIVE,
  395. '%s refs/heads/tag6^{}' % FIVE,
  396. '%s refs/heads/tag6' % SIX,
  397. ], sorted(lines))
  398. # ensure peeled tag was advertised immediately following tag
  399. for i, line in enumerate(lines):
  400. if line.endswith(' refs/heads/tag6'):
  401. self.assertEqual('%s refs/heads/tag6^{}' % FIVE, lines[i+1])
  402. # TODO: test commit time cutoff
  403. def _handle_shallow_request(self, lines, heads):
  404. self._walker.proto.set_output(lines + [None])
  405. self._walker._handle_shallow_request(heads)
  406. def assertReceived(self, expected):
  407. self.assertEqual(
  408. expected, list(iter(self._walker.proto.get_received_line, None)))
  409. def test_handle_shallow_request_no_client_shallows(self):
  410. self._handle_shallow_request(['deepen 1\n'], [FOUR, FIVE])
  411. self.assertEqual(set([TWO, THREE]), self._walker.shallow)
  412. self.assertReceived([
  413. 'shallow %s' % TWO,
  414. 'shallow %s' % THREE,
  415. ])
  416. def test_handle_shallow_request_no_new_shallows(self):
  417. lines = [
  418. 'shallow %s\n' % TWO,
  419. 'shallow %s\n' % THREE,
  420. 'deepen 1\n',
  421. ]
  422. self._handle_shallow_request(lines, [FOUR, FIVE])
  423. self.assertEqual(set([TWO, THREE]), self._walker.shallow)
  424. self.assertReceived([])
  425. def test_handle_shallow_request_unshallows(self):
  426. lines = [
  427. 'shallow %s\n' % TWO,
  428. 'deepen 2\n',
  429. ]
  430. self._handle_shallow_request(lines, [FOUR, FIVE])
  431. self.assertEqual(set([ONE]), self._walker.shallow)
  432. self.assertReceived([
  433. 'shallow %s' % ONE,
  434. 'unshallow %s' % TWO,
  435. # THREE is unshallow but was is not shallow in the client
  436. ])
  437. @skipIfPY3
  438. class TestProtocolGraphWalker(object):
  439. def __init__(self):
  440. self.acks = []
  441. self.lines = []
  442. self.done = False
  443. self.http_req = None
  444. self.advertise_refs = False
  445. def read_proto_line(self, allowed):
  446. command, sha = self.lines.pop(0)
  447. if allowed is not None:
  448. assert command in allowed
  449. return command, sha
  450. def send_ack(self, sha, ack_type=''):
  451. self.acks.append((sha, ack_type))
  452. def send_nak(self):
  453. self.acks.append((None, 'nak'))
  454. def all_wants_satisfied(self, haves):
  455. return self.done
  456. def pop_ack(self):
  457. if not self.acks:
  458. return None
  459. return self.acks.pop(0)
  460. @skipIfPY3
  461. class AckGraphWalkerImplTestCase(TestCase):
  462. """Base setup and asserts for AckGraphWalker tests."""
  463. def setUp(self):
  464. super(AckGraphWalkerImplTestCase, self).setUp()
  465. self._walker = TestProtocolGraphWalker()
  466. self._walker.lines = [
  467. ('have', TWO),
  468. ('have', ONE),
  469. ('have', THREE),
  470. ('done', None),
  471. ]
  472. self._impl = self.impl_cls(self._walker)
  473. def assertNoAck(self):
  474. self.assertEqual(None, self._walker.pop_ack())
  475. def assertAcks(self, acks):
  476. for sha, ack_type in acks:
  477. self.assertEqual((sha, ack_type), self._walker.pop_ack())
  478. self.assertNoAck()
  479. def assertAck(self, sha, ack_type=''):
  480. self.assertAcks([(sha, ack_type)])
  481. def assertNak(self):
  482. self.assertAck(None, 'nak')
  483. def assertNextEquals(self, sha):
  484. self.assertEqual(sha, next(self._impl))
  485. @skipIfPY3
  486. class SingleAckGraphWalkerImplTestCase(AckGraphWalkerImplTestCase):
  487. impl_cls = SingleAckGraphWalkerImpl
  488. def test_single_ack(self):
  489. self.assertNextEquals(TWO)
  490. self.assertNoAck()
  491. self.assertNextEquals(ONE)
  492. self._walker.done = True
  493. self._impl.ack(ONE)
  494. self.assertAck(ONE)
  495. self.assertNextEquals(THREE)
  496. self._impl.ack(THREE)
  497. self.assertNoAck()
  498. self.assertNextEquals(None)
  499. self.assertNoAck()
  500. def test_single_ack_flush(self):
  501. # same as ack test but ends with a flush-pkt instead of done
  502. self._walker.lines[-1] = (None, None)
  503. self.assertNextEquals(TWO)
  504. self.assertNoAck()
  505. self.assertNextEquals(ONE)
  506. self._walker.done = True
  507. self._impl.ack(ONE)
  508. self.assertAck(ONE)
  509. self.assertNextEquals(THREE)
  510. self.assertNoAck()
  511. self.assertNextEquals(None)
  512. self.assertNoAck()
  513. def test_single_ack_nak(self):
  514. self.assertNextEquals(TWO)
  515. self.assertNoAck()
  516. self.assertNextEquals(ONE)
  517. self.assertNoAck()
  518. self.assertNextEquals(THREE)
  519. self.assertNoAck()
  520. self.assertNextEquals(None)
  521. self.assertNak()
  522. def test_single_ack_nak_flush(self):
  523. # same as nak test but ends with a flush-pkt instead of done
  524. self._walker.lines[-1] = (None, None)
  525. self.assertNextEquals(TWO)
  526. self.assertNoAck()
  527. self.assertNextEquals(ONE)
  528. self.assertNoAck()
  529. self.assertNextEquals(THREE)
  530. self.assertNoAck()
  531. self.assertNextEquals(None)
  532. self.assertNak()
  533. @skipIfPY3
  534. class MultiAckGraphWalkerImplTestCase(AckGraphWalkerImplTestCase):
  535. impl_cls = MultiAckGraphWalkerImpl
  536. def test_multi_ack(self):
  537. self.assertNextEquals(TWO)
  538. self.assertNoAck()
  539. self.assertNextEquals(ONE)
  540. self._walker.done = True
  541. self._impl.ack(ONE)
  542. self.assertAck(ONE, 'continue')
  543. self.assertNextEquals(THREE)
  544. self._impl.ack(THREE)
  545. self.assertAck(THREE, 'continue')
  546. self.assertNextEquals(None)
  547. self.assertAck(THREE)
  548. def test_multi_ack_partial(self):
  549. self.assertNextEquals(TWO)
  550. self.assertNoAck()
  551. self.assertNextEquals(ONE)
  552. self._impl.ack(ONE)
  553. self.assertAck(ONE, 'continue')
  554. self.assertNextEquals(THREE)
  555. self.assertNoAck()
  556. self.assertNextEquals(None)
  557. # done, re-send ack of last common
  558. self.assertAck(ONE)
  559. def test_multi_ack_flush(self):
  560. self._walker.lines = [
  561. ('have', TWO),
  562. (None, None),
  563. ('have', ONE),
  564. ('have', THREE),
  565. ('done', None),
  566. ]
  567. self.assertNextEquals(TWO)
  568. self.assertNoAck()
  569. self.assertNextEquals(ONE)
  570. self.assertNak() # nak the flush-pkt
  571. self._walker.done = True
  572. self._impl.ack(ONE)
  573. self.assertAck(ONE, 'continue')
  574. self.assertNextEquals(THREE)
  575. self._impl.ack(THREE)
  576. self.assertAck(THREE, 'continue')
  577. self.assertNextEquals(None)
  578. self.assertAck(THREE)
  579. def test_multi_ack_nak(self):
  580. self.assertNextEquals(TWO)
  581. self.assertNoAck()
  582. self.assertNextEquals(ONE)
  583. self.assertNoAck()
  584. self.assertNextEquals(THREE)
  585. self.assertNoAck()
  586. self.assertNextEquals(None)
  587. self.assertNak()
  588. @skipIfPY3
  589. class MultiAckDetailedGraphWalkerImplTestCase(AckGraphWalkerImplTestCase):
  590. impl_cls = MultiAckDetailedGraphWalkerImpl
  591. def test_multi_ack(self):
  592. self.assertNextEquals(TWO)
  593. self.assertNoAck()
  594. self.assertNextEquals(ONE)
  595. self._walker.done = True
  596. self._impl.ack(ONE)
  597. self.assertAcks([(ONE, 'common'), (ONE, 'ready')])
  598. self.assertNextEquals(THREE)
  599. self._impl.ack(THREE)
  600. self.assertAck(THREE, 'ready')
  601. self.assertNextEquals(None)
  602. self.assertAck(THREE)
  603. def test_multi_ack_partial(self):
  604. self.assertNextEquals(TWO)
  605. self.assertNoAck()
  606. self.assertNextEquals(ONE)
  607. self._impl.ack(ONE)
  608. self.assertAck(ONE, 'common')
  609. self.assertNextEquals(THREE)
  610. self.assertNoAck()
  611. self.assertNextEquals(None)
  612. # done, re-send ack of last common
  613. self.assertAck(ONE)
  614. def test_multi_ack_flush(self):
  615. # same as ack test but contains a flush-pkt in the middle
  616. self._walker.lines = [
  617. ('have', TWO),
  618. (None, None),
  619. ('have', ONE),
  620. ('have', THREE),
  621. ('done', None),
  622. ]
  623. self.assertNextEquals(TWO)
  624. self.assertNoAck()
  625. self.assertNextEquals(ONE)
  626. self.assertNak() # nak the flush-pkt
  627. self._walker.done = True
  628. self._impl.ack(ONE)
  629. self.assertAcks([(ONE, 'common'), (ONE, 'ready')])
  630. self.assertNextEquals(THREE)
  631. self._impl.ack(THREE)
  632. self.assertAck(THREE, 'ready')
  633. self.assertNextEquals(None)
  634. self.assertAck(THREE)
  635. def test_multi_ack_nak(self):
  636. self.assertNextEquals(TWO)
  637. self.assertNoAck()
  638. self.assertNextEquals(ONE)
  639. self.assertNoAck()
  640. self.assertNextEquals(THREE)
  641. self.assertNoAck()
  642. self.assertNextEquals(None)
  643. self.assertNak()
  644. def test_multi_ack_nak_flush(self):
  645. # same as nak test but contains a flush-pkt in the middle
  646. self._walker.lines = [
  647. ('have', TWO),
  648. (None, None),
  649. ('have', ONE),
  650. ('have', THREE),
  651. ('done', None),
  652. ]
  653. self.assertNextEquals(TWO)
  654. self.assertNoAck()
  655. self.assertNextEquals(ONE)
  656. self.assertNak()
  657. self.assertNextEquals(THREE)
  658. self.assertNoAck()
  659. self.assertNextEquals(None)
  660. self.assertNak()
  661. def test_multi_ack_stateless(self):
  662. # transmission ends with a flush-pkt
  663. self._walker.lines[-1] = (None, None)
  664. self._walker.http_req = True
  665. self.assertNextEquals(TWO)
  666. self.assertNoAck()
  667. self.assertNextEquals(ONE)
  668. self.assertNoAck()
  669. self.assertNextEquals(THREE)
  670. self.assertNoAck()
  671. self.assertNextEquals(None)
  672. self.assertNak()
  673. @skipIfPY3
  674. class FileSystemBackendTests(TestCase):
  675. """Tests for FileSystemBackend."""
  676. def setUp(self):
  677. super(FileSystemBackendTests, self).setUp()
  678. self.path = tempfile.mkdtemp()
  679. self.repo = Repo.init(self.path)
  680. self.backend = FileSystemBackend()
  681. def test_nonexistant(self):
  682. self.assertRaises(NotGitRepository,
  683. self.backend.open_repository, "/does/not/exist/unless/foo")
  684. def test_absolute(self):
  685. repo = self.backend.open_repository(self.path)
  686. self.assertEqual(os.path.abspath(repo.path), os.path.abspath(self.repo.path))
  687. def test_child(self):
  688. self.assertRaises(NotGitRepository,
  689. self.backend.open_repository, os.path.join(self.path, "foo"))
  690. def test_bad_repo_path(self):
  691. backend = FileSystemBackend()
  692. self.assertRaises(NotGitRepository,
  693. lambda: backend.open_repository('/ups'))
  694. @skipIfPY3
  695. class DictBackendTests(TestCase):
  696. """Tests for DictBackend."""
  697. def test_nonexistant(self):
  698. repo = MemoryRepo.init_bare([], {})
  699. backend = DictBackend({'/': repo})
  700. self.assertRaises(NotGitRepository,
  701. backend.open_repository, "/does/not/exist/unless/foo")
  702. def test_bad_repo_path(self):
  703. repo = MemoryRepo.init_bare([], {})
  704. backend = DictBackend({'/': repo})
  705. self.assertRaises(NotGitRepository,
  706. lambda: backend.open_repository('/ups'))
  707. @skipIfPY3
  708. class ServeCommandTests(TestCase):
  709. """Tests for serve_command."""
  710. def setUp(self):
  711. super(ServeCommandTests, self).setUp()
  712. self.backend = DictBackend({})
  713. def serve_command(self, handler_cls, args, inf, outf):
  714. return serve_command(handler_cls, ["test"] + args, backend=self.backend,
  715. inf=inf, outf=outf)
  716. def test_receive_pack(self):
  717. commit = make_commit(id=ONE, parents=[], commit_time=111)
  718. self.backend.repos["/"] = MemoryRepo.init_bare(
  719. [commit], {"refs/heads/master": commit.id})
  720. outf = BytesIO()
  721. exitcode = self.serve_command(ReceivePackHandler, ["/"], BytesIO("0000"), outf)
  722. outlines = outf.getvalue().splitlines()
  723. self.assertEqual(2, len(outlines))
  724. self.assertEqual("1111111111111111111111111111111111111111 refs/heads/master",
  725. outlines[0][4:].split("\x00")[0])
  726. self.assertEqual("0000", outlines[-1])
  727. self.assertEqual(0, exitcode)
  728. @skipIfPY3
  729. class UpdateServerInfoTests(TestCase):
  730. """Tests for update_server_info."""
  731. def setUp(self):
  732. super(UpdateServerInfoTests, self).setUp()
  733. self.path = tempfile.mkdtemp()
  734. self.repo = Repo.init(self.path)
  735. def test_empty(self):
  736. update_server_info(self.repo)
  737. with open(os.path.join(self.path, ".git", "info", "refs"), 'rb') as f:
  738. self.assertEqual(b'', f.read())
  739. with open(os.path.join(self.path, ".git", "objects", "info", "packs"), 'rb') as f:
  740. self.assertEqual(b'', f.read())
  741. def test_simple(self):
  742. commit_id = self.repo.do_commit(
  743. message="foo",
  744. committer="Joe Example <joe@example.com>",
  745. ref="refs/heads/foo")
  746. update_server_info(self.repo)
  747. with open(os.path.join(self.path, ".git", "info", "refs"), 'rb') as f:
  748. self.assertEqual(f.read(), commit_id + b'\trefs/heads/foo\n')
  749. with open(os.path.join(self.path, ".git", "objects", "info", "packs"), 'rb') as f:
  750. self.assertEqual(f.read(), b'')