Fix numerous style issues.
[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
20 """Tests for the smart protocol server."""
21
22
23 from unittest import TestCase
24
25 from dulwich.errors import (
26     GitProtocolError,
27     )
28 from dulwich.server import (
29     Backend,
30     DictBackend,
31     BackendRepo,
32     Handler,
33     MultiAckGraphWalkerImpl,
34     MultiAckDetailedGraphWalkerImpl,
35     ProtocolGraphWalker,
36     SingleAckGraphWalkerImpl,
37     UploadPackHandler,
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 HandlerTestCase(TestCase):
81
82     def setUp(self):
83         self._handler = Handler(Backend(), None)
84         self._handler.capabilities = lambda: ('cap1', 'cap2', 'cap3')
85         self._handler.required_capabilities = lambda: ('cap2',)
86
87     def assertSucceeds(self, func, *args, **kwargs):
88         try:
89             func(*args, **kwargs)
90         except GitProtocolError, e:
91             self.fail(e)
92
93     def test_capability_line(self):
94         self.assertEquals('cap1 cap2 cap3', self._handler.capability_line())
95
96     def test_set_client_capabilities(self):
97         set_caps = self._handler.set_client_capabilities
98         self.assertSucceeds(set_caps, ['cap2'])
99         self.assertSucceeds(set_caps, ['cap1', 'cap2'])
100
101         # different order
102         self.assertSucceeds(set_caps, ['cap3', 'cap1', 'cap2'])
103
104         # error cases
105         self.assertRaises(GitProtocolError, set_caps, ['capxxx', 'cap2'])
106         self.assertRaises(GitProtocolError, set_caps, ['cap1', 'cap3'])
107
108         # ignore innocuous but unknown capabilities
109         self.assertRaises(GitProtocolError, set_caps, ['cap2', 'ignoreme'])
110         self.assertFalse('ignoreme' in self._handler.capabilities())
111         self._handler.innocuous_capabilities = lambda: ('ignoreme',)
112         self.assertSucceeds(set_caps, ['cap2', 'ignoreme'])
113
114     def test_has_capability(self):
115         self.assertRaises(GitProtocolError, self._handler.has_capability, 'cap')
116         caps = self._handler.capabilities()
117         self._handler.set_client_capabilities(caps)
118         for cap in caps:
119             self.assertTrue(self._handler.has_capability(cap))
120         self.assertFalse(self._handler.has_capability('capxxx'))
121
122
123 class UploadPackHandlerTestCase(TestCase):
124
125     def setUp(self):
126         self._backend = DictBackend({"/": BackendRepo()})
127         self._handler = UploadPackHandler(self._backend,
128                 ["/", "host=lolcathost"], None, None)
129         self._handler.proto = TestProto()
130
131     def test_progress(self):
132         caps = self._handler.required_capabilities()
133         self._handler.set_client_capabilities(caps)
134         self._handler.progress('first message')
135         self._handler.progress('second message')
136         self.assertEqual('first message',
137                          self._handler.proto.get_received_line(2))
138         self.assertEqual('second message',
139                          self._handler.proto.get_received_line(2))
140         self.assertEqual(None, self._handler.proto.get_received_line(2))
141
142     def test_no_progress(self):
143         caps = list(self._handler.required_capabilities()) + ['no-progress']
144         self._handler.set_client_capabilities(caps)
145         self._handler.progress('first message')
146         self._handler.progress('second message')
147         self.assertEqual(None, self._handler.proto.get_received_line(2))
148
149     def test_get_tagged(self):
150         refs = {
151             'refs/tags/tag1': ONE,
152             'refs/tags/tag2': TWO,
153             'refs/heads/master': FOUR,  # not a tag, no peeled value
154             }
155         peeled = {
156             'refs/tags/tag1': '1234',
157             'refs/tags/tag2': '5678',
158             }
159
160         class TestRepo(object):
161             def get_peeled(self, ref):
162                 return peeled.get(ref, refs[ref])
163
164         caps = list(self._handler.required_capabilities()) + ['include-tag']
165         self._handler.set_client_capabilities(caps)
166         self.assertEquals({'1234': ONE, '5678': TWO},
167                           self._handler.get_tagged(refs, repo=TestRepo()))
168
169         # non-include-tag case
170         caps = self._handler.required_capabilities()
171         self._handler.set_client_capabilities(caps)
172         self.assertEquals({}, self._handler.get_tagged(refs, repo=TestRepo()))
173
174
175 class TestCommit(object):
176
177     def __init__(self, sha, parents, commit_time):
178         self.id = sha
179         self.parents = parents
180         self.commit_time = commit_time
181         self.type_name = "commit"
182
183     def __repr__(self):
184         return '%s(%s)' % (self.__class__.__name__, self._sha)
185
186
187 class TestRepo(object):
188     def __init__(self):
189         self.peeled = {}
190
191     def get_peeled(self, name):
192         return self.peeled[name]
193
194
195 class TestBackend(object):
196
197     def __init__(self, repo, objects):
198         self.repo = repo
199         self.object_store = objects
200
201
202 class TestUploadPackHandler(Handler):
203
204     def __init__(self, objects, proto):
205         self.backend = TestBackend(TestRepo(), objects)
206         self.proto = proto
207         self.stateless_rpc = False
208         self.advertise_refs = False
209
210     def capabilities(self):
211         return ('multi_ack',)
212
213
214 class ProtocolGraphWalkerTestCase(TestCase):
215
216     def setUp(self):
217         # Create the following commit tree:
218         #   3---5
219         #  /
220         # 1---2---4
221         self._objects = {
222           ONE: TestCommit(ONE, [], 111),
223           TWO: TestCommit(TWO, [ONE], 222),
224           THREE: TestCommit(THREE, [ONE], 333),
225           FOUR: TestCommit(FOUR, [TWO], 444),
226           FIVE: TestCommit(FIVE, [THREE], 555),
227           }
228
229         self._walker = ProtocolGraphWalker(
230             TestUploadPackHandler(self._objects, TestProto()),
231             self._objects, None)
232
233     def test_is_satisfied_no_haves(self):
234         self.assertFalse(self._walker._is_satisfied([], ONE, 0))
235         self.assertFalse(self._walker._is_satisfied([], TWO, 0))
236         self.assertFalse(self._walker._is_satisfied([], THREE, 0))
237
238     def test_is_satisfied_have_root(self):
239         self.assertTrue(self._walker._is_satisfied([ONE], ONE, 0))
240         self.assertTrue(self._walker._is_satisfied([ONE], TWO, 0))
241         self.assertTrue(self._walker._is_satisfied([ONE], THREE, 0))
242
243     def test_is_satisfied_have_branch(self):
244         self.assertTrue(self._walker._is_satisfied([TWO], TWO, 0))
245         # wrong branch
246         self.assertFalse(self._walker._is_satisfied([TWO], THREE, 0))
247
248     def test_all_wants_satisfied(self):
249         self._walker.set_wants([FOUR, FIVE])
250         # trivial case: wants == haves
251         self.assertTrue(self._walker.all_wants_satisfied([FOUR, FIVE]))
252         # cases that require walking the commit tree
253         self.assertTrue(self._walker.all_wants_satisfied([ONE]))
254         self.assertFalse(self._walker.all_wants_satisfied([TWO]))
255         self.assertFalse(self._walker.all_wants_satisfied([THREE]))
256         self.assertTrue(self._walker.all_wants_satisfied([TWO, THREE]))
257
258     def test_read_proto_line(self):
259         self._walker.proto.set_output([
260           'want %s' % ONE,
261           'want %s' % TWO,
262           'have %s' % THREE,
263           'foo %s' % FOUR,
264           'bar',
265           'done',
266           ])
267         self.assertEquals(('want', ONE), self._walker.read_proto_line())
268         self.assertEquals(('want', TWO), self._walker.read_proto_line())
269         self.assertEquals(('have', THREE), self._walker.read_proto_line())
270         self.assertRaises(GitProtocolError, self._walker.read_proto_line)
271         self.assertRaises(GitProtocolError, self._walker.read_proto_line)
272         self.assertEquals(('done', None), self._walker.read_proto_line())
273         self.assertEquals((None, None), self._walker.read_proto_line())
274
275     def test_determine_wants(self):
276         self.assertRaises(GitProtocolError, self._walker.determine_wants, {})
277
278         self._walker.proto.set_output([
279           'want %s multi_ack' % ONE,
280           'want %s' % TWO,
281           ])
282         heads = {'ref1': ONE, 'ref2': TWO, 'ref3': THREE}
283         self._walker.get_peeled = heads.get
284         self.assertEquals([ONE, TWO], 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         self._walker.proto.set_output([])
290         self.assertEquals([], self._walker.determine_wants(heads))
291
292         self._walker.proto.set_output(['want %s multi_ack' % ONE, 'foo'])
293         self.assertRaises(GitProtocolError, self._walker.determine_wants, heads)
294
295         self._walker.proto.set_output(['want %s multi_ack' % FOUR])
296         self.assertRaises(GitProtocolError, self._walker.determine_wants, heads)
297
298     def test_determine_wants_advertisement(self):
299         self._walker.proto.set_output([])
300         # advertise branch tips plus tag
301         heads = {'ref4': FOUR, 'ref5': FIVE, 'tag6': SIX}
302         peeled = {'ref4': FOUR, 'ref5': FIVE, 'tag6': FIVE}
303         self._walker.get_peeled = peeled.get
304         self._walker.determine_wants(heads)
305         lines = []
306         while True:
307             line = self._walker.proto.get_received_line()
308             if line == 'None':
309                 break
310             # strip capabilities list if present
311             if '\x00' in line:
312                 line = line[:line.index('\x00')]
313             lines.append(line.rstrip())
314
315         self.assertEquals([
316           '%s ref4' % FOUR,
317           '%s ref5' % FIVE,
318           '%s tag6^{}' % FIVE,
319           '%s tag6' % SIX,
320           ], sorted(lines))
321
322         # ensure peeled tag was advertised immediately following tag
323         for i, line in enumerate(lines):
324             if line.endswith(' tag6'):
325                 self.assertEquals('%s tag6^{}' % FIVE, lines[i+1])
326
327     # TODO: test commit time cutoff
328
329
330 class TestProtocolGraphWalker(object):
331
332     def __init__(self):
333         self.acks = []
334         self.lines = []
335         self.done = False
336         self.stateless_rpc = False
337         self.advertise_refs = False
338
339     def read_proto_line(self):
340         return self.lines.pop(0)
341
342     def send_ack(self, sha, ack_type=''):
343         self.acks.append((sha, ack_type))
344
345     def send_nak(self):
346         self.acks.append((None, 'nak'))
347
348     def all_wants_satisfied(self, haves):
349         return self.done
350
351     def pop_ack(self):
352         if not self.acks:
353             return None
354         return self.acks.pop(0)
355
356
357 class AckGraphWalkerImplTestCase(TestCase):
358     """Base setup and asserts for AckGraphWalker tests."""
359
360     def setUp(self):
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()