Statistics
| Revision:

gvsig-scripting / org.gvsig.scripting / trunk / org.gvsig.scripting / org.gvsig.scripting.app / org.gvsig.scripting.app.mainplugin / src / main / resources-plugin / scripting / lib / dulwich / tests / test_server.py @ 959

History | View | Annotate | Download (34.7 KB)

1
# test_server.py -- Tests for the git server
2
# Copyright (C) 2010 Google, Inc.
3
#
4
# Dulwich is dual-licensed under the Apache License, Version 2.0 and the GNU
5
# General Public License as public by the Free Software Foundation; version 2.0
6
# or (at your option) any later version. You can redistribute it and/or
7
# modify it under the terms of either of these two licenses.
8
#
9
# Unless required by applicable law or agreed to in writing, software
10
# distributed under the License is distributed on an "AS IS" BASIS,
11
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
# See the License for the specific language governing permissions and
13
# limitations under the License.
14
#
15
# You should have received a copy of the licenses; if not, see
16
# <http://www.gnu.org/licenses/> for a copy of the GNU General Public License
17
# and <http://www.apache.org/licenses/LICENSE-2.0> for a copy of the Apache
18
# License, Version 2.0.
19
#
20

    
21
"""Tests for the smart protocol server."""
22

    
23
from io import BytesIO
24
import os
25
import shutil
26
import tempfile
27

    
28
import sys
29

    
30
from dulwich.errors import (
31
    GitProtocolError,
32
    NotGitRepository,
33
    UnexpectedCommandError,
34
    HangupException,
35
    )
36
from dulwich.object_store import (
37
    MemoryObjectStore,
38
    )
39
from dulwich.repo import (
40
    MemoryRepo,
41
    Repo,
42
    )
43
from dulwich.server import (
44
    Backend,
45
    DictBackend,
46
    FileSystemBackend,
47
    MultiAckGraphWalkerImpl,
48
    MultiAckDetailedGraphWalkerImpl,
49
    PackHandler,
50
    _split_proto_line,
51
    serve_command,
52
    _find_shallow,
53
    ProtocolGraphWalker,
54
    ReceivePackHandler,
55
    SingleAckGraphWalkerImpl,
56
    UploadPackHandler,
57
    update_server_info,
58
    )
59
from dulwich.tests import TestCase
60
from dulwich.tests.utils import (
61
    make_commit,
62
    make_tag,
63
    )
64
from dulwich.protocol import (
65
    ZERO_SHA,
66
    )
67

    
68
ONE = b'1' * 40
69
TWO = b'2' * 40
70
THREE = b'3' * 40
71
FOUR = b'4' * 40
72
FIVE = b'5' * 40
73
SIX = b'6' * 40
74

    
75

    
76
class TestProto(object):
77

    
78
    def __init__(self):
79
        self._output = []
80
        self._received = {0: [], 1: [], 2: [], 3: []}
81

    
82
    def set_output(self, output_lines):
83
        self._output = output_lines
84

    
85
    def read_pkt_line(self):
86
        if self._output:
87
            data = self._output.pop(0)
88
            if data is not None:
89
                return data.rstrip() + b'\n'
90
            else:
91
                # flush-pkt ('0000').
92
                return None
93
        else:
94
            raise HangupException()
95

    
96
    def write_sideband(self, band, data):
97
        self._received[band].append(data)
98

    
99
    def write_pkt_line(self, data):
100
        self._received[0].append(data)
101

    
102
    def get_received_line(self, band=0):
103
        lines = self._received[band]
104
        return lines.pop(0)
105

    
106

    
107
class TestGenericPackHandler(PackHandler):
108

    
109
    def __init__(self):
110
        PackHandler.__init__(self, Backend(), None)
111

    
112
    @classmethod
113
    def capabilities(cls):
114
        return (b'cap1', b'cap2', b'cap3')
115

    
116
    @classmethod
117
    def required_capabilities(cls):
118
        return (b'cap2',)
119

    
120

    
121
class HandlerTestCase(TestCase):
122

    
123
    def setUp(self):
124
        super(HandlerTestCase, self).setUp()
125
        self._handler = TestGenericPackHandler()
126

    
127
    def assertSucceeds(self, func, *args, **kwargs):
128
        try:
129
            func(*args, **kwargs)
130
        except GitProtocolError as e:
131
            self.fail(e)
132

    
133
    def test_capability_line(self):
134
        self.assertEqual(b' cap1 cap2 cap3', self._handler.capability_line())
135

    
136
    def test_set_client_capabilities(self):
137
        set_caps = self._handler.set_client_capabilities
138
        self.assertSucceeds(set_caps, [b'cap2'])
139
        self.assertSucceeds(set_caps, [b'cap1', b'cap2'])
140

    
141
        # different order
142
        self.assertSucceeds(set_caps, [b'cap3', b'cap1', b'cap2'])
143

    
144
        # error cases
145
        self.assertRaises(GitProtocolError, set_caps, [b'capxxx', b'cap2'])
146
        self.assertRaises(GitProtocolError, set_caps, [b'cap1', b'cap3'])
147

    
148
        # ignore innocuous but unknown capabilities
149
        self.assertRaises(GitProtocolError, set_caps, [b'cap2', b'ignoreme'])
150
        self.assertFalse(b'ignoreme' in self._handler.capabilities())
151
        self._handler.innocuous_capabilities = lambda: (b'ignoreme',)
