test_server.py 33 KB

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