Add smart HTTP support to dul-web.
[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 cStringIO import StringIO
24 from unittest import TestCase
25
26 from dulwich.errors import (
27     GitProtocolError,
28     )
29 from dulwich.server import (
30     UploadPackHandler,
31     ProtocolGraphWalker,
32     SingleAckGraphWalkerImpl,
33     MultiAckGraphWalkerImpl,
34     MultiAckDetailedGraphWalkerImpl,
35     )
36
37 from dulwich.protocol import (
38     SINGLE_ACK,
39     MULTI_ACK,
40     )
41
42 ONE = '1' * 40
43 TWO = '2' * 40
44 THREE = '3' * 40
45 FOUR = '4' * 40
46 FIVE = '5' * 40
47
48 class TestProto(object):
49     def __init__(self):
50         self._output = []
51         self._received = {0: [], 1: [], 2: [], 3: []}
52
53     def set_output(self, output_lines):
54         self._output = ['%s\n' % line.rstrip() for line in output_lines]
55
56     def read_pkt_line(self):
57         if self._output:
58             return self._output.pop(0)
59         else:
60             return None
61
62     def write_sideband(self, band, data):
63         self._received[band].append(data)
64
65     def write_pkt_line(self, data):
66         if data is None:
67             data = 'None'
68         self._received[0].append(data)
69
70     def get_received_line(self, band=0):
71         lines = self._received[band]
72         if lines:
73             return lines.pop(0)
74         else:
75             return None
76
77
78 class UploadPackHandlerTestCase(TestCase):
79     def setUp(self):
80         self._handler = UploadPackHandler(None, None, None)
81
82     def test_set_client_capabilities(self):
83         try:
84             self._handler.set_client_capabilities([])
85         except GitProtocolError:
86             self.fail()
87
88         try:
89             self._handler.set_client_capabilities([
90                 'multi_ack', 'side-band-64k', 'thin-pack', 'ofs-delta'])
91         except GitProtocolError:
92             self.fail()
93
94     def test_set_client_capabilities_error(self):
95         self.assertRaises(GitProtocolError,
96                           self._handler.set_client_capabilities,
97                           ['weird_ack_level', 'ofs-delta'])
98         try:
99             self._handler.set_client_capabilities(['include-tag'])
100         except GitProtocolError:
101             self.fail()
102
103
104 class TestCommit(object):
105     def __init__(self, sha, parents, commit_time):
106         self.id = sha
107         self._parents = parents
108         self.commit_time = commit_time
109
110     def get_parents(self):
111         return self._parents
112
113     def __repr__(self):
114         return '%s(%s)' % (self.__class__.__name__, self._sha)
115
116
117 class TestBackend(object):
118     def __init__(self, objects):
119         self.object_store = objects
120
121
122 class TestHandler(object):
123     def __init__(self, objects, proto):
124         self.backend = TestBackend(objects)
125         self.proto = proto
126         self.stateless_rpc = False
127         self.advertise_refs = False
128
129     def capabilities(self):
130         return 'multi_ack'
131
132
133 class ProtocolGraphWalkerTestCase(TestCase):
134     def setUp(self):
135         # Create the following commit tree:
136         #   3---5
137         #  /
138         # 1---2---4
139         self._objects = {
140             ONE: TestCommit(ONE, [], 111),
141             TWO: TestCommit(TWO, [ONE], 222),
142             THREE: TestCommit(THREE, [ONE], 333),
143             FOUR: TestCommit(FOUR, [TWO], 444),
144             FIVE: TestCommit(FIVE, [THREE], 555),
145             }
146         self._walker = ProtocolGraphWalker(
147             TestHandler(self._objects, TestProto()))
148
149     def test_is_satisfied_no_haves(self):
150         self.assertFalse(self._walker._is_satisfied([], ONE, 0))
151         self.assertFalse(self._walker._is_satisfied([], TWO, 0))
152         self.assertFalse(self._walker._is_satisfied([], THREE, 0))
153
154     def test_is_satisfied_have_root(self):
155         self.assertTrue(self._walker._is_satisfied([ONE], ONE, 0))
156         self.assertTrue(self._walker._is_satisfied([ONE], TWO, 0))
157         self.assertTrue(self._walker._is_satisfied([ONE], THREE, 0))
158
159     def test_is_satisfied_have_branch(self):
160         self.assertTrue(self._walker._is_satisfied([TWO], TWO, 0))
161         # wrong branch
162         self.assertFalse(self._walker._is_satisfied([TWO], THREE, 0))
163
164     def test_all_wants_satisfied(self):
165         self._walker.set_wants([FOUR, FIVE])
166         # trivial case: wants == haves
167         self.assertTrue(self._walker.all_wants_satisfied([FOUR, FIVE]))
168         # cases that require walking the commit tree
169         self.assertTrue(self._walker.all_wants_satisfied([ONE]))
170         self.assertFalse(self._walker.all_wants_satisfied([TWO]))
171         self.assertFalse(self._walker.all_wants_satisfied([THREE]))
172         self.assertTrue(self._walker.all_wants_satisfied([TWO, THREE]))
173
174     def test_read_proto_line(self):
175         self._walker.proto.set_output([
176             'want %s' % ONE,
177             'want %s' % TWO,
178             'have %s' % THREE,
179             'foo %s' % FOUR,
180             'bar',
181             'done',
182             ])
183         self.assertEquals(('want', ONE), self._walker.read_proto_line())
184         self.assertEquals(('want', TWO), self._walker.read_proto_line())
185         self.assertEquals(('have', THREE), self._walker.read_proto_line())
186         self.assertRaises(GitProtocolError, self._walker.read_proto_line)
187         self.assertRaises(GitProtocolError, self._walker.read_proto_line)
188         self.assertEquals(('done', None), self._walker.read_proto_line())
189         self.assertEquals((None, None), self._walker.read_proto_line())
190
191     def test_determine_wants(self):
192         self.assertRaises(GitProtocolError, self._walker.determine_wants, {})
193
194         self._walker.proto.set_output([
195             'want %s multi_ack' % ONE,
196             'want %s' % TWO,
197             ])
198         heads = {'ref1': ONE, 'ref2': TWO, 'ref3': THREE}
199         self.assertEquals([ONE, TWO], self._walker.determine_wants(heads))
200
201         self._walker.proto.set_output(['want %s multi_ack' % FOUR])
202         self.assertRaises(GitProtocolError, self._walker.determine_wants, heads)
203
204         self._walker.proto.set_output([])
205         self.assertRaises(GitProtocolError, self._walker.determine_wants, heads)
206
207         self._walker.proto.set_output(['want %s multi_ack' % ONE, 'foo'])
208         self.assertRaises(GitProtocolError, self._walker.determine_wants, heads)
209
210         self._walker.proto.set_output(['want %s multi_ack' % FOUR])
211         self.assertRaises(GitProtocolError, self._walker.determine_wants, heads)
212
213     # TODO: test commit time cutoff
214
215
216 class TestProtocolGraphWalker(object):
217     def __init__(self):
218         self.acks = []
219         self.lines = []
220         self.done = False
221         self.stateless_rpc = False
222         self.advertise_refs = False
223
224     def read_proto_line(self):
225         return self.lines.pop(0)
226
227     def send_ack(self, sha, ack_type=''):
228         self.acks.append((sha, ack_type))
229
230     def send_nak(self):
231         self.acks.append((None, 'nak'))
232
233     def all_wants_satisfied(self, haves):
234         return self.done
235
236     def pop_ack(self):
237         if not self.acks:
238             return None
239         return self.acks.pop(0)
240
241
242 class AckGraphWalkerImplTestCase(TestCase):
243     """Base setup and asserts for AckGraphWalker tests."""
244     def setUp(self):
245         self._walker = TestProtocolGraphWalker()
246         self._walker.lines = [
247             ('have', TWO),
248             ('have', ONE),
249             ('have', THREE),
250             ('done', None),
251             ]
252         self._impl = self.impl_cls(self._walker)
253
254     def assertNoAck(self):
255         self.assertEquals(None, self._walker.pop_ack())
256
257     def assertAcks(self, acks):
258         for sha, ack_type in acks:
259             self.assertEquals((sha, ack_type), self._walker.pop_ack())
260         self.assertNoAck()
261
262     def assertAck(self, sha, ack_type=''):
263         self.assertAcks([(sha, ack_type)])
264
265     def assertNak(self):
266         self.assertAck(None, 'nak')
267
268     def assertNextEquals(self, sha):
269         self.assertEquals(sha, self._impl.next())
270
271
272 class SingleAckGraphWalkerImplTestCase(AckGraphWalkerImplTestCase):
273     impl_cls = SingleAckGraphWalkerImpl
274
275     def test_single_ack(self):
276         self.assertNextEquals(TWO)
277         self.assertNoAck()
278
279         self.assertNextEquals(ONE)
280         self._walker.done = True
281         self._impl.ack(ONE)
282         self.assertAck(ONE)
283
284         self.assertNextEquals(THREE)
285         self._impl.ack(THREE)
286         self.assertNoAck()
287
288         self.assertNextEquals(None)
289         self.assertNoAck()
290
291     def test_single_ack_flush(self):
292         # same as ack test but ends with a flush-pkt instead of done
293         self._walker.lines[-1] = (None, None)
294
295         self.assertNextEquals(TWO)
296         self.assertNoAck()
297
298         self.assertNextEquals(ONE)
299         self._walker.done = True
300         self._impl.ack(ONE)
301         self.assertAck(ONE)
302
303         self.assertNextEquals(THREE)
304         self.assertNoAck()
305
306         self.assertNextEquals(None)
307         self.assertNoAck()
308
309     def test_single_ack_nak(self):
310         self.assertNextEquals(TWO)
311         self.assertNoAck()
312
313         self.assertNextEquals(ONE)
314         self.assertNoAck()
315
316         self.assertNextEquals(THREE)
317         self.assertNoAck()
318
319         self.assertNextEquals(None)
320         self.assertNak()
321
322     def test_single_ack_nak_flush(self):
323         # same as nak test but ends with a flush-pkt instead of done
324         self._walker.lines[-1] = (None, None)
325
326         self.assertNextEquals(TWO)
327         self.assertNoAck()
328
329         self.assertNextEquals(ONE)
330         self.assertNoAck()
331
332         self.assertNextEquals(THREE)
333         self.assertNoAck()
334
335         self.assertNextEquals(None)
336         self.assertNak()
337
338 class MultiAckGraphWalkerImplTestCase(AckGraphWalkerImplTestCase):
339     impl_cls = MultiAckGraphWalkerImpl
340
341     def test_multi_ack(self):
342         self.assertNextEquals(TWO)
343         self.assertNoAck()
344
345         self.assertNextEquals(ONE)
346         self._walker.done = True
347         self._impl.ack(ONE)
348         self.assertAck(ONE, 'continue')
349
350         self.assertNextEquals(THREE)
351         self._impl.ack(THREE)
352         self.assertAck(THREE, 'continue')
353
354         self.assertNextEquals(None)
355         self.assertAck(THREE)
356
357     def test_multi_ack_partial(self):
358         self.assertNextEquals(TWO)
359         self.assertNoAck()
360
361         self.assertNextEquals(ONE)
362         self._impl.ack(ONE)
363         self.assertAck(ONE, 'continue')
364
365         self.assertNextEquals(THREE)
366         self.assertNoAck()
367
368         self.assertNextEquals(None)
369         # done, re-send ack of last common
370         self.assertAck(ONE)
371
372     def test_multi_ack_flush(self):
373         self._walker.lines = [
374             ('have', TWO),
375             (None, None),
376             ('have', ONE),
377             ('have', THREE),
378             ('done', None),
379             ]
380         self.assertNextEquals(TWO)
381         self.assertNoAck()
382
383         self.assertNextEquals(ONE)
384         self.assertNak() # nak the flush-pkt
385
386         self._walker.done = True
387         self._impl.ack(ONE)
388         self.assertAck(ONE, 'continue')
389
390         self.assertNextEquals(THREE)
391         self._impl.ack(THREE)
392         self.assertAck(THREE, 'continue')
393
394         self.assertNextEquals(None)
395         self.assertAck(THREE)
396
397     def test_multi_ack_nak(self):
398         self.assertNextEquals(TWO)
399         self.assertNoAck()
400
401         self.assertNextEquals(ONE)
402         self.assertNoAck()
403
404         self.assertNextEquals(THREE)
405         self.assertNoAck()
406
407         self.assertNextEquals(None)
408         self.assertNak()
409
410 class MultiAckDetailedGraphWalkerImplTestCase(AckGraphWalkerImplTestCase):
411     impl_cls = MultiAckDetailedGraphWalkerImpl
412
413     def test_multi_ack(self):
414         self.assertNextEquals(TWO)
415         self.assertNoAck()
416
417         self.assertNextEquals(ONE)
418         self._walker.done = True
419         self._impl.ack(ONE)
420         self.assertAcks([(ONE, 'common'), (ONE, 'ready')])
421
422         self.assertNextEquals(THREE)
423         self._impl.ack(THREE)
424         self.assertAck(THREE, 'ready')
425
426         self.assertNextEquals(None)
427         self.assertAck(THREE)
428
429     def test_multi_ack_partial(self):
430         self.assertNextEquals(TWO)
431         self.assertNoAck()
432
433         self.assertNextEquals(ONE)
434         self._impl.ack(ONE)
435         self.assertAck(ONE, 'common')
436
437         self.assertNextEquals(THREE)
438         self.assertNoAck()
439
440         self.assertNextEquals(None)
441         # done, re-send ack of last common
442         self.assertAck(ONE)
443
444     def test_multi_ack_flush(self):
445         # same as ack test but contains a flush-pkt in the middle
446         self._walker.lines = [
447             ('have', TWO),
448             (None, None),
449             ('have', ONE),
450             ('have', THREE),
451             ('done', None),
452             ]
453         self.assertNextEquals(TWO)
454         self.assertNoAck()
455
456         self.assertNextEquals(ONE)
457         self.assertNak() # nak the flush-pkt
458
459         self._walker.done = True
460         self._impl.ack(ONE)
461         self.assertAcks([(ONE, 'common'), (ONE, 'ready')])
462
463         self.assertNextEquals(THREE)
464         self._impl.ack(THREE)
465         self.assertAck(THREE, 'ready')
466
467         self.assertNextEquals(None)
468         self.assertAck(THREE)
469
470     def test_multi_ack_nak(self):
471         self.assertNextEquals(TWO)
472         self.assertNoAck()
473
474         self.assertNextEquals(ONE)
475         self.assertNoAck()
476
477         self.assertNextEquals(THREE)
478         self.assertNoAck()
479
480         self.assertNextEquals(None)
481         self.assertNak()
482
483     def test_multi_ack_nak_flush(self):
484         # same as nak test but contains a flush-pkt in the middle
485         self._walker.lines = [
486             ('have', TWO),
487             (None, None),
488             ('have', ONE),
489             ('have', THREE),
490             ('done', None),
491             ]
492         self.assertNextEquals(TWO)
493         self.assertNoAck()
494
495         self.assertNextEquals(ONE)
496         self.assertNak()
497
498         self.assertNextEquals(THREE)
499         self.assertNoAck()
500
501         self.assertNextEquals(None)
502         self.assertNak()
503
504     def test_multi_ack_stateless(self):
505         # transmission ends with a flush-pkt
506         self._walker.lines[-1] = (None, None)
507         self._walker.stateless_rpc = True
508
509         self.assertNextEquals(TWO)
510         self.assertNoAck()
511
512         self.assertNextEquals(ONE)
513         self.assertNoAck()
514
515         self.assertNextEquals(THREE)
516         self.assertNoAck()
517
518         self.assertNextEquals(None)
519         self.assertNak()