152
        self.assertSucceeds(set_caps, [b'cap2', b'ignoreme'])
153

    
154
    def test_has_capability(self):
155
        self.assertRaises(GitProtocolError, self._handler.has_capability, b'cap')
156
        caps = self._handler.capabilities()
157
        self._handler.set_client_capabilities(caps)
158
        for cap in caps:
159
            self.assertTrue(self._handler.has_capability(cap))
160
        self.assertFalse(self._handler.has_capability(b'capxxx'))
161

    
162

    
163
class UploadPackHandlerTestCase(TestCase):
164

    
165
    def setUp(self):
166
        super(UploadPackHandlerTestCase, self).setUp()
167
        self._repo = MemoryRepo.init_bare([], {})
168
        backend = DictBackend({b'/': self._repo})
169
        self._handler = UploadPackHandler(
170
          backend, [b'/', b'host=lolcathost'], TestProto())
171

    
172
    def test_progress(self):
173
        caps = self._handler.required_capabilities()
174
        self._handler.set_client_capabilities(caps)
175
        self._handler.progress(b'first message')
176
        self._handler.progress(b'second message')
177
        self.assertEqual(b'first message',
178
                         self._handler.proto.get_received_line(2))
179
        self.assertEqual(b'second message',
180
                         self._handler.proto.get_received_line(2))
181
        self.assertRaises(IndexError, self._handler.proto.get_received_line, 2)
182

    
183
    def test_no_progress(self):
184
        caps = list(self._handler.required_capabilities()) + [b'no-progress']
185
        self._handler.set_client_capabilities(caps)
186
        self._handler.progress(b'first message')
187
        self._handler.progress(b'second message')
188
        self.assertRaises(IndexError, self._handler.proto.get_received_line, 2)
189

    
190
    def test_get_tagged(self):
191
        refs = {
192
            b'refs/tags/tag1': ONE,
193
            b'refs/tags/tag2': TWO,
194
            b'refs/heads/master': FOUR,  # not a tag, no peeled value
195
            }
196
        # repo needs to peel this object
197
        self._repo.object_store.add_object(make_commit(id=FOUR))
198
        self._repo.refs._update(refs)
199
        peeled = {
200
            b'refs/tags/tag1': b'1234' * 10,
201
            b'refs/tags/tag2': b'5678' * 10,
202
            }
203
        self._repo.refs._update_peeled(peeled)
204

    
205
        caps = list(self._handler.required_capabilities()) + [b'include-tag']
206
        self._handler.set_client_capabilities(caps)
207
        self.assertEqual({b'1234' * 10: ONE, b'5678' * 10: TWO},
208
                          self._handler.get_tagged(refs, repo=self._repo))
209

    
210
        # non-include-tag case
211
        caps = self._handler.required_capabilities()
212
        self._handler.set_client_capabilities(caps)
213
        self.assertEqual({}, self._handler.get_tagged(refs, repo=self._repo))
214

    
215

    
216
class FindShallowTests(TestCase):
217

    
218
    def setUp(self):
219
        super(FindShallowTests, self).setUp()
220
        self._store = MemoryObjectStore()
221

    
222
    def make_commit(self, **attrs):
223
        commit = make_commit(**attrs)
224
        self._store.add_object(commit)
225
        return commit
226

    
227
    def make_linear_commits(self, n, message=b''):
228
        commits = []
229
        parents = []
230
        for _ in range(n):
231
            commits.append(self.make_commit(parents=parents, message=message))
232
            parents = [commits[-1].id]
233
        return commits
234

    
235
    def assertSameElements(self, expected, actual):
236
        self.assertEqual(set(expected), set(actual))
237

    
238
    def test_linear(self):
239
        c1, c2, c3 = self.make_linear_commits(3)
240

    
241
        self.assertEqual((set([c3.id]), set([])),
242
                         _find_shallow(self._store, [c3.id], 1))
243
        self.assertEqual((set([c2.id]), set([c3.id])),
244
                         _find_shallow(self._store, [c3.id], 2))
245
        self.assertEqual((set([c1.id]), set([c2.id, c3.id])),
246
                         _find_shallow(self._store, [c3.id], 3))
247
        self.assertEqual((set([]), set([c1.id, c2.id, c3.id])),
248
                         _find_shallow(self._store, [c3.id], 4))
249

    
250
    def test_multiple_independent(self):
251
        a = self.make_linear_commits(2, message=b'a')
252
        b = self.make_linear_commits(2, message=b'b')
253
        c = self.make_linear_commits(2, message=b'c')
254
        heads = [a[1].id, b[1].id, c[1].id]
255

    
256
        self.assertEqual((set([a[0].id, b[0].id, c[0].id]), set(heads)),
257
                         _find_shallow(self._store, heads, 2))
258

    
259
    def test_multiple_overlapping(self):
260
        # Create the following commit tree:
261
        # 1--2
262
        #  \
263
        #   3--4
264
        c1, c2 = self.make_linear_commits(2)
265
        c3 = self.make_commit(parents=[c1.id])
266
        c4 = self.make_commit(parents=[c3.id])
267

    
268
        # 1 is shallow along the path from 4, but not along the path from 2.
269
        self.assertEqual((set([c1.id]), set([c1.id, c2.id, c3.id, c4.id])),
270
                         _find_shallow(self._store, [c2.id, c4.id], 3))
271

    
272
    def test_merge(self):
273
        c1 = self.make_commit()
274
        c2 = self.make_commit()
275
        c3 = self.make_commit(parents=[c1.id, c2.id])
276

    
277
        self.assertEqual((set([c1.id, c2.id]), set([c3.id])),
278
                         _find_shallow(self._store, [c3.id], 2))
279

    
280
    def test_tag(self):
281
        c1, c2 = self.make_linear_commits(2)
282
        tag = make_tag(c2, name=b'tag')
283
        self._store.add_object(tag)
284

    
285
        self.assertEqual((set([c1.id]), set([c2.id])),
286
                         _find_shallow(self._store, [tag.id], 2))
287

    
288

    
289
class TestUploadPackHandler(UploadPackHandler):
290
    @classmethod
291
    def required_capabilities(self):
292
        return ()
293

    
294
class ReceivePackHandlerTestCase(TestCase):
295

    
296
    def setUp(self):
297
        super(ReceivePackHandlerTestCase, self).setUp()
298
        self._repo = MemoryRepo.init_bare([], {})
299
        backend = DictBackend({b'/': self._repo})
300
        self._handler = ReceivePackHandler(
301
          backend, [b'/', b'host=lolcathost'], TestProto())
302

    
303
    def test_apply_pack_del_ref(self):
304
        refs = {
305
            b'refs/heads/master': TWO,
306
            b'refs/heads/fake-branch': ONE}
307
        self._repo.refs._update(refs)
308
        update_refs = [[ONE, ZERO_SHA, b'refs/heads/fake-branch'], ]
309
        status = self._handler._apply_pack(update_refs)
310
        self.assertEqual(status[0][0], b'unpack')
311
        self.assertEqual(status[0][1], b'ok')
312
        self.assertEqual(status[1][0], b'refs/heads/fake-branch')
313
        self.assertEqual(status[1][1], b'ok')
314

    
315

    
316
class ProtocolGraphWalkerEmptyTestCase(TestCase):
317
    def setUp(self):
318
        super(ProtocolGraphWalkerEmptyTestCase, self).setUp()
319
        self._repo = MemoryRepo.init_bare([], {})
320
        backend = DictBackend({b'/': self._repo})
321
        self._walker = ProtocolGraphWalker(
322
            TestUploadPackHandler(backend, [b'/', b'host=lolcats'], TestProto()),
323
            self._repo.object_store, self._repo.get_peeled)
324

    
325
    def test_empty_repository(self):
326
        # The server should wait for a flush packet.
327
        self._walker.proto.set_output([])
328
        self.assertRaises(HangupException, self._walker.determine_wants, {})
329
        self.assertEqual(None, self._walker.proto.get_received_line())
330

    
331
        self._walker.proto.set_output([None])
332
        self.assertEqual([], self._walker.determine_wants({}))
333
        self.assertEqual(None, self._walker.proto.get_received_line())
334

    
335

    
336

    
337
class ProtocolGraphWalkerTestCase(TestCase):
338

    
339
    def setUp(self):
340
        super(ProtocolGraphWalkerTestCase, self).setUp()
341
        # Create the following commit tree:
342
        #   3---5
343
        #  /
344
        # 1---2---4
345
        commits = [
346
          make_commit(id=ONE, parents=[], commit_time=111),
347
          make_commit(id=TWO, parents=[ONE], commit_time=222),
348
          make_commit(id=THREE, parents=[ONE], commit_time=333),
349
          make_commit(id=FOUR, parents=[TWO], commit_time=444),
350
          make_commit(id=FIVE, parents=[THREE], commit_time=555),
351
          ]
352
        self._repo = MemoryRepo.init_bare(commits, {})
353
        backend = DictBackend({b'/': self._repo})
354
        self._walker = ProtocolGraphWalker(
355
            TestUploadPackHandler(backend, [b'/', b'host=lolcats'], TestProto()),
356
            self._repo.object_store, self._repo.get_peeled)
357

    
358
    def test_all_wants_satisfied_no_haves(self):
359
        self._walker.set_wants([ONE])
360
        self.assertFalse(self._walker.all_wants_satisfied([]))
361
        self._walker.set_wants([TWO])
362
        self.assertFalse(self._walker.all_wants_satisfied([]))
363
        self._walker.set_wants([THREE])
364
        self.assertFalse(self._walker.all_wants_satisfied([]))
365

    
366
    def test_all_wants_satisfied_have_root(self):
367
        self._walker.set_wants([ONE])
368
        self.assertTrue(self._walker.all_wants_satisfied([ONE]))
369
        self._walker.set_wants([TWO])
370
        self.assertTrue(self._walker.all_wants_satisfied([ONE]))
371
        self._walker.set_wants([THREE])
372
        self.assertTrue(self._walker.all_wants_satisfied([ONE]))
373

    
374
    def test_all_wants_satisfied_have_branch(self):
375
        self._walker.set_wants([TWO])
376
        self.assertTrue(self._walker.all_wants_satisfied([TWO]))
377
        # wrong branch
378
        self._walker.set_wants([THREE])
379
        self.assertFalse(self._walker.all_wants_satisfied([TWO]))
380

    
381
    def test_all_wants_satisfied(self):
382
        self._walker.set_wants([FOUR, FIVE])
383
        # trivial case: wants == haves
384
        self.assertTrue(self._walker.all_wants_satisfied([FOUR, FIVE]))
385
        # cases that require walking the commit tree
386
        self.assertTrue(self._walker.all_wants_satisfied([ONE]))
387
        self.assertFalse(self._walker.all_wants_satisfied([TWO]))
388
        self.assertFalse(self._walker.all_wants_satisfied([THREE]))
389
        self.assertTrue(self._walker.all_wants_satisfied([TWO, THREE]))
390

    
391
    def test_split_proto_line(self):
392
        allowed = (b'want', b'done', None)
393
        self.assertEqual((b'want', ONE),
394
                          _split_proto_line(b'want ' + ONE + b'\n', allowed))
395
        self.assertEqual((b'want', TWO),
396
                          _split_proto_line(b'want ' + TWO + b'\n', allowed))
397
        self.assertRaises(GitProtocolError, _split_proto_line,
398
                          b'want xxxx\n', allowed)
399
        self.assertRaises(UnexpectedCommandError, _split_proto_line,
400
                          b'have ' + THREE + b'\n', allowed)
401
        self.assertRaises(GitProtocolError, _split_proto_line,
402
                          b'foo ' + FOUR + b'\n', allowed)
403
        self.assertRaises(GitProtocolError, _split_proto_line, b'bar', allowed)
404
        self.assertEqual((b'done', None), _split_proto_line(b'done\n', allowed))
405
        self.assertEqual((None, None), _split_proto_line(b'', allowed))
406

    
407
    def test_determine_wants(self):
408
        self._walker.proto.set_output([None])
409
        self.assertEqual([], self._walker.determine_wants({}))
410
        self.assertEqual(None, self._walker.proto.get_received_line())
411

    
412
        self._walker.proto.set_output([
413
          b'want ' + ONE + b' multi_ack',
414
          b'want ' + TWO,
415
          None,
416
          ])
417
        heads = {
418
          b'refs/heads/ref1': ONE,
419
          b'refs/heads/ref2': TWO,
420
          b'refs/heads/ref3': THREE,
421
          }
422
        self._repo.refs._update(heads)
423
        self.assertEqual([ONE, TWO], self._walker.determine_wants(heads))
424

    
425
        self._walker.advertise_refs = True
426
        self.assertEqual([], self._walker.determine_wants(heads))
427
        self._walker.advertise_refs = False
428

    
429
        self._walker.proto.set_output([b'want ' + FOUR + b' multi_ack', None])
430
        self.assertRaises(GitProtocolError, self._walker.determine_wants, heads)
431

    
432
        self._walker.proto.set_output([None])
433
        self.assertEqual([], self._walker.determine_wants(heads))
434

    
435
        self._walker.proto.set_output([b'want ' + ONE + b' multi_ack', b'foo', None])
436
        self.assertRaises(GitProtocolError, self._walker.determine_wants, heads)
437

    
438
        self._walker.proto.set_output([b'want ' + FOUR + b' multi_ack', None])
439
        self.assertRaises(GitProtocolError, self._walker.determine_wants, heads)
440

    
441
    def test_determine_wants_advertisement(self):
442
        self._walker.proto.set_output([None])
443
        # advertise branch tips plus tag
444
        heads = {
445
          b'refs/heads/ref4': FOUR,
446
          b'refs/heads/ref5': FIVE,
447
          b'refs/heads/tag6': SIX,
448
          }
449
        self._repo.refs._update(heads)
450
        self._repo.refs._update_peeled(heads)
451
        self._repo.refs._update_peeled({b'refs/heads/tag6': FIVE})
452
        self._walker.determine_wants(heads)
453
        lines = []
454
        while True:
455
            line = self._walker.proto.get_received_line()
456
            if line is None:
457
                break
458
            # strip capabilities list if present
459
            if b'\x00' in line:
460
                line = line[:line.index(b'\x00')]
461
            lines.append(line.rstrip())
462

    
463
        self.assertEqual([
464
          FOUR + b' refs/heads/ref4',
465
          FIVE + b' refs/heads/ref5',
466
          FIVE + b' refs/heads/tag6^{}',
467
          SIX + b' refs/heads/tag6',
468
          ], sorted(lines))
469

    
470
        # ensure peeled tag was advertised immediately following tag
471
        for i, line in enumerate(lines):
472
            if line.endswith(b' refs/heads/tag6'):
473
                self.assertEqual(FIVE + b' refs/heads/tag6^{}', lines[i+1])
474

    
475
    # TODO: test commit time cutoff
476

    
477
    def _handle_shallow_request(self, lines, heads):
478
        self._walker.proto.set_output(lines + [None])
479
        self._walker._handle_shallow_request(heads)
480

    
481
    def assertReceived(self, expected):
482
        self.assertEqual(
483
          expected, list(iter(self._walker.proto.get_received_line, None)))
484

    
485
    def test_handle_shallow_request_no_client_shallows(self):
486
        self._handle_shallow_request([b'deepen 2\n'], [FOUR, FIVE])
487
        self.assertEqual(set([TWO, THREE]), self._walker.shallow)
488
        self.assertReceived([
489
          b'shallow ' + TWO,
490
          b'shallow ' + THREE,
491
          ])
492

    
493
    def test_handle_shallow_request_no_new_shallows(self):
494
        lines = [
495
          b'shallow ' + TWO + b'\n',
496
          b'shallow ' + THREE + b'\n',
497
          b'deepen 2\n',
498
          ]
499
        self._handle_shallow_request(lines, [FOUR, FIVE])
500
        self.assertEqual(set([TWO, THREE]), self._walker.shallow)
501
        self.assertReceived([])
502

    
503
    def test_handle_shallow_request_unshallows(self):
504
        lines = [
505
          b'shallow ' + TWO + b'\n',
506
          b'deepen 3\n',
507
          ]
508
        self._handle_shallow_request(lines, [FOUR, FIVE])
509
        self.assertEqual(set([ONE]), self._walker.shallow)
510
        self.assertReceived([
511
          b'shallow ' + ONE,
512
          b'unshallow ' + TWO,
513
          # THREE is unshallow but was is not shallow in the client
514
          ])
515

    
516

    
517
class TestProtocolGraphWalker(object):
518

    
519
    def __init__(self):
520
        self.acks = []
521
        self.lines = []
522
        self.wants_satisified = False
523
        self.http_req = None
524
        self.advertise_refs = False
525
        self._impl = None
526
        self.done_required = True
527
        self.done_received = False
528
        self._empty = False
529
        self.pack_sent = False
530

    
531
    def read_proto_line(self, allowed):
532
        command, sha = self.lines.pop(0)
533
        if allowed is not None:
534
            assert command in allowed
535
        return command, sha
536

    
537
    def send_ack(self, sha, ack_type=b''):
538
        self.acks.append((sha, ack_type))
539

    
540
    def send_nak(self):
541
        self.acks.append((None, b'nak'))
542

    
543
    def all_wants_satisfied(self, haves):
544
        if haves:
545
            return self.wants_satisified
546

    
547
    def pop_ack(self):
548
        if not self.acks:
549
            return None
550
        return self.acks.pop(0)
551

    
552
    def handle_done(self):
553
        if not self._impl:
554
            return
555
        # Whether or not PACK is sent after is determined by this, so
556
        # record this value.
557
        self.pack_sent = self._impl.handle_done(self.done_required,
558
            self.done_received)
559
        return self.pack_sent
560

    
561
    def notify_done(self):
562
        self.done_received = True
563

    
564

    
565
class AckGraphWalkerImplTestCase(TestCase):
566
    """Base setup and asserts for AckGraphWalker tests."""
567

    
568
    def setUp(self):
569
        super(AckGraphWalkerImplTestCase, self).setUp()
570
        self._walker = TestProtocolGraphWalker()
571
        self._walker.lines = [
572
          (b'have', TWO),
573
          (b'have', ONE),
574
          (b'have', THREE),
575
          (b'done', None),
576
          ]
577
        self._impl = self.impl_cls(self._walker)
578
        self._walker._impl = self._impl
579

    
580
    def assertNoAck(self):
581
        self.assertEqual(None, self._walker.pop_ack())
582

    
583
    def assertAcks(self, acks):
584
        for sha, ack_type in acks:
585
            self.assertEqual((sha, ack_type), self._walker.pop_ack())
586
        self.assertNoAck()
587

    
588
    def assertAck(self, sha, ack_type=b''):
589
        self.assertAcks([(sha, ack_type)])
590

    
591
    def assertNak(self):
592
        self.assertAck(None, b'nak')
593

    
594
    def assertNextEquals(self, sha):
595
        self.assertEqual(sha, next(self._impl))
596

    
597
    def assertNextEmpty(self):
598
        # This is necessary because of no-done - the assumption that it
599
        # it safe to immediately send out the final ACK is no longer
600
        # true but the test is still needed for it.  TestProtocolWalker
601
        # does implement the handle_done which will determine whether
602
        # the final confirmation can be sent.
603
        self.assertRaises(IndexError, next, self._impl)
604
        self._walker.handle_done()
605

    
606

    
607
class SingleAckGraphWalkerImplTestCase(AckGraphWalkerImplTestCase):
608

    
609
    impl_cls = SingleAckGraphWalkerImpl
610

    
611
    def test_single_ack(self):
612
        self.assertNextEquals(TWO)
613
        self.assertNoAck()
614

    
615
        self.assertNextEquals(ONE)
616
        self._impl.ack(ONE)
617
        self.assertAck(ONE)
618

    
619
        self.assertNextEquals(THREE)
620
        self._impl.ack(THREE)
621
        self.assertNoAck()
622

    
623
        self.assertNextEquals(None)
624
        self.assertNoAck()
625

    
626
    def test_single_ack_flush(self):
627
        # same as ack test but ends with a flush-pkt instead of done
628
        self._walker.lines[-1] = (None, None)
629

    
630
        self.assertNextEquals(TWO)
631
        self.assertNoAck()
632

    
633
        self.assertNextEquals(ONE)
634
        self._impl.ack(ONE)
635
        self.assertAck(ONE)
636

    
637
        self.assertNextEquals(THREE)
638
        self.assertNoAck()
639

    
640
        self.assertNextEquals(None)
641
        self.assertNoAck()
642

    
643
    def test_single_ack_nak(self):
644
        self.assertNextEquals(TWO)
645
        self.assertNoAck()
646

    
647
        self.assertNextEquals(ONE)
648
        self.assertNoAck()
649

    
650
        self.assertNextEquals(THREE)
651
        self.assertNoAck()
652

    
653
        self.assertNextEquals(None)
654
        self.assertNextEmpty()
655
        self.assertNak()
656

    
657
    def test_single_ack_nak_flush(self):
658
        # same as nak test but ends with a flush-pkt instead of done
659
        self._walker.lines[-1] = (None, None)
660

    
661
        self.assertNextEquals(TWO)
662
        self.assertNoAck()
663

    
664
        self.assertNextEquals(ONE)
665
        self.assertNoAck()
666

    
667
        self.assertNextEquals(THREE)
668
        self.assertNoAck()
669

    
670
        self.assertNextEquals(None)
671
        self.assertNextEmpty()
672
        self.assertNak()
673

    
674

    
675
class MultiAckGraphWalkerImplTestCase(AckGraphWalkerImplTestCase):
676

    
677
    impl_cls = MultiAckGraphWalkerImpl
678

    
679
    def test_multi_ack(self):
680
        self.assertNextEquals(TWO)
681
        self.assertNoAck()
682

    
683
        self.assertNextEquals(ONE)
684
        self._impl.ack(ONE)
685
        self.assertAck(ONE, b'continue')
686

    
687
        self.assertNextEquals(THREE)
688
        self._impl.ack(THREE)
689
        self.assertAck(THREE, b'continue')
690

    
691
        self.assertNextEquals(None)
692
        self.assertNextEmpty()
693
        self.assertAck(THREE)
694

    
695
    def test_multi_ack_partial(self):
696
        self.assertNextEquals(TWO)
697
        self.assertNoAck()
698

    
699
        self.assertNextEquals(ONE)
700
        self._impl.ack(ONE)
701
        self.assertAck(ONE, b'continue')
702

    
703
        self.assertNextEquals(THREE)
704
        self.assertNoAck()
705

    
706
        self.assertNextEquals(None)
707
        self.assertNextEmpty()
708
        self.assertAck(ONE)
709

    
710
    def test_multi_ack_flush(self):
711
        self._walker.lines = [
712
          (b'have', TWO),
713
          (None, None),
714
          (b'have', ONE),
715
          (b'have', THREE),
716
          (b'done', None),
717
          ]
718
        self.assertNextEquals(TWO)
719
        self.assertNoAck()
720

    
721
        self.assertNextEquals(ONE)
722
        self.assertNak()  # nak the flush-pkt
723

    
724
        self._impl.ack(ONE)
725
        self.assertAck(ONE, b'continue')
726

    
727
        self.assertNextEquals(THREE)
728
        self._impl.ack(THREE)
729
        self.assertAck(THREE, b'continue')
730

    
731
        self.assertNextEquals(None)
732
        self.assertNextEmpty()
733
        self.assertAck(THREE)
734

    
735
    def test_multi_ack_nak(self):
736
        self.assertNextEquals(TWO)
737
        self.assertNoAck()
738

    
739
        self.assertNextEquals(ONE)
740
        self.assertNoAck()
741

    
742
        self.assertNextEquals(THREE)
743
        self.assertNoAck()
744

    
745
        self.assertNextEquals(None)
746
        self.assertNextEmpty()
747
        self.assertNak()
748

    
749

    
750
class MultiAckDetailedGraphWalkerImplTestCase(AckGraphWalkerImplTestCase):
751

    
752
    impl_cls = MultiAckDetailedGraphWalkerImpl
753

    
754
    def test_multi_ack(self):
755
        self.assertNextEquals(TWO)
756
        self.assertNoAck()
757

    
758
        self.assertNextEquals(ONE)
759
        self._impl.ack(ONE)
760
        self.assertAck(ONE, b'common')
761

    
762
        self.assertNextEquals(THREE)
763
        self._impl.ack(THREE)
764
        self.assertAck(THREE, b'common')
765

    
766
        # done is read.
767
        self._walker.wants_satisified = True
768
        self.assertNextEquals(None)
769
        self._walker.lines.append((None, None))
770
        self.assertNextEmpty()
771
        self.assertAcks([(THREE, b'ready'), (None, b'nak'), (THREE, b'')])
772
        # PACK is sent
773
        self.assertTrue(self._walker.pack_sent)
774

    
775
    def test_multi_ack_nodone(self):
776
        self._walker.done_required = False
777
        self.assertNextEquals(TWO)
778
        self.assertNoAck()
779

    
780
        self.assertNextEquals(ONE)
781
        self._impl.ack(ONE)
782
        self.assertAck(ONE, b'common')
783

    
784
        self.assertNextEquals(THREE)
785
        self._impl.ack(THREE)
786
        self.assertAck(THREE, b'common')
787

    
788
        # done is read.
789
        self._walker.wants_satisified = True
790
        self.assertNextEquals(None)
791
        self._walker.lines.append((None, None))
792
        self.assertNextEmpty()
793
        self.assertAcks([(THREE, b'ready'), (None, b'nak'), (THREE, b'')])
794
        # PACK is sent
795
        self.assertTrue(self._walker.pack_sent)
796

    
797
    def test_multi_ack_flush_end(self):
798
        # transmission ends with a flush-pkt without a done but no-done is
799
        # assumed.
800
        self._walker.lines[-1] = (None, None)
801
        self.assertNextEquals(TWO)
802
        self.assertNoAck()
803

    
804
        self.assertNextEquals(ONE)
805
        self._impl.ack(ONE)
806
        self.assertAck(ONE, b'common')
807

    
808
        self.assertNextEquals(THREE)
809
        self._impl.ack(THREE)
810
        self.assertAck(THREE, b'common')
811

    
812
        # no done is read
813
        self._walker.wants_satisified = True
814
        self.assertNextEmpty()
815
        self.assertAcks([(THREE, b'ready'), (None, b'nak')])
816
        # PACK is NOT sent
817
        self.assertFalse(self._walker.pack_sent)
818

    
819
    def test_multi_ack_flush_end_nodone(self):
820
        # transmission ends with a flush-pkt without a done but no-done is
821
        # assumed.
822
        self._walker.lines[-1] = (None, None)
823
        self._walker.done_required = False
824
        self.assertNextEquals(TWO)
825
        self.assertNoAck()
826

    
827
        self.assertNextEquals(ONE)
828
        self._impl.ack(ONE)
829
        self.assertAck(ONE, b'common')
830

    
831
        self.assertNextEquals(THREE)
832
        self._impl.ack(THREE)
833
        self.assertAck(THREE, b'common')
834

    
835
        # no done is read, but pretend it is (last 'ACK 'commit_id' '')
836
        self._walker.wants_satisified = True
837
        self.assertNextEmpty()
838
        self.assertAcks([(THREE, b'ready'), (None, b'nak'), (THREE, b'')])
839
        # PACK is sent
840
        self.assertTrue(self._walker.pack_sent)
841

    
842
    def test_multi_ack_partial(self):
843
        self.assertNextEquals(TWO)
844
        self.assertNoAck()
845

    
846
        self.assertNextEquals(ONE)
847
        self._impl.ack(ONE)
848
        self.assertAck(ONE, b'common')
849

    
850
        self.assertNextEquals(THREE)
851
        self.assertNoAck()
852

    
853
        self.assertNextEquals(None)
854
        self.assertNextEmpty()
855
        self.assertAck(ONE)
856

    
857
    def test_multi_ack_flush(self):
858
        # same as ack test but contains a flush-pkt in the middle
859
        self._walker.lines = [
860
          (b'have', TWO),
861
          (None, None),
862
          (b'have', ONE),
863
          (b'have', THREE),
864
          (b'done', None),
865
          (None, None),
866
          ]
867
        self.assertNextEquals(TWO)
868
        self.assertNoAck()
869

    
870
        self.assertNextEquals(ONE)
871
        self.assertNak()  # nak the flush-pkt
872

    
873
        self._impl.ack(ONE)
874
        self.assertAck(ONE, b'common')
875

    
876
        self.assertNextEquals(THREE)
877
        self._impl.ack(THREE)
878
        self.assertAck(THREE, b'common')
879

    
880
        self._walker.wants_satisified = True
881
        self.assertNextEquals(None)
882
        self.assertNextEmpty()
883
        self.assertAcks([(THREE, b'ready'), (None, b'nak'), (THREE, b'')])
884

    
885
    def test_multi_ack_nak(self):
886
        self.assertNextEquals(TWO)
887
        self.assertNoAck()
888

    
889
        self.assertNextEquals(ONE)
890
        self.assertNoAck()
891

    
892
        self.assertNextEquals(THREE)
893
        self.assertNoAck()
894

    
895
        # Done is sent here.
896
        self.assertNextEquals(None)
897
        self.assertNextEmpty()
898
        self.assertNak()
899
        self.assertNextEmpty()
900
        self.assertTrue(self._walker.pack_sent)
901

    
902
    def test_multi_ack_nak_nodone(self):
903
        self._walker.done_required = False
904
        self.assertNextEquals(TWO)
905
        self.assertNoAck()
906

    
907
        self.assertNextEquals(ONE)
908
        self.assertNoAck()
909

    
910
        self.assertNextEquals(THREE)
911
        self.assertNoAck()
912

    
913
        # Done is sent here.
914
        self.assertFalse(self._walker.pack_sent)
915
        self.assertNextEquals(None)
916
        self.assertNextEmpty()
917
        self.assertTrue(self._walker.pack_sent)
918
        self.assertNak()
919
        self.assertNextEmpty()
920

    
921
    def test_multi_ack_nak_flush(self):
922
        # same as nak test but contains a flush-pkt in the middle
923
        self._walker.lines = [
924
          (b'have', TWO),
925
          (None, None),
926
          (b'have', ONE),
927
          (b'have', THREE),
928
          (b'done', None),
929
          ]
930
        self.assertNextEquals(TWO)
931
        self.assertNoAck()
932

    
933
        self.assertNextEquals(ONE)
934
        self.assertNak()
935

    
936
        self.assertNextEquals(THREE)
937
        self.assertNoAck()
938

    
939
        self.assertNextEquals(None)
940
        self.assertNextEmpty()
941
        self.assertNak()
942

    
943
    def test_multi_ack_stateless(self):
944
        # transmission ends with a flush-pkt
945
        self._walker.lines[-1] = (None, None)
946
        self._walker.http_req = True
947

    
948
        self.assertNextEquals(TWO)
949
        self.assertNoAck()
950

    
951
        self.assertNextEquals(ONE)
952
        self.assertNoAck()
953

    
954
        self.assertNextEquals(THREE)
955
        self.assertNoAck()
956

    
957
        self.assertFalse(self._walker.pack_sent)
958
        self.assertNextEquals(None)
959
        self.assertNak()
960

    
961
        self.assertNextEmpty()
962
        self.assertNoAck()
963
        self.assertFalse(self._walker.pack_sent)
964

    
965
    def test_multi_ack_stateless_nodone(self):
966
        self._walker.done_required = False
967
        # transmission ends with a flush-pkt
968
        self._walker.lines[-1] = (None, None)
969
        self._walker.http_req = True
970

    
971
        self.assertNextEquals(TWO)
972
        self.assertNoAck()
973

    
974
        self.assertNextEquals(ONE)
975
        self.assertNoAck()
976

    
977
        self.assertNextEquals(THREE)
978
        self.assertNoAck()
979

    
980
        self.assertFalse(self._walker.pack_sent)
981
        self.assertNextEquals(None)
982
        self.assertNak()
983

    
984
        self.assertNextEmpty()
985
        self.assertNoAck()
986
        # PACK will still not be sent.
987
        self.assertFalse(self._walker.pack_sent)
988

    
989

    
990
class FileSystemBackendTests(TestCase):
991
    """Tests for FileSystemBackend."""
992

    
993
    def setUp(self):
994
        super(FileSystemBackendTests, self).setUp()
995
        self.path = tempfile.mkdtemp()
996
        self.addCleanup(shutil.rmtree, self.path)
997
        self.repo = Repo.init(self.path)
998
        if sys.platform == 'win32':
999
            self.backend = FileSystemBackend(self.path[0] + ':' + os.sep)
1000
        else:
1001
            self.backend = FileSystemBackend()
1002

    
1003
    def test_nonexistant(self):
1004
        self.assertRaises(NotGitRepository,
1005
            self.backend.open_repository, "/does/not/exist/unless/foo")
1006

    
1007
    def test_absolute(self):
1008
        repo = self.backend.open_repository(self.path)
1009
        self.assertEqual(
1010
            os.path.normcase(os.path.abspath(repo.path)),
1011
            os.path.normcase(os.path.abspath(self.repo.path)))
1012

    
1013
    def test_child(self):
1014
        self.assertRaises(NotGitRepository,
1015
            self.backend.open_repository, os.path.join(self.path, "foo"))
1016

    
1017
    def test_bad_repo_path(self):
1018
        backend = FileSystemBackend()
1019

    
1020
        self.assertRaises(NotGitRepository,
1021
                          lambda: backend.open_repository('/ups'))
1022

    
1023

    
1024
class DictBackendTests(TestCase):
1025
    """Tests for DictBackend."""
1026

    
1027
    def test_nonexistant(self):
1028
        repo = MemoryRepo.init_bare([], {})
1029
        backend = DictBackend({b'/': repo})
1030
        self.assertRaises(NotGitRepository,
1031
            backend.open_repository, "/does/not/exist/unless/foo")
1032

    
1033
    def test_bad_repo_path(self):
1034
        repo = MemoryRepo.init_bare([], {})
1035
        backend = DictBackend({b'/': repo})
1036

    
1037
        self.assertRaises(NotGitRepository,
1038
                          lambda: backend.open_repository('/ups'))
1039

    
1040

    
1041
class ServeCommandTests(TestCase):
1042
    """Tests for serve_command."""
1043

    
1044
    def setUp(self):
1045
        super(ServeCommandTests, self).setUp()
1046
        self.backend = DictBackend({})
1047

    
1048
    def serve_command(self, handler_cls, args, inf, outf):
1049
        return serve_command(handler_cls, [b"test"] + args, backend=self.backend,
1050
            inf=inf, outf=outf)
1051

    
1052
    def test_receive_pack(self):
1053
        commit = make_commit(id=ONE, parents=[], commit_time=111)
1054
        self.backend.repos[b"/"] = MemoryRepo.init_bare(
1055
            [commit], {b"refs/heads/master": commit.id})
1056
        outf = BytesIO()
1057
        exitcode = self.serve_command(ReceivePackHandler, [b"/"], BytesIO(b"0000"), outf)
1058
        outlines = outf.getvalue().splitlines()
1059
        self.assertEqual(2, len(outlines))
1060
        self.assertEqual(b"1111111111111111111111111111111111111111 refs/heads/master",
1061
            outlines[0][4:].split(b"\x00")[0])
1062
        self.assertEqual(b"0000", outlines[-1])
1063
        self.assertEqual(0, exitcode)
1064

    
1065

    
1066
class UpdateServerInfoTests(TestCase):
1067
    """Tests for update_server_info."""
1068

    
1069
    def setUp(self):
1070
        super(UpdateServerInfoTests, self).setUp()
1071
        self.path = tempfile.mkdtemp()
1072
        self.addCleanup(shutil.rmtree, self.path)
1073
        self.repo = Repo.init(self.path)
1074

    
1075
    def test_empty(self):
1076
        update_server_info(self.repo)
1077
        with open(os.path.join(self.path, ".git", "info", "refs"), 'rb') as f:
1078
            self.assertEqual(b'', f.read())
1079
        with open(os.path.join(self.path, ".git", "objects", "info", "packs"), 'rb') as f:
1080
            self.assertEqual(b'', f.read())
1081

    
1082
    def test_simple(self):
1083
        commit_id = self.repo.do_commit(
1084
            message=b"foo",
1085
            committer=b"Joe Example <joe@example.com>",
1086
            ref=b"refs/heads/foo")
1087
        update_server_info(self.repo)
1088
        with open(os.path.join(self.path, ".git", "info", "refs"), 'rb') as f:
1089
            self.assertEqual(f.read(), commit_id + b'\trefs/heads/foo\n')
1090
        with open(os.path.join(self.path, ".git", "objects", "info", "packs"), 'rb') as f:
1091
            self.assertEqual(f.read(), b'')