test_server.py 35 KB

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