e66ab7446045feb3bb237c076edf1aed55499566
[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     )
25 from dulwich.server import (
26     Backend,
27     DictBackend,
28     BackendRepo,
29     Handler,
30     MultiAckGraphWalkerImpl,
31     MultiAckDetailedGraphWalkerImpl,
32     _split_proto_line,
33     ProtocolGraphWalker,
34     SingleAckGraphWalkerImpl,
35     UploadPackHandler,
36     )
37 from dulwich.tests import TestCase
38
39
40
41 ONE = '1' * 40
42 TWO = '2' * 40
43 THREE = '3' * 40
44 FOUR = '4' * 40
45 FIVE = '5' * 40
46 SIX = '6' * 40
47
48
49 class TestProto(object):
50
51     def __init__(self):
52         self._output = []
53         self._received = {0: [], 1: [], 2: [], 3: []}
54
55     def set_output(self, output_lines):
56         self._output = ['%s\n' % line.rstrip() for line in output_lines]
57
58     def read_pkt_line(self):
59         if self._output:
60             return self._output.pop(0)
61         else:
62             return None
63
64     def write_sideband(self, band, data):
65         self._received[band].append(data)
66
67     def write_pkt_line(self, data):
68         if data is None:
69             data = 'None'
70         self._received[0].append(data)
71
72     def get_received_line(self, band=0):
73         lines = self._received[band]
74         if lines:
75             return lines.pop(0)
76         else:
77             return None
78
79
80 class TestGenericHandler(Handler):
81
82     def __init__(self):
83         Handler.__init__(self, Backend(), None)
84
85     @classmethod
86     def capabilities(cls):
87         return ('cap1', 'cap2', 'cap3')
88
89     @classmethod
90     def required_capabilities(cls):
91         return ('cap2',)
92
93
94 class HandlerTestCase(TestCase):
95
96     def setUp(self):
97         super(HandlerTestCase, self).setUp()
98         self._handler = TestGenericHandler()
99
100     def assertSucceeds(self, func, *args, **kwargs):
101         try:
102             func(*args, **kwargs)
103         except GitProtocolError, e:
104             self.fail(e)
105
106     def test_capability_line(self):
107         self.assertEquals('cap1 cap2 cap3', self._handler.capability_line())
108
109     def test_set_client_capabilities(self):
110         set_caps = self._handler.set_client_capabilities
111         self.assertSucceeds(set_caps, ['cap2'])
112         self.assertSucceeds(set_caps, ['cap1', 'cap2'])
113
114         # different order
115         self.assertSucceeds(set_caps, ['cap3', 'cap1', 'cap2'])
116
117         # error cases
118         self.assertRaises(GitProtocolError, set_caps, ['capxxx', 'cap2'])
119         self.assertRaises(GitProtocolError, set_caps, ['cap1', 'cap3'])
120
121         # ignore innocuous but unknown capabilities
122         self.assertRaises(GitProtocolError, set_caps, ['cap2', 'ignoreme'])
123         self.assertFalse('ignoreme' in self._handler.capabilities())
124         self._handler.innocuous_capabilities = lambda: ('ignoreme',)
125         self.assertSucceeds(set_caps, ['cap2', 'ignoreme'])
126
127     def test_has_capability(self):
128         self.assertRaises(GitProtocolError, self._handler.has_capability, 'cap')
129         caps = self._handler.capabilities()
130         self._handler.set_client_capabilities(caps)
131         for cap in caps:
132             self.assertTrue(self._handler.has_capability(cap))
133         self.assertFalse(self._handler.has_capability('capxxx'))
134
135
136 class UploadPackHandlerTestCase(TestCase):
137
138     def setUp(self):
139         super(UploadPackHandlerTestCase, self).setUp()
140         self._backend = DictBackend({"/": BackendRepo()})
141         self._handler = UploadPackHandler(self._backend,
142                 ["/", "host=lolcathost"], None, None)
143         self._handler.proto = TestProto()
144
145     def test_progress(self):
146         caps = self._handler.required_capabilities()
147         self._handler.set_client_capabilities(caps)
148         self._handler.progress('first message')
149         self._handler.progress('second message')
150         self.assertEqual('first message',
151                          self._handler.proto.get_received_line(2))
152         self.assertEqual('second message',
153                          self._handler.proto.get_received_line(2))
154         self.assertEqual(None, self._handler.proto.get_received_line(2))
155
156     def test_no_progress(self):
157         caps = list(self._handler.required_capabilities()) + ['no-progress']
158         self._handler.set_client_capabilities(caps)
159         self._handler.progress('first message')
160         self._handler.progress('second message')
161         self.assertEqual(None, self._handler.proto.get_received_line(2))
162
163     def test_get_tagged(self):
164         refs = {
165             'refs/tags/tag1': ONE,
166             'refs/tags/tag2': TWO,
167             'refs/heads/master': FOUR,  # not a tag, no peeled value
168             }
169         peeled = {
170             'refs/tags/tag1': '1234',
171             'refs/tags/tag2': '5678',
172             }
173
174         class TestRepo(object):
175             def get_peeled(self, ref):
176                 return peeled.get(ref, refs[ref])
177
178         caps = list(self._handler.required_capabilities()) + ['include-tag']
179         self._handler.set_client_capabilities(caps)
180         self.assertEquals({'1234': ONE, '5678': TWO},
181                           self._handler.get_tagged(refs, repo=TestRepo()))
182
183         # non-include-tag case
184         caps = self._handler.required_capabilities()
185         self._handler.set_client_capabilities(caps)
186         self.assertEquals({}, self._handler.get_tagged(refs, repo=TestRepo()))
187
188
189 class TestCommit(object):
190
191     def __init__(self, sha, parents, commit_time):
192         self.id = sha
193         self.parents = parents
194         self.commit_time = commit_time
195         self.type_name = "commit"
196
197     def __repr__(self):
198         return '%s(%s)' % (self.__class__.__name__, self._sha)
199
200
201 class TestRepo(object):
202     def __init__(self):
203         self.peeled = {}
204
205     def get_peeled(self, name):
206         return self.peeled[name]
207
208
209 class TestBackend(object):
210
211     def __init__(self, repo, objects):
212         self.repo = repo
213         self.object_store = objects
214
215
216 class TestUploadPackHandler(Handler):
217
218     def __init__(self, objects, proto):
219         self.backend = TestBackend(TestRepo(), objects)
220         self.proto = proto
221         self.stateless_rpc = False
222         self.advertise_refs = False
223
224     @classmethod
225     def capabilities(cls):
226         return ('multi_ack',)
227
228
229 class ProtocolGraphWalkerTestCase(TestCase):
230
231     def setUp(self):
232         super(ProtocolGraphWalkerTestCase, self).setUp()
233         # Create the following commit tree:
234         #   3---5
235         #  /
236         # 1---2---4
237         self._objects = {
238           ONE: TestCommit(ONE, [], 111),
239           TWO: TestCommit(TWO, [ONE], 222),
240           THREE: TestCommit(THREE, [ONE], 333),
241           FOUR: TestCommit(FOUR, [TWO], 444),
242           FIVE: TestCommit(FIVE, [THREE], 555),
243           }
244
245         self._walker = ProtocolGraphWalker(
246             TestUploadPackHandler(self._objects, TestProto()),
247             self._objects, None)
248
249     def test_is_satisfied_no_haves(self):
250         self.assertFalse(self._walker._is_satisfied([], ONE, 0))
251         self.assertFalse(self._walker._is_satisfied([], TWO, 0))
252         self.assertFalse(self._walker._is_satisfied([], THREE, 0))
253
254     def test_is_satisfied_have_root(self):
255         self.assertTrue(self._walker._is_satisfied([ONE], ONE, 0))
256         self.assertTrue(self._walker._is_satisfied([ONE], TWO, 0))
257         self.assertTrue(self._walker._is_satisfied([ONE], THREE, 0))
258
259     def test_is_satisfied_have_branch(self):
260         self.assertTrue(self._walker._is_satisfied([TWO], TWO, 0))
261         # wrong branch
262         self.assertFalse(self._walker._is_satisfied([TWO], THREE, 0))
263
264     def test_all_wants_satisfied(self):
265         self._walker.set_wants([FOUR, FIVE])
266         # trivial case: wants == haves
267         self.assertTrue(self._walker.all_wants_satisfied([FOUR, FIVE]))
268         # cases that require walking the commit tree
269         self.assertTrue(self._walker.all_wants_satisfied([ONE]))
270         self.assertFalse(self._walker.all_wants_satisfied([TWO]))
271         self.assertFalse(self._walker.all_wants_satisfied([THREE]))
272         self.assertTrue(self._walker.all_wants_satisfied([TWO, THREE]))
273
274     def test_split_proto_line(self):
275         self.assertEquals(('want', ONE), _split_proto_line('want %s\n' % ONE))
276         self.assertEquals(('want', TWO), _split_proto_line('want %s\n' % TWO))
277         self.assertEquals(('have', THREE),
278                           _split_proto_line('have %s\n' % THREE))
279         self.assertRaises(GitProtocolError,
280                           _split_proto_line, 'foo %s\n' % FOUR)
281         self.assertRaises(GitProtocolError, _split_proto_line, 'bar')
282         self.assertEquals(('done', None), _split_proto_line('done\n'))
283         self.assertEquals((None, None), _split_proto_line(''))
284
285     def test_determine_wants(self):
286         self.assertRaises(GitProtocolError, self._walker.determine_wants, {})
287
288         self._walker.proto.set_output([
289           'want %s multi_ack' % ONE,
290           'want %s' % TWO,
291           ])
292         heads = {'ref1': ONE, 'ref2': TWO, 'ref3': THREE}
293         self._walker.get_peeled = heads.get
294         self.assertEquals([ONE, TWO], self._walker.determine_wants(heads))
295
296         self._walker.proto.set_output(['want %s multi_ack' % FOUR])
297         self.assertRaises(GitProtocolError, self._walker.determine_wants, heads)
298
299         self._walker.proto.set_output([])
300         self.assertEquals([], self._walker.determine_wants(heads))
301
302         self._walker.proto.set_output(['want %s multi_ack' % ONE, 'foo'])
303         self.assertRaises(GitProtocolError, self._walker.determine_wants, heads)
304
305         self._walker.proto.set_output(['want %s multi_ack' % FOUR])
306         self.assertRaises(GitProtocolError, self._walker.determine_wants, heads)
307
308     def test_determine_wants_advertisement(self):
309         self._walker.proto.set_output([])
310         # advertise branch tips plus tag
311         heads = {'ref4': FOUR, 'ref5': FIVE, 'tag6': SIX}
312         peeled = {'ref4': FOUR, 'ref5': FIVE, 'tag6': FIVE}
313         self._walker.get_peeled = peeled.get
314         self._walker.determine_wants(heads)
315         lines = []
316         while True:
317             line = self._walker.proto.get_received_line()
318             if line == 'None':
319                 break
320             # strip capabilities list if present
321             if '\x00' in line:
322                 line = line[:line.index('\x00')]
323             lines.append(line.rstrip())
324
325         self.assertEquals([
326           '%s ref4' % FOUR,
327           '%s ref5' % FIVE,
328           '%s tag6^{}' % FIVE,
329           '%s tag6' % SIX,
330           ], sorted(lines))
331
332         # ensure peeled tag was advertised immediately following tag
333         for i, line in enumerate(lines):
334             if line.endswith(' tag6'):
335                 self.assertEquals('%s tag6^{}' % FIVE, lines[i+1])
336
337     # TODO: test commit time cutoff
338
339
340 class TestProtocolGraphWalker(object):
341
342     def __init__(self):
343         self.acks = []
344         self.lines = []
345         self.done = False
346         self.stateless_rpc = False
347         self.advertise_refs = False
348
349     def read_proto_line(self):
350         return self.lines.pop(0)
351
352     def send_ack(self, sha, ack_type=''):
353         self.acks.append((sha, ack_type))
354
355     def send_nak(self):
356         self.acks.append((None, 'nak'))
357
358     def all_wants_satisfied(self, haves):
359         return self.done
360
361     def pop_ack(self):
362         if not self.acks:
363             return None
364         return self.acks.pop(0)
365
366
367 class AckGraphWalkerImplTestCase(TestCase):
368     """Base setup and asserts for AckGraphWalker tests."""
369
370     def setUp(self):
371         super(AckGraphWalkerImplTestCase, self).setUp()
372         self._walker = TestProtocolGraphWalker()
373         self._walker.lines = [
374           ('have', TWO),
375           ('have', ONE),
376           ('have', THREE),
377           ('done', None),
378           ]
379         self._impl = self.impl_cls(self._walker)
380
381     def assertNoAck(self):
382         self.assertEquals(None, self._walker.pop_ack())
383
384     def assertAcks(self, acks):
385         for sha, ack_type in acks:
386             self.assertEquals((sha, ack_type), self._walker.pop_ack())
387         self.assertNoAck()
388
389     def assertAck(self, sha, ack_type=''):
390         self.assertAcks([(sha, ack_type)])
391
392     def assertNak(self):
393         self.assertAck(None, 'nak')
394
395     def assertNextEquals(self, sha):
396         self.assertEquals(sha, self._impl.next())
397
398
399 class SingleAckGraphWalkerImplTestCase(AckGraphWalkerImplTestCase):
400
401     impl_cls = SingleAckGraphWalkerImpl
402
403     def test_single_ack(self):
404         self.assertNextEquals(TWO)
405         self.assertNoAck()
406
407         self.assertNextEquals(ONE)
408         self._walker.done = True
409         self._impl.ack(ONE)
410         self.assertAck(ONE)
411
412         self.assertNextEquals(THREE)
413         self._impl.ack(THREE)
414         self.assertNoAck()
415
416         self.assertNextEquals(None)
417         self.assertNoAck()
418
419     def test_single_ack_flush(self):
420         # same as ack test but ends with a flush-pkt instead of done
421         self._walker.lines[-1] = (None, None)
422
423         self.assertNextEquals(TWO)
424         self.assertNoAck()
425
426         self.assertNextEquals(ONE)
427         self._walker.done = True
428         self._impl.ack(ONE)
429         self.assertAck(ONE)
430
431         self.assertNextEquals(THREE)
432         self.assertNoAck()
433
434         self.assertNextEquals(None)
435         self.assertNoAck()
436
437     def test_single_ack_nak(self):
438         self.assertNextEquals(TWO)
439         self.assertNoAck()
440
441         self.assertNextEquals(ONE)
442         self.assertNoAck()
443
444         self.assertNextEquals(THREE)
445         self.assertNoAck()
446
447         self.assertNextEquals(None)
448         self.assertNak()
449
450     def test_single_ack_nak_flush(self):
451         # same as nak test but ends with a flush-pkt instead of done
452         self._walker.lines[-1] = (None, None)
453
454         self.assertNextEquals(TWO)
455         self.assertNoAck()
456
457         self.assertNextEquals(ONE)
458         self.assertNoAck()
459
460         self.assertNextEquals(THREE)
461         self.assertNoAck()
462
463         self.assertNextEquals(None)
464         self.assertNak()
465
466
467 class MultiAckGraphWalkerImplTestCase(AckGraphWalkerImplTestCase):
468
469     impl_cls = MultiAckGraphWalkerImpl
470
471     def test_multi_ack(self):
472         self.assertNextEquals(TWO)
473         self.assertNoAck()
474
475         self.assertNextEquals(ONE)
476         self._walker.done = True
477         self._impl.ack(ONE)
478         self.assertAck(ONE, 'continue')
479
480         self.assertNextEquals(THREE)
481         self._impl.ack(THREE)
482         self.assertAck(THREE, 'continue')
483
484         self.assertNextEquals(None)
485         self.assertAck(THREE)
486
487     def test_multi_ack_partial(self):
488         self.assertNextEquals(TWO)
489         self.assertNoAck()
490
491         self.assertNextEquals(ONE)
492         self._impl.ack(ONE)
493         self.assertAck(ONE, 'continue')
494
495         self.assertNextEquals(THREE)
496         self.assertNoAck()
497
498         self.assertNextEquals(None)
499         # done, re-send ack of last common
500         self.assertAck(ONE)
501
502     def test_multi_ack_flush(self):
503         self._walker.lines = [
504           ('have', TWO),
505           (None, None),
506           ('have', ONE),
507           ('have', THREE),
508           ('done', None),
509           ]
510         self.assertNextEquals(TWO)
511         self.assertNoAck()
512
513         self.assertNextEquals(ONE)
514         self.assertNak()  # nak the flush-pkt
515
516         self._walker.done = True
517         self._impl.ack(ONE)
518         self.assertAck(ONE, 'continue')
519
520         self.assertNextEquals(THREE)
521         self._impl.ack(THREE)
522         self.assertAck(THREE, 'continue')
523
524         self.assertNextEquals(None)
525         self.assertAck(THREE)
526
527     def test_multi_ack_nak(self):
528         self.assertNextEquals(TWO)
529         self.assertNoAck()
530
531         self.assertNextEquals(ONE)
532         self.assertNoAck()
533
534         self.assertNextEquals(THREE)
535         self.assertNoAck()
536
537         self.assertNextEquals(None)
538         self.assertNak()
539
540
541 class MultiAckDetailedGraphWalkerImplTestCase(AckGraphWalkerImplTestCase):
542
543     impl_cls = MultiAckDetailedGraphWalkerImpl
544
545     def test_multi_ack(self):
546         self.assertNextEquals(TWO)
547         self.assertNoAck()
548
549         self.assertNextEquals(ONE)
550         self._walker.done = True
551         self._impl.ack(ONE)
552         self.assertAcks([(ONE, 'common'), (ONE, 'ready')])
553
554         self.assertNextEquals(THREE)
555         self._impl.ack(THREE)
556         self.assertAck(THREE, 'ready')
557
558         self.assertNextEquals(None)
559         self.assertAck(THREE)
560
561     def test_multi_ack_partial(self):
562         self.assertNextEquals(TWO)
563         self.assertNoAck()
564
565         self.assertNextEquals(ONE)
566         self._impl.ack(ONE)
567         self.assertAck(ONE, 'common')
568
569         self.assertNextEquals(THREE)
570         self.assertNoAck()
571
572         self.assertNextEquals(None)
573         # done, re-send ack of last common
574         self.assertAck(ONE)
575
576     def test_multi_ack_flush(self):
577         # same as ack test but contains a flush-pkt in the middle
578         self._walker.lines = [
579           ('have', TWO),
580           (None, None),
581           ('have', ONE),
582           ('have', THREE),
583           ('done', None),
584           ]
585         self.assertNextEquals(TWO)
586         self.assertNoAck()
587
588         self.assertNextEquals(ONE)
589         self.assertNak()  # nak the flush-pkt
590
591         self._walker.done = True
592         self._impl.ack(ONE)
593         self.assertAcks([(ONE, 'common'), (ONE, 'ready')])
594
595         self.assertNextEquals(THREE)
596         self._impl.ack(THREE)
597         self.assertAck(THREE, 'ready')
598
599         self.assertNextEquals(None)
600         self.assertAck(THREE)
601
602     def test_multi_ack_nak(self):
603         self.assertNextEquals(TWO)
604         self.assertNoAck()
605
606         self.assertNextEquals(ONE)
607         self.assertNoAck()
608
609         self.assertNextEquals(THREE)
610         self.assertNoAck()
611
612         self.assertNextEquals(None)
613         self.assertNak()
614
615     def test_multi_ack_nak_flush(self):
616         # same as nak test but contains a flush-pkt in the middle
617         self._walker.lines = [
618           ('have', TWO),
619           (None, None),
620           ('have', ONE),
621           ('have', THREE),
622           ('done', None),
623           ]
624         self.assertNextEquals(TWO)
625         self.assertNoAck()
626
627         self.assertNextEquals(ONE)
628         self.assertNak()
629
630         self.assertNextEquals(THREE)
631         self.assertNoAck()
632
633         self.assertNextEquals(None)
634         self.assertNak()
635
636     def test_multi_ack_stateless(self):
637         # transmission ends with a flush-pkt
638         self._walker.lines[-1] = (None, None)
639         self._walker.stateless_rpc = True
640
641         self.assertNextEquals(TWO)
642         self.assertNoAck()
643
644         self.assertNextEquals(ONE)
645         self.assertNoAck()
646
647         self.assertNextEquals(THREE)
648         self.assertNoAck()
649
650         self.assertNextEquals(None)
651         self.assertNak()