test_server.py 37 KB

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