test_server.py 35 KB

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