1 # test_server.py -- Tests for the git server
2 # Copyright (C) 2010 Google, Inc.
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; version 2
7 # or (at your option) any later version of the License.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 """Tests for the smart protocol server."""
22 from dulwich.errors import (
24 UnexpectedCommandError,
26 from dulwich.repo import (
29 from dulwich.server import (
34 MultiAckGraphWalkerImpl,
35 MultiAckDetailedGraphWalkerImpl,
38 SingleAckGraphWalkerImpl,
41 from dulwich.tests import TestCase
55 class TestProto(object):
59 self._received = {0: [], 1: [], 2: [], 3: []}
61 def set_output(self, output_lines):
62 self._output = ['%s\n' % line.rstrip() for line in output_lines]
64 def read_pkt_line(self):
66 return self._output.pop(0)
70 def write_sideband(self, band, data):
71 self._received[band].append(data)
73 def write_pkt_line(self, data):
76 self._received[0].append(data)
78 def get_received_line(self, band=0):
79 lines = self._received[band]
86 class TestGenericHandler(Handler):
89 Handler.__init__(self, Backend(), None)
92 def capabilities(cls):
93 return ('cap1', 'cap2', 'cap3')
96 def required_capabilities(cls):
100 class HandlerTestCase(TestCase):
103 super(HandlerTestCase, self).setUp()
104 self._handler = TestGenericHandler()
106 def assertSucceeds(self, func, *args, **kwargs):
108 func(*args, **kwargs)
109 except GitProtocolError, e:
112 def test_capability_line(self):
113 self.assertEquals('cap1 cap2 cap3', self._handler.capability_line())
115 def test_set_client_capabilities(self):
116 set_caps = self._handler.set_client_capabilities
117 self.assertSucceeds(set_caps, ['cap2'])
118 self.assertSucceeds(set_caps, ['cap1', 'cap2'])
121 self.assertSucceeds(set_caps, ['cap3', 'cap1', 'cap2'])
124 self.assertRaises(GitProtocolError, set_caps, ['capxxx', 'cap2'])
125 self.assertRaises(GitProtocolError, set_caps, ['cap1', 'cap3'])
127 # ignore innocuous but unknown capabilities
128 self.assertRaises(GitProtocolError, set_caps, ['cap2', 'ignoreme'])
129 self.assertFalse('ignoreme' in self._handler.capabilities())
130 self._handler.innocuous_capabilities = lambda: ('ignoreme',)
131 self.assertSucceeds(set_caps, ['cap2', 'ignoreme'])
133 def test_has_capability(self):
134 self.assertRaises(GitProtocolError, self._handler.has_capability, 'cap')
135 caps = self._handler.capabilities()
136 self._handler.set_client_capabilities(caps)
138 self.assertTrue(self._handler.has_capability(cap))
139 self.assertFalse(self._handler.has_capability('capxxx'))
142 class UploadPackHandlerTestCase(TestCase):
145 self._repo = MemoryRepo.init_bare([], {})
146 backend = DictBackend({'/': self._repo})
147 self._handler = UploadPackHandler(
148 backend, ['/', 'host=lolcathost'], TestProto())
150 def test_progress(self):
151 caps = self._handler.required_capabilities()
152 self._handler.set_client_capabilities(caps)
153 self._handler.progress('first message')
154 self._handler.progress('second message')
155 self.assertEqual('first message',
156 self._handler.proto.get_received_line(2))
157 self.assertEqual('second message',
158 self._handler.proto.get_received_line(2))
159 self.assertEqual(None, self._handler.proto.get_received_line(2))
161 def test_no_progress(self):
162 caps = list(self._handler.required_capabilities()) + ['no-progress']
163 self._handler.set_client_capabilities(caps)
164 self._handler.progress('first message')
165 self._handler.progress('second message')
166 self.assertEqual(None, self._handler.proto.get_received_line(2))
168 def test_get_tagged(self):
170 'refs/tags/tag1': ONE,
171 'refs/tags/tag2': TWO,
172 'refs/heads/master': FOUR, # not a tag, no peeled value
174 # repo needs to peel this object
175 self._repo.object_store.add_object(make_commit(id=FOUR))
176 self._repo.refs._update(refs)
178 'refs/tags/tag1': '1234' * 10,
179 'refs/tags/tag2': '5678' * 10,
181 self._repo.refs._update_peeled(peeled)
183 caps = list(self._handler.required_capabilities()) + ['include-tag']
184 self._handler.set_client_capabilities(caps)
185 self.assertEquals({'1234' * 10: ONE, '5678' * 10: TWO},
186 self._handler.get_tagged(refs, repo=self._repo))
188 # non-include-tag case
189 caps = self._handler.required_capabilities()
190 self._handler.set_client_capabilities(caps)
191 self.assertEquals({}, self._handler.get_tagged(refs, repo=self._repo))
194 class TestUploadPackHandler(UploadPackHandler):
196 def required_capabilities(self):
200 class ProtocolGraphWalkerTestCase(TestCase):
203 super(ProtocolGraphWalkerTestCase, self).setUp()
204 # Create the following commit tree:
209 make_commit(id=ONE, parents=[], commit_time=111),
210 make_commit(id=TWO, parents=[ONE], commit_time=222),
211 make_commit(id=THREE, parents=[ONE], commit_time=333),
212 make_commit(id=FOUR, parents=[TWO], commit_time=444),
213 make_commit(id=FIVE, parents=[THREE], commit_time=555),
215 self._repo = MemoryRepo.init_bare(commits, {})
216 backend = DictBackend({'/': self._repo})
217 self._walker = ProtocolGraphWalker(
218 TestUploadPackHandler(backend, ['/', 'host=lolcats'], TestProto()),
219 self._repo.object_store, self._repo.get_peeled)
221 def test_is_satisfied_no_haves(self):
222 self.assertFalse(self._walker._is_satisfied([], ONE, 0))
223 self.assertFalse(self._walker._is_satisfied([], TWO, 0))
224 self.assertFalse(self._walker._is_satisfied([], THREE, 0))
226 def test_is_satisfied_have_root(self):
227 self.assertTrue(self._walker._is_satisfied([ONE], ONE, 0))
228 self.assertTrue(self._walker._is_satisfied([ONE], TWO, 0))
229 self.assertTrue(self._walker._is_satisfied([ONE], THREE, 0))
231 def test_is_satisfied_have_branch(self):
232 self.assertTrue(self._walker._is_satisfied([TWO], TWO, 0))
234 self.assertFalse(self._walker._is_satisfied([TWO], THREE, 0))
236 def test_all_wants_satisfied(self):
237 self._walker.set_wants([FOUR, FIVE])
238 # trivial case: wants == haves
239 self.assertTrue(self._walker.all_wants_satisfied([FOUR, FIVE]))
240 # cases that require walking the commit tree
241 self.assertTrue(self._walker.all_wants_satisfied([ONE]))
242 self.assertFalse(self._walker.all_wants_satisfied([TWO]))
243 self.assertFalse(self._walker.all_wants_satisfied([THREE]))
244 self.assertTrue(self._walker.all_wants_satisfied([TWO, THREE]))
246 def test_split_proto_line(self):
247 allowed = ('want', 'done', None)
248 self.assertEquals(('want', ONE),
249 _split_proto_line('want %s\n' % ONE, allowed))
250 self.assertEquals(('want', TWO),
251 _split_proto_line('want %s\n' % TWO, allowed))
252 self.assertRaises(GitProtocolError, _split_proto_line,
253 'want xxxx\n', allowed)
254 self.assertRaises(UnexpectedCommandError, _split_proto_line,
255 'have %s\n' % THREE, allowed)
256 self.assertRaises(GitProtocolError, _split_proto_line,
257 'foo %s\n' % FOUR, allowed)
258 self.assertRaises(GitProtocolError, _split_proto_line, 'bar', allowed)
259 self.assertEquals(('done', None), _split_proto_line('done\n', allowed))
260 self.assertEquals((None, None), _split_proto_line('', allowed))
262 def test_determine_wants(self):
263 self.assertRaises(GitProtocolError, self._walker.determine_wants, {})
265 self._walker.proto.set_output([
266 'want %s multi_ack' % ONE,
270 'refs/heads/ref1': ONE,
271 'refs/heads/ref2': TWO,
272 'refs/heads/ref3': THREE,
274 self._repo.refs._update(heads)
275 self.assertEquals([ONE, TWO], self._walker.determine_wants(heads))
277 self._walker.proto.set_output(['want %s multi_ack' % FOUR])
278 self.assertRaises(GitProtocolError, self._walker.determine_wants, heads)
280 self._walker.proto.set_output([])
281 self.assertEquals([], self._walker.determine_wants(heads))
283 self._walker.proto.set_output(['want %s multi_ack' % ONE, 'foo'])
284 self.assertRaises(GitProtocolError, self._walker.determine_wants, heads)
286 self._walker.proto.set_output(['want %s multi_ack' % FOUR])
287 self.assertRaises(GitProtocolError, self._walker.determine_wants, heads)
289 def test_determine_wants_advertisement(self):
290 self._walker.proto.set_output([])
291 # advertise branch tips plus tag
293 'refs/heads/ref4': FOUR,
294 'refs/heads/ref5': FIVE,
295 'refs/heads/tag6': SIX,
297 self._repo.refs._update(heads)
298 self._repo.refs._update_peeled(heads)
299 self._repo.refs._update_peeled({'refs/heads/tag6': FIVE})
300 self._walker.determine_wants(heads)
303 line = self._walker.proto.get_received_line()
306 # strip capabilities list if present
308 line = line[:line.index('\x00')]
309 lines.append(line.rstrip())
312 '%s refs/heads/ref4' % FOUR,
313 '%s refs/heads/ref5' % FIVE,
314 '%s refs/heads/tag6^{}' % FIVE,
315 '%s refs/heads/tag6' % SIX,
318 # ensure peeled tag was advertised immediately following tag
319 for i, line in enumerate(lines):
320 if line.endswith(' refs/heads/tag6'):
321 self.assertEquals('%s refs/heads/tag6^{}' % FIVE, lines[i+1])
323 # TODO: test commit time cutoff
326 class TestProtocolGraphWalker(object):
332 self.stateless_rpc = False
333 self.advertise_refs = False
335 def read_proto_line(self, allowed):
336 command, sha = self.lines.pop(0)
337 if allowed is not None:
338 assert command in allowed
341 def send_ack(self, sha, ack_type=''):
342 self.acks.append((sha, ack_type))
345 self.acks.append((None, 'nak'))
347 def all_wants_satisfied(self, haves):
353 return self.acks.pop(0)
356 class AckGraphWalkerImplTestCase(TestCase):
357 """Base setup and asserts for AckGraphWalker tests."""
360 super(AckGraphWalkerImplTestCase, self).setUp()
361 self._walker = TestProtocolGraphWalker()
362 self._walker.lines = [
368 self._impl = self.impl_cls(self._walker)
370 def assertNoAck(self):
371 self.assertEquals(None, self._walker.pop_ack())
373 def assertAcks(self, acks):
374 for sha, ack_type in acks:
375 self.assertEquals((sha, ack_type), self._walker.pop_ack())
378 def assertAck(self, sha, ack_type=''):
379 self.assertAcks([(sha, ack_type)])
382 self.assertAck(None, 'nak')
384 def assertNextEquals(self, sha):
385 self.assertEquals(sha, self._impl.next())
388 class SingleAckGraphWalkerImplTestCase(AckGraphWalkerImplTestCase):
390 impl_cls = SingleAckGraphWalkerImpl
392 def test_single_ack(self):
393 self.assertNextEquals(TWO)
396 self.assertNextEquals(ONE)
397 self._walker.done = True
401 self.assertNextEquals(THREE)
402 self._impl.ack(THREE)
405 self.assertNextEquals(None)
408 def test_single_ack_flush(self):
409 # same as ack test but ends with a flush-pkt instead of done
410 self._walker.lines[-1] = (None, None)
412 self.assertNextEquals(TWO)
415 self.assertNextEquals(ONE)
416 self._walker.done = True
420 self.assertNextEquals(THREE)
423 self.assertNextEquals(None)
426 def test_single_ack_nak(self):
427 self.assertNextEquals(TWO)
430 self.assertNextEquals(ONE)
433 self.assertNextEquals(THREE)
436 self.assertNextEquals(None)
439 def test_single_ack_nak_flush(self):
440 # same as nak test but ends with a flush-pkt instead of done
441 self._walker.lines[-1] = (None, None)
443 self.assertNextEquals(TWO)
446 self.assertNextEquals(ONE)
449 self.assertNextEquals(THREE)
452 self.assertNextEquals(None)
456 class MultiAckGraphWalkerImplTestCase(AckGraphWalkerImplTestCase):
458 impl_cls = MultiAckGraphWalkerImpl
460 def test_multi_ack(self):
461 self.assertNextEquals(TWO)
464 self.assertNextEquals(ONE)
465 self._walker.done = True
467 self.assertAck(ONE, 'continue')
469 self.assertNextEquals(THREE)
470 self._impl.ack(THREE)
471 self.assertAck(THREE, 'continue')
473 self.assertNextEquals(None)
474 self.assertAck(THREE)
476 def test_multi_ack_partial(self):
477 self.assertNextEquals(TWO)
480 self.assertNextEquals(ONE)
482 self.assertAck(ONE, 'continue')
484 self.assertNextEquals(THREE)
487 self.assertNextEquals(None)
488 # done, re-send ack of last common
491 def test_multi_ack_flush(self):
492 self._walker.lines = [
499 self.assertNextEquals(TWO)
502 self.assertNextEquals(ONE)
503 self.assertNak() # nak the flush-pkt
505 self._walker.done = True
507 self.assertAck(ONE, 'continue')
509 self.assertNextEquals(THREE)
510 self._impl.ack(THREE)
511 self.assertAck(THREE, 'continue')
513 self.assertNextEquals(None)
514 self.assertAck(THREE)
516 def test_multi_ack_nak(self):
517 self.assertNextEquals(TWO)
520 self.assertNextEquals(ONE)
523 self.assertNextEquals(THREE)
526 self.assertNextEquals(None)
530 class MultiAckDetailedGraphWalkerImplTestCase(AckGraphWalkerImplTestCase):
532 impl_cls = MultiAckDetailedGraphWalkerImpl
534 def test_multi_ack(self):
535 self.assertNextEquals(TWO)
538 self.assertNextEquals(ONE)
539 self._walker.done = True
541 self.assertAcks([(ONE, 'common'), (ONE, 'ready')])
543 self.assertNextEquals(THREE)
544 self._impl.ack(THREE)
545 self.assertAck(THREE, 'ready')
547 self.assertNextEquals(None)
548 self.assertAck(THREE)
550 def test_multi_ack_partial(self):
551 self.assertNextEquals(TWO)
554 self.assertNextEquals(ONE)
556 self.assertAck(ONE, 'common')
558 self.assertNextEquals(THREE)
561 self.assertNextEquals(None)
562 # done, re-send ack of last common
565 def test_multi_ack_flush(self):
566 # same as ack test but contains a flush-pkt in the middle
567 self._walker.lines = [
574 self.assertNextEquals(TWO)
577 self.assertNextEquals(ONE)
578 self.assertNak() # nak the flush-pkt
580 self._walker.done = True
582 self.assertAcks([(ONE, 'common'), (ONE, 'ready')])
584 self.assertNextEquals(THREE)
585 self._impl.ack(THREE)
586 self.assertAck(THREE, 'ready')
588 self.assertNextEquals(None)
589 self.assertAck(THREE)
591 def test_multi_ack_nak(self):
592 self.assertNextEquals(TWO)
595 self.assertNextEquals(ONE)
598 self.assertNextEquals(THREE)
601 self.assertNextEquals(None)
604 def test_multi_ack_nak_flush(self):
605 # same as nak test but contains a flush-pkt in the middle
606 self._walker.lines = [
613 self.assertNextEquals(TWO)
616 self.assertNextEquals(ONE)
619 self.assertNextEquals(THREE)
622 self.assertNextEquals(None)
625 def test_multi_ack_stateless(self):
626 # transmission ends with a flush-pkt
627 self._walker.lines[-1] = (None, None)
628 self._walker.stateless_rpc = True
630 self.assertNextEquals(TWO)
633 self.assertNextEquals(ONE)
636 self.assertNextEquals(THREE)
639 self.assertNextEquals(None)