e64954a2a49d8009d5ce7012707faf4e237637c5
[jelmer/dulwich-libgit2.git] / dulwich / tests / test_server.py
1 # test_server.py -- Tests for the git server
2 # Copyright (C) 2010 Google, Inc.
3 #
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; version 2
7 # or (at your option) any later version of the License.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
17 # MA  02110-1301, USA.
18
19 """Tests for the smart protocol server."""
20
21
22 from dulwich.errors import (
23     GitProtocolError,
24     UnexpectedCommandError,
25     )
26 from dulwich.repo import (
27     MemoryRepo,
28     )
29 from dulwich.server import (
30     Backend,
31     DictBackend,
32     BackendRepo,
33     Handler,
34     MultiAckGraphWalkerImpl,
35     MultiAckDetailedGraphWalkerImpl,
36     _split_proto_line,
37     ProtocolGraphWalker,
38     SingleAckGraphWalkerImpl,
39     UploadPackHandler,
40     )
41 from dulwich.tests import TestCase
42 from utils import (
43     make_commit,
44     )
45
46
47 ONE = '1' * 40
48 TWO = '2' * 40
49 THREE = '3' * 40
50 FOUR = '4' * 40
51 FIVE = '5' * 40
52 SIX = '6' * 40
53
54
55 class TestProto(object):
56
57     def __init__(self):
58         self._output = []
59         self._received = {0: [], 1: [], 2: [], 3: []}
60
61     def set_output(self, output_lines):
62         self._output = ['%s\n' % line.rstrip() for line in output_lines]
63
64     def read_pkt_line(self):
65         if self._output:
66             return self._output.pop(0)
67         else:
68             return None
69
70     def write_sideband(self, band, data):
71         self._received[band].append(data)
72
73     def write_pkt_line(self, data):
74         if data is None:
75             data = 'None'
76         self._received[0].append(data)
77
78     def get_received_line(self, band=0):
79         lines = self._received[band]
80         if lines:
81             return lines.pop(0)
82         else:
83             return None
84
85
86 class TestGenericHandler(Handler):
87
88     def __init__(self):
89         Handler.__init__(self, Backend(), None)
90
91     @classmethod
92     def capabilities(cls):
93         return ('cap1', 'cap2', 'cap3')
94
95     @classmethod
96     def required_capabilities(cls):
97         return ('cap2',)
98
99
100 class HandlerTestCase(TestCase):
101
102     def setUp(self):
103         super(HandlerTestCase, self).setUp()
104         self._handler = TestGenericHandler()
105
106     def assertSucceeds(self, func, *args, **kwargs):
107         try:
108             func(*args, **kwargs)
109         except GitProtocolError, e:
110             self.fail(e)
111
112     def test_capability_line(self):
113         self.assertEquals('cap1 cap2 cap3', self._handler.capability_line())
114
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'])
119
120         # different order
121         self.assertSucceeds(set_caps, ['cap3', 'cap1', 'cap2'])
122
123         # error cases
124         self.assertRaises(GitProtocolError, set_caps, ['capxxx', 'cap2'])
125         self.assertRaises(GitProtocolError, set_caps, ['cap1', 'cap3'])
126
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'])
132
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)
137         for cap in caps:
138             self.assertTrue(self._handler.has_capability(cap))
139         self.assertFalse(self._handler.has_capability('capxxx'))
140
141
142 class UploadPackHandlerTestCase(TestCase):
143
144     def setUp(self):
145         self._repo = MemoryRepo.init_bare([], {})
146         backend = DictBackend({'/': self._repo})
147         self._handler = UploadPackHandler(
148           backend, ['/', 'host=lolcathost'], TestProto())
149
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))
160
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))
167
168     def test_get_tagged(self):
169         refs = {
170             'refs/tags/tag1': ONE,
171             'refs/tags/tag2': TWO,
172             'refs/heads/master': FOUR,  # not a tag, no peeled value
173             }
174         # repo needs to peel this object
175         self._repo.object_store.add_object(make_commit(id=FOUR))
176         self._repo.refs._update(refs)
177         peeled = {
178             'refs/tags/tag1': '1234' * 10,
179             'refs/tags/tag2': '5678' * 10,
180             }
181         self._repo.refs._update_peeled(peeled)
182
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))
187
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))
192
193
194 class TestUploadPackHandler(UploadPackHandler):
195     @classmethod
196     def required_capabilities(self):
197         return ()
198
199
200 class ProtocolGraphWalkerTestCase(TestCase):
201
202     def setUp(self):
203         super(ProtocolGraphWalkerTestCase, self).setUp()
204         # Create the following commit tree:
205         #   3---5
206         #  /
207         # 1---2---4
208         commits = [
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),
214           ]
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)
220
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))
225
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))
230
231     def test_is_satisfied_have_branch(self):
232         self.assertTrue(self._walker._is_satisfied([TWO], TWO, 0))
233         # wrong branch
234         self.assertFalse(self._walker._is_satisfied([TWO], THREE, 0))
235
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]))
245
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))
261
262     def test_determine_wants(self):
263         self.assertRaises(GitProtocolError, self._walker.determine_wants, {})
264
265         self._walker.proto.set_output([
266           'want %s multi_ack' % ONE,
267           'want %s' % TWO,
268           ])
269         heads = {
270           'refs/heads/ref1': ONE,
271           'refs/heads/ref2': TWO,
272           'refs/heads/ref3': THREE,
273           }
274         self._repo.refs._update(heads)
275         self.assertEquals([ONE, TWO], self._walker.determine_wants(heads))
276
277         self._walker.proto.set_output(['want %s multi_ack' % FOUR])
278         self.assertRaises(GitProtocolError, self._walker.determine_wants, heads)
279
280         self._walker.proto.set_output([])
281         self.assertEquals([], self._walker.determine_wants(heads))
282
283         self._walker.proto.set_output(['want %s multi_ack' % ONE, 'foo'])
284         self.assertRaises(GitProtocolError, self._walker.determine_wants, heads)
285
286         self._walker.proto.set_output(['want %s multi_ack' % FOUR])
287         self.assertRaises(GitProtocolError, self._walker.determine_wants, heads)
288
289     def test_determine_wants_advertisement(self):
290         self._walker.proto.set_output([])
291         # advertise branch tips plus tag
292         heads = {
293           'refs/heads/ref4': FOUR,
294           'refs/heads/ref5': FIVE,
295           'refs/heads/tag6': SIX,
296           }
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)
301         lines = []
302         while True:
303             line = self._walker.proto.get_received_line()
304             if line == 'None':
305                 break
306             # strip capabilities list if present
307             if '\x00' in line:
308                 line = line[:line.index('\x00')]
309             lines.append(line.rstrip())
310
311         self.assertEquals([
312           '%s refs/heads/ref4' % FOUR,
313           '%s refs/heads/ref5' % FIVE,
314           '%s refs/heads/tag6^{}' % FIVE,
315           '%s refs/heads/tag6' % SIX,
316           ], sorted(lines))
317
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])
322
323     # TODO: test commit time cutoff
324
325
326 class TestProtocolGraphWalker(object):
327
328     def __init__(self):
329         self.acks = []
330         self.lines = []
331         self.done = False
332         self.stateless_rpc = False
333         self.advertise_refs = False
334
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
339         return command, sha
340
341     def send_ack(self, sha, ack_type=''):
342         self.acks.append((sha, ack_type))
343
344     def send_nak(self):
345         self.acks.append((None, 'nak'))
346
347     def all_wants_satisfied(self, haves):
348         return self.done
349
350     def pop_ack(self):
351         if not self.acks:
352             return None
353         return self.acks.pop(0)
354
355
356 class AckGraphWalkerImplTestCase(TestCase):
357     """Base setup and asserts for AckGraphWalker tests."""
358
359     def setUp(self):
360         super(AckGraphWalkerImplTestCase, self).setUp()
361         self._walker = TestProtocolGraphWalker()
362         self._walker.lines = [
363           ('have', TWO),
364           ('have', ONE),
365           ('have', THREE),
366           ('done', None),
367           ]
368         self._impl = self.impl_cls(self._walker)
369
370     def assertNoAck(self):
371         self.assertEquals(None, self._walker.pop_ack())
372
373     def assertAcks(self, acks):
374         for sha, ack_type in acks:
375             self.assertEquals((sha, ack_type), self._walker.pop_ack())
376         self.assertNoAck()
377
378     def assertAck(self, sha, ack_type=''):
379         self.assertAcks([(sha, ack_type)])
380
381     def assertNak(self):
382         self.assertAck(None, 'nak')
383
384     def assertNextEquals(self, sha):
385         self.assertEquals(sha, self._impl.next())
386
387
388 class SingleAckGraphWalkerImplTestCase(AckGraphWalkerImplTestCase):
389
390     impl_cls = SingleAckGraphWalkerImpl
391
392     def test_single_ack(self):
393         self.assertNextEquals(TWO)
394         self.assertNoAck()
395
396         self.assertNextEquals(ONE)
397         self._walker.done = True
398         self._impl.ack(ONE)
399         self.assertAck(ONE)
400
401         self.assertNextEquals(THREE)
402         self._impl.ack(THREE)
403         self.assertNoAck()
404
405         self.assertNextEquals(None)
406         self.assertNoAck()
407
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)
411
412         self.assertNextEquals(TWO)
413         self.assertNoAck()
414
415         self.assertNextEquals(ONE)
416         self._walker.done = True
417         self._impl.ack(ONE)
418         self.assertAck(ONE)
419
420         self.assertNextEquals(THREE)
421         self.assertNoAck()
422
423         self.assertNextEquals(None)
424         self.assertNoAck()
425
426     def test_single_ack_nak(self):
427         self.assertNextEquals(TWO)
428         self.assertNoAck()
429
430         self.assertNextEquals(ONE)
431         self.assertNoAck()
432
433         self.assertNextEquals(THREE)
434         self.assertNoAck()
435
436         self.assertNextEquals(None)
437         self.assertNak()
438
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)
442
443         self.assertNextEquals(TWO)
444         self.assertNoAck()
445
446         self.assertNextEquals(ONE)
447         self.assertNoAck()
448
449         self.assertNextEquals(THREE)
450         self.assertNoAck()
451
452         self.assertNextEquals(None)
453         self.assertNak()
454
455
456 class MultiAckGraphWalkerImplTestCase(AckGraphWalkerImplTestCase):
457
458     impl_cls = MultiAckGraphWalkerImpl
459
460     def test_multi_ack(self):
461         self.assertNextEquals(TWO)
462         self.assertNoAck()
463
464         self.assertNextEquals(ONE)
465         self._walker.done = True
466         self._impl.ack(ONE)
467         self.assertAck(ONE, 'continue')
468
469         self.assertNextEquals(THREE)
470         self._impl.ack(THREE)
471         self.assertAck(THREE, 'continue')
472
473         self.assertNextEquals(None)
474         self.assertAck(THREE)
475
476     def test_multi_ack_partial(self):
477         self.assertNextEquals(TWO)
478         self.assertNoAck()
479
480         self.assertNextEquals(ONE)
481         self._impl.ack(ONE)
482         self.assertAck(ONE, 'continue')
483
484         self.assertNextEquals(THREE)
485         self.assertNoAck()
486
487         self.assertNextEquals(None)
488         # done, re-send ack of last common
489         self.assertAck(ONE)
490
491     def test_multi_ack_flush(self):
492         self._walker.lines = [
493           ('have', TWO),
494           (None, None),
495           ('have', ONE),
496           ('have', THREE),
497           ('done', None),
498           ]
499         self.assertNextEquals(TWO)
500         self.assertNoAck()
501
502         self.assertNextEquals(ONE)
503         self.assertNak()  # nak the flush-pkt
504
505         self._walker.done = True
506         self._impl.ack(ONE)
507         self.assertAck(ONE, 'continue')
508
509         self.assertNextEquals(THREE)
510         self._impl.ack(THREE)
511         self.assertAck(THREE, 'continue')
512
513         self.assertNextEquals(None)
514         self.assertAck(THREE)
515
516     def test_multi_ack_nak(self):
517         self.assertNextEquals(TWO)
518         self.assertNoAck()
519
520         self.assertNextEquals(ONE)
521         self.assertNoAck()
522
523         self.assertNextEquals(THREE)
524         self.assertNoAck()
525
526         self.assertNextEquals(None)
527         self.assertNak()
528
529
530 class MultiAckDetailedGraphWalkerImplTestCase(AckGraphWalkerImplTestCase):
531
532     impl_cls = MultiAckDetailedGraphWalkerImpl
533
534     def test_multi_ack(self):
535         self.assertNextEquals(TWO)
536         self.assertNoAck()
537
538         self.assertNextEquals(ONE)
539         self._walker.done = True
540         self._impl.ack(ONE)
541         self.assertAcks([(ONE, 'common'), (ONE, 'ready')])
542
543         self.assertNextEquals(THREE)
544         self._impl.ack(THREE)
545         self.assertAck(THREE, 'ready')
546
547         self.assertNextEquals(None)
548         self.assertAck(THREE)
549
550     def test_multi_ack_partial(self):
551         self.assertNextEquals(TWO)
552         self.assertNoAck()
553
554         self.assertNextEquals(ONE)
555         self._impl.ack(ONE)
556         self.assertAck(ONE, 'common')
557
558         self.assertNextEquals(THREE)
559         self.assertNoAck()
560
561         self.assertNextEquals(None)
562         # done, re-send ack of last common
563         self.assertAck(ONE)
564
565     def test_multi_ack_flush(self):
566         # same as ack test but contains a flush-pkt in the middle
567         self._walker.lines = [
568           ('have', TWO),
569           (None, None),
570           ('have', ONE),
571           ('have', THREE),
572           ('done', None),
573           ]
574         self.assertNextEquals(TWO)
575         self.assertNoAck()
576
577         self.assertNextEquals(ONE)
578         self.assertNak()  # nak the flush-pkt
579
580         self._walker.done = True
581         self._impl.ack(ONE)
582         self.assertAcks([(ONE, 'common'), (ONE, 'ready')])
583
584         self.assertNextEquals(THREE)
585         self._impl.ack(THREE)
586         self.assertAck(THREE, 'ready')
587
588         self.assertNextEquals(None)
589         self.assertAck(THREE)
590
591     def test_multi_ack_nak(self):
592         self.assertNextEquals(TWO)
593         self.assertNoAck()
594
595         self.assertNextEquals(ONE)
596         self.assertNoAck()
597
598         self.assertNextEquals(THREE)
599         self.assertNoAck()
600
601         self.assertNextEquals(None)
602         self.assertNak()
603
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 = [
607           ('have', TWO),
608           (None, None),
609           ('have', ONE),
610           ('have', THREE),
611           ('done', None),
612           ]
613         self.assertNextEquals(TWO)
614         self.assertNoAck()
615
616         self.assertNextEquals(ONE)
617         self.assertNak()
618
619         self.assertNextEquals(THREE)
620         self.assertNoAck()
621
622         self.assertNextEquals(None)
623         self.assertNak()
624
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
629
630         self.assertNextEquals(TWO)
631         self.assertNoAck()
632
633         self.assertNextEquals(ONE)
634         self.assertNoAck()
635
636         self.assertNextEquals(THREE)
637         self.assertNoAck()
638
639         self.assertNextEquals(None)
640         self.assertNak()