~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_urlutils.py

  • Committer: John Arbash Meinel
  • Date: 2011-01-12 16:45:49 UTC
  • mto: This revision was merged to the branch mainline in revision 5599.
  • Revision ID: john@arbash-meinel.com-20110112164549-6buxl1yr8vosoh1t
Add to What's New

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2006-2010 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
"""Tests for the urlutils wrapper."""
 
18
 
 
19
import os
 
20
import sys
 
21
 
 
22
from bzrlib import osutils, urlutils, win32utils
 
23
from bzrlib.errors import InvalidURL, InvalidURLJoin, InvalidRebaseURLs
 
24
from bzrlib.tests import TestCaseInTempDir, TestCase, TestSkipped
 
25
 
 
26
 
 
27
class TestUrlToPath(TestCase):
 
28
 
 
29
    def test_basename(self):
 
30
        # bzrlib.urlutils.basename
 
31
        # Test bzrlib.urlutils.split()
 
32
        basename = urlutils.basename
 
33
        if sys.platform == 'win32':
 
34
            self.assertRaises(InvalidURL, basename, 'file:///path/to/foo')
 
35
            self.assertEqual('foo', basename('file:///C|/foo'))
 
36
            self.assertEqual('foo', basename('file:///C:/foo'))
 
37
            self.assertEqual('', basename('file:///C:/'))
 
38
        else:
 
39
            self.assertEqual('foo', basename('file:///foo'))
 
40
            self.assertEqual('', basename('file:///'))
 
41
 
 
42
        self.assertEqual('foo', basename('http://host/path/to/foo'))
 
43
        self.assertEqual('foo', basename('http://host/path/to/foo/'))
 
44
        self.assertEqual('',
 
45
            basename('http://host/path/to/foo/', exclude_trailing_slash=False))
 
46
        self.assertEqual('path', basename('http://host/path'))
 
47
        self.assertEqual('', basename('http://host/'))
 
48
        self.assertEqual('', basename('http://host'))
 
49
        self.assertEqual('path', basename('http:///nohost/path'))
 
50
 
 
51
        self.assertEqual('path', basename('random+scheme://user:pass@ahost:port/path'))
 
52
        self.assertEqual('path', basename('random+scheme://user:pass@ahost:port/path/'))
 
53
        self.assertEqual('', basename('random+scheme://user:pass@ahost:port/'))
 
54
 
 
55
        # relative paths
 
56
        self.assertEqual('foo', basename('path/to/foo'))
 
57
        self.assertEqual('foo', basename('path/to/foo/'))
 
58
        self.assertEqual('', basename('path/to/foo/',
 
59
            exclude_trailing_slash=False))
 
60
        self.assertEqual('foo', basename('path/../foo'))
 
61
        self.assertEqual('foo', basename('../path/foo'))
 
62
 
 
63
    def test_normalize_url_files(self):
 
64
        # Test that local paths are properly normalized
 
65
        normalize_url = urlutils.normalize_url
 
66
 
 
67
        def norm_file(expected, path):
 
68
            url = normalize_url(path)
 
69
            self.assertStartsWith(url, 'file:///')
 
70
            if sys.platform == 'win32':
 
71
                url = url[len('file:///C:'):]
 
72
            else:
 
73
                url = url[len('file://'):]
 
74
 
 
75
            self.assertEndsWith(url, expected)
 
76
 
 
77
        norm_file('path/to/foo', 'path/to/foo')
 
78
        norm_file('/path/to/foo', '/path/to/foo')
 
79
        norm_file('path/to/foo', '../path/to/foo')
 
80
 
 
81
        # Local paths are assumed to *not* be escaped at all
 
82
        try:
 
83
            u'uni/\xb5'.encode(osutils.get_user_encoding())
 
84
        except UnicodeError:
 
85
            # locale cannot handle unicode
 
86
            pass
 
87
        else:
 
88
            norm_file('uni/%C2%B5', u'uni/\xb5')
 
89
 
 
90
        norm_file('uni/%25C2%25B5', u'uni/%C2%B5')
 
91
        norm_file('uni/%20b', u'uni/ b')
 
92
        # All the crazy characters get escaped in local paths => file:/// urls
 
93
        # The ' ' character must not be at the end, because on win32
 
94
        # it gets stripped off by ntpath.abspath
 
95
        norm_file('%27%20%3B/%3F%3A%40%26%3D%2B%24%2C%23', "' ;/?:@&=+$,#")
 
96
 
 
97
    def test_normalize_url_hybrid(self):
 
98
        # Anything with a scheme:// should be treated as a hybrid url
 
99
        # which changes what characters get escaped.
 
100
        normalize_url = urlutils.normalize_url
 
101
 
 
102
        eq = self.assertEqual
 
103
        eq('file:///foo/', normalize_url(u'file:///foo/'))
 
104
        eq('file:///foo/%20', normalize_url(u'file:///foo/ '))
 
105
        eq('file:///foo/%20', normalize_url(u'file:///foo/%20'))
 
106
        # Don't escape reserved characters
 
107
        eq('file:///ab_c.d-e/%f:?g&h=i+j;k,L#M$',
 
108
            normalize_url('file:///ab_c.d-e/%f:?g&h=i+j;k,L#M$'))
 
109
        eq('http://ab_c.d-e/%f:?g&h=i+j;k,L#M$',
 
110
            normalize_url('http://ab_c.d-e/%f:?g&h=i+j;k,L#M$'))
 
111
 
 
112
        # Escape unicode characters, but not already escaped chars
 
113
        eq('http://host/ab/%C2%B5/%C2%B5',
 
114
            normalize_url(u'http://host/ab/%C2%B5/\xb5'))
 
115
 
 
116
        # Unescape characters that don't need to be escaped
 
117
        eq('http://host/~bob%2525-._',
 
118
                normalize_url('http://host/%7Ebob%2525%2D%2E%5F'))
 
119
        eq('http://host/~bob%2525-._',
 
120
                normalize_url(u'http://host/%7Ebob%2525%2D%2E%5F'))
 
121
 
 
122
        # Normalize verifies URLs when they are not unicode
 
123
        # (indicating they did not come from the user)
 
124
        self.assertRaises(InvalidURL, normalize_url, 'http://host/\xb5')
 
125
        self.assertRaises(InvalidURL, normalize_url, 'http://host/ ')
 
126
 
 
127
    def test_url_scheme_re(self):
 
128
        # Test paths that may be URLs
 
129
        def test_one(url, scheme_and_path):
 
130
            """Assert that _url_scheme_re correctly matches
 
131
 
 
132
            :param scheme_and_path: The (scheme, path) that should be matched
 
133
                can be None, to indicate it should not match
 
134
            """
 
135
            m = urlutils._url_scheme_re.match(url)
 
136
            if scheme_and_path is None:
 
137
                self.assertEqual(None, m)
 
138
            else:
 
139
                self.assertEqual(scheme_and_path[0], m.group('scheme'))
 
140
                self.assertEqual(scheme_and_path[1], m.group('path'))
 
141
 
 
142
        # Local paths
 
143
        test_one('/path', None)
 
144
        test_one('C:/path', None)
 
145
        test_one('../path/to/foo', None)
 
146
        test_one(u'../path/to/fo\xe5', None)
 
147
 
 
148
        # Real URLS
 
149
        test_one('http://host/path/', ('http', 'host/path/'))
 
150
        test_one('sftp://host/path/to/foo', ('sftp', 'host/path/to/foo'))
 
151
        test_one('file:///usr/bin', ('file', '/usr/bin'))
 
152
        test_one('file:///C:/Windows', ('file', '/C:/Windows'))
 
153
        test_one('file:///C|/Windows', ('file', '/C|/Windows'))
 
154
        test_one(u'readonly+sftp://host/path/\xe5', ('readonly+sftp', u'host/path/\xe5'))
 
155
 
 
156
        # Weird stuff
 
157
        # Can't have slashes or colons in the scheme
 
158
        test_one('/path/to/://foo', None)
 
159
        test_one('scheme:stuff://foo', ('scheme', 'stuff://foo'))
 
160
        # Must have more than one character for scheme
 
161
        test_one('C://foo', None)
 
162
        test_one('ab://foo', ('ab', 'foo'))
 
163
 
 
164
    def test_dirname(self):
 
165
        # Test bzrlib.urlutils.dirname()
 
166
        dirname = urlutils.dirname
 
167
        if sys.platform == 'win32':
 
168
            self.assertRaises(InvalidURL, dirname, 'file:///path/to/foo')
 
169
            self.assertEqual('file:///C|/', dirname('file:///C|/foo'))
 
170
            self.assertEqual('file:///C|/', dirname('file:///C|/'))
 
171
        else:
 
172
            self.assertEqual('file:///', dirname('file:///foo'))
 
173
            self.assertEqual('file:///', dirname('file:///'))
 
174
 
 
175
        self.assertEqual('http://host/path/to', dirname('http://host/path/to/foo'))
 
176
        self.assertEqual('http://host/path/to', dirname('http://host/path/to/foo/'))
 
177
        self.assertEqual('http://host/path/to/foo',
 
178
            dirname('http://host/path/to/foo/', exclude_trailing_slash=False))
 
179
        self.assertEqual('http://host/', dirname('http://host/path'))
 
180
        self.assertEqual('http://host/', dirname('http://host/'))
 
181
        self.assertEqual('http://host', dirname('http://host'))
 
182
        self.assertEqual('http:///nohost', dirname('http:///nohost/path'))
 
183
 
 
184
        self.assertEqual('random+scheme://user:pass@ahost:port/',
 
185
            dirname('random+scheme://user:pass@ahost:port/path'))
 
186
        self.assertEqual('random+scheme://user:pass@ahost:port/',
 
187
            dirname('random+scheme://user:pass@ahost:port/path/'))
 
188
        self.assertEqual('random+scheme://user:pass@ahost:port/',
 
189
            dirname('random+scheme://user:pass@ahost:port/'))
 
190
 
 
191
        # relative paths
 
192
        self.assertEqual('path/to', dirname('path/to/foo'))
 
193
        self.assertEqual('path/to', dirname('path/to/foo/'))
 
194
        self.assertEqual('path/to/foo',
 
195
            dirname('path/to/foo/', exclude_trailing_slash=False))
 
196
        self.assertEqual('path/..', dirname('path/../foo'))
 
197
        self.assertEqual('../path', dirname('../path/foo'))
 
198
    
 
199
    def test_is_url(self):
 
200
        self.assertTrue(urlutils.is_url('http://foo/bar'))
 
201
        self.assertTrue(urlutils.is_url('bzr+ssh://foo/bar'))
 
202
        self.assertTrue(urlutils.is_url('lp:foo/bar'))
 
203
        self.assertTrue(urlutils.is_url('file:///foo/bar'))
 
204
        self.assertFalse(urlutils.is_url(''))
 
205
        self.assertFalse(urlutils.is_url('foo'))
 
206
        self.assertFalse(urlutils.is_url('foo/bar'))
 
207
        self.assertFalse(urlutils.is_url('/foo'))
 
208
        self.assertFalse(urlutils.is_url('/foo/bar'))
 
209
        self.assertFalse(urlutils.is_url('C:/'))
 
210
        self.assertFalse(urlutils.is_url('C:/foo'))
 
211
        self.assertFalse(urlutils.is_url('C:/foo/bar'))
 
212
 
 
213
    def test_join(self):
 
214
        def test(expected, *args):
 
215
            joined = urlutils.join(*args)
 
216
            self.assertEqual(expected, joined)
 
217
 
 
218
        # Test relative path joining
 
219
        test('foo', 'foo') # relative fragment with nothing is preserved.
 
220
        test('foo/bar', 'foo', 'bar')
 
221
        test('http://foo/bar', 'http://foo', 'bar')
 
222
        test('http://foo/bar', 'http://foo', '.', 'bar')
 
223
        test('http://foo/baz', 'http://foo', 'bar', '../baz')
 
224
        test('http://foo/bar/baz', 'http://foo', 'bar/baz')
 
225
        test('http://foo/baz', 'http://foo', 'bar/../baz')
 
226
        test('http://foo/baz', 'http://foo/bar/', '../baz')
 
227
        test('lp:foo/bar', 'lp:foo', 'bar')
 
228
        test('lp:foo/bar/baz', 'lp:foo', 'bar/baz')
 
229
 
 
230
        # Absolute paths
 
231
        test('http://foo', 'http://foo') # abs url with nothing is preserved.
 
232
        test('http://bar', 'http://foo', 'http://bar')
 
233
        test('sftp://bzr/foo', 'http://foo', 'bar', 'sftp://bzr/foo')
 
234
        test('file:///bar', 'foo', 'file:///bar')
 
235
        test('http://bar/', 'http://foo', 'http://bar/')
 
236
        test('http://bar/a', 'http://foo', 'http://bar/a')
 
237
        test('http://bar/a/', 'http://foo', 'http://bar/a/')
 
238
        test('lp:bar', 'http://foo', 'lp:bar')
 
239
        test('lp:bar', 'lp:foo', 'lp:bar')
 
240
        test('file:///stuff', 'lp:foo', 'file:///stuff')
 
241
 
 
242
        # From a base path
 
243
        test('file:///foo', 'file:///', 'foo')
 
244
        test('file:///bar/foo', 'file:///bar/', 'foo')
 
245
        test('http://host/foo', 'http://host/', 'foo')
 
246
        test('http://host/', 'http://host', '')
 
247
 
 
248
        # Invalid joinings
 
249
        # Cannot go above root
 
250
        # Implicitly at root:
 
251
        self.assertRaises(InvalidURLJoin, urlutils.join,
 
252
                'http://foo', '../baz')
 
253
        self.assertRaises(InvalidURLJoin, urlutils.join,
 
254
                'http://foo', '/..')
 
255
        # Joining from a path explicitly under the root.
 
256
        self.assertRaises(InvalidURLJoin, urlutils.join,
 
257
                'http://foo/a', '../../b')
 
258
 
 
259
    def test_joinpath(self):
 
260
        def test(expected, *args):
 
261
            joined = urlutils.joinpath(*args)
 
262
            self.assertEqual(expected, joined)
 
263
 
 
264
        # Test a single element
 
265
        test('foo', 'foo')
 
266
 
 
267
        # Test relative path joining
 
268
        test('foo/bar', 'foo', 'bar')
 
269
        test('foo/bar', 'foo', '.', 'bar')
 
270
        test('foo/baz', 'foo', 'bar', '../baz')
 
271
        test('foo/bar/baz', 'foo', 'bar/baz')
 
272
        test('foo/baz', 'foo', 'bar/../baz')
 
273
 
 
274
        # Test joining to an absolute path
 
275
        test('/foo', '/foo')
 
276
        test('/foo', '/foo', '.')
 
277
        test('/foo/bar', '/foo', 'bar')
 
278
        test('/', '/foo', '..')
 
279
 
 
280
        # Test joining with an absolute path
 
281
        test('/bar', 'foo', '/bar')
 
282
 
 
283
        # Test joining to a path with a trailing slash
 
284
        test('foo/bar', 'foo/', 'bar')
 
285
 
 
286
        # Invalid joinings
 
287
        # Cannot go above root
 
288
        self.assertRaises(InvalidURLJoin, urlutils.joinpath, '/', '../baz')
 
289
        self.assertRaises(InvalidURLJoin, urlutils.joinpath, '/', '..')
 
290
        self.assertRaises(InvalidURLJoin, urlutils.joinpath, '/', '/..')
 
291
 
 
292
    def test_join_segment_parameters_raw(self):
 
293
        join_segment_parameters_raw = urlutils.join_segment_parameters_raw
 
294
        self.assertEquals("/somedir/path", 
 
295
            join_segment_parameters_raw("/somedir/path"))
 
296
        self.assertEquals("/somedir/path,rawdata", 
 
297
            join_segment_parameters_raw("/somedir/path", "rawdata"))
 
298
        self.assertRaises(InvalidURLJoin,
 
299
            join_segment_parameters_raw, "/somedir/path",
 
300
                "rawdata1,rawdata2,rawdata3")
 
301
        self.assertEquals("/somedir/path,bla,bar",
 
302
            join_segment_parameters_raw("/somedir/path", "bla", "bar"))
 
303
        self.assertEquals("/somedir,exist=some/path,bla,bar",
 
304
            join_segment_parameters_raw("/somedir,exist=some/path",
 
305
                "bla", "bar"))
 
306
        self.assertRaises(TypeError, join_segment_parameters_raw, 
 
307
            "/somepath", 42)
 
308
 
 
309
    def test_join_segment_parameters(self):
 
310
        join_segment_parameters = urlutils.join_segment_parameters
 
311
        self.assertEquals("/somedir/path", 
 
312
            join_segment_parameters("/somedir/path", {}))
 
313
        self.assertEquals("/somedir/path,key1=val1", 
 
314
            join_segment_parameters("/somedir/path", {"key1": "val1"}))
 
315
        self.assertRaises(InvalidURLJoin,
 
316
            join_segment_parameters, "/somedir/path",
 
317
            {"branch": "brr,brr,brr"})
 
318
        self.assertRaises(InvalidURLJoin,
 
319
            join_segment_parameters, "/somedir/path", {"key1=val1": "val2"})
 
320
        self.assertEquals("/somedir/path,key1=val1,key2=val2",
 
321
            join_segment_parameters("/somedir/path", {
 
322
                "key1": "val1", "key2": "val2"}))
 
323
        self.assertEquals("/somedir/path,key1=val1,key2=val2",
 
324
            join_segment_parameters("/somedir/path,key1=val1", {
 
325
                "key2": "val2"}))
 
326
        self.assertEquals("/somedir/path,key1=val2",
 
327
            join_segment_parameters("/somedir/path,key1=val1", {
 
328
                "key1": "val2"}))
 
329
        self.assertEquals("/somedir,exist=some/path,key1=val1",
 
330
            join_segment_parameters("/somedir,exist=some/path",
 
331
                {"key1": "val1"}))
 
332
        self.assertEquals("/,key1=val1,key2=val2",
 
333
            join_segment_parameters("/,key1=val1", {"key2": "val2"}))
 
334
        self.assertRaises(TypeError,
 
335
            join_segment_parameters, "/,key1=val1", {"foo": 42})
 
336
 
 
337
    def test_function_type(self):
 
338
        if sys.platform == 'win32':
 
339
            self.assertEqual(urlutils._win32_local_path_to_url, urlutils.local_path_to_url)
 
340
            self.assertEqual(urlutils._win32_local_path_from_url, urlutils.local_path_from_url)
 
341
        else:
 
342
            self.assertEqual(urlutils._posix_local_path_to_url, urlutils.local_path_to_url)
 
343
            self.assertEqual(urlutils._posix_local_path_from_url, urlutils.local_path_from_url)
 
344
 
 
345
    def test_posix_local_path_to_url(self):
 
346
        to_url = urlutils._posix_local_path_to_url
 
347
        self.assertEqual('file:///path/to/foo',
 
348
            to_url('/path/to/foo'))
 
349
 
 
350
        try:
 
351
            result = to_url(u'/path/to/r\xe4ksm\xf6rg\xe5s')
 
352
        except UnicodeError:
 
353
            raise TestSkipped("local encoding cannot handle unicode")
 
354
 
 
355
        self.assertEqual('file:///path/to/r%C3%A4ksm%C3%B6rg%C3%A5s', result)
 
356
        self.assertFalse(isinstance(result, unicode))
 
357
 
 
358
    def test_posix_local_path_from_url(self):
 
359
        from_url = urlutils._posix_local_path_from_url
 
360
        self.assertEqual('/path/to/foo',
 
361
            from_url('file:///path/to/foo'))
 
362
        self.assertEqual(u'/path/to/r\xe4ksm\xf6rg\xe5s',
 
363
            from_url('file:///path/to/r%C3%A4ksm%C3%B6rg%C3%A5s'))
 
364
        self.assertEqual(u'/path/to/r\xe4ksm\xf6rg\xe5s',
 
365
            from_url('file:///path/to/r%c3%a4ksm%c3%b6rg%c3%a5s'))
 
366
        self.assertEqual(u'/path/to/r\xe4ksm\xf6rg\xe5s',
 
367
            from_url('file://localhost/path/to/r%c3%a4ksm%c3%b6rg%c3%a5s'))
 
368
 
 
369
        self.assertRaises(InvalidURL, from_url, '/path/to/foo')
 
370
        self.assertRaises(
 
371
            InvalidURL, from_url,
 
372
            'file://remotehost/path/to/r%c3%a4ksm%c3%b6rg%c3%a5s')
 
373
 
 
374
    def test_win32_local_path_to_url(self):
 
375
        to_url = urlutils._win32_local_path_to_url
 
376
        self.assertEqual('file:///C:/path/to/foo',
 
377
            to_url('C:/path/to/foo'))
 
378
        # BOGUS: on win32, ntpath.abspath will strip trailing
 
379
        #       whitespace, so this will always fail
 
380
        #       Though under linux, it fakes abspath support
 
381
        #       and thus will succeed
 
382
        # self.assertEqual('file:///C:/path/to/foo%20',
 
383
        #     to_url('C:/path/to/foo '))
 
384
        self.assertEqual('file:///C:/path/to/f%20oo',
 
385
            to_url('C:/path/to/f oo'))
 
386
 
 
387
        self.assertEqual('file:///', to_url('/'))
 
388
 
 
389
        try:
 
390
            result = to_url(u'd:/path/to/r\xe4ksm\xf6rg\xe5s')
 
391
        except UnicodeError:
 
392
            raise TestSkipped("local encoding cannot handle unicode")
 
393
 
 
394
        self.assertEqual('file:///D:/path/to/r%C3%A4ksm%C3%B6rg%C3%A5s', result)
 
395
        self.assertFalse(isinstance(result, unicode))
 
396
 
 
397
    def test_win32_unc_path_to_url(self):
 
398
        to_url = urlutils._win32_local_path_to_url
 
399
        self.assertEqual('file://HOST/path',
 
400
            to_url(r'\\HOST\path'))
 
401
        self.assertEqual('file://HOST/path',
 
402
            to_url('//HOST/path'))
 
403
 
 
404
        try:
 
405
            result = to_url(u'//HOST/path/to/r\xe4ksm\xf6rg\xe5s')
 
406
        except UnicodeError:
 
407
            raise TestSkipped("local encoding cannot handle unicode")
 
408
 
 
409
        self.assertEqual('file://HOST/path/to/r%C3%A4ksm%C3%B6rg%C3%A5s', result)
 
410
        self.assertFalse(isinstance(result, unicode))
 
411
 
 
412
    def test_win32_local_path_from_url(self):
 
413
        from_url = urlutils._win32_local_path_from_url
 
414
        self.assertEqual('C:/path/to/foo',
 
415
            from_url('file:///C|/path/to/foo'))
 
416
        self.assertEqual(u'D:/path/to/r\xe4ksm\xf6rg\xe5s',
 
417
            from_url('file:///d|/path/to/r%C3%A4ksm%C3%B6rg%C3%A5s'))
 
418
        self.assertEqual(u'D:/path/to/r\xe4ksm\xf6rg\xe5s',
 
419
            from_url('file:///d:/path/to/r%c3%a4ksm%c3%b6rg%c3%a5s'))
 
420
        self.assertEqual('/', from_url('file:///'))
 
421
 
 
422
        self.assertRaises(InvalidURL, from_url, 'file:///C:')
 
423
        self.assertRaises(InvalidURL, from_url, 'file:///c')
 
424
        self.assertRaises(InvalidURL, from_url, '/path/to/foo')
 
425
        # Not a valid _win32 url, no drive letter
 
426
        self.assertRaises(InvalidURL, from_url, 'file:///path/to/foo')
 
427
 
 
428
    def test_win32_unc_path_from_url(self):
 
429
        from_url = urlutils._win32_local_path_from_url
 
430
        self.assertEqual('//HOST/path', from_url('file://HOST/path'))
 
431
        # despite IE allows 2, 4, 5 and 6 slashes in URL to another machine
 
432
        # we want to use only 2 slashes
 
433
        # Firefox understand only 5 slashes in URL, but it's ugly
 
434
        self.assertRaises(InvalidURL, from_url, 'file:////HOST/path')
 
435
        self.assertRaises(InvalidURL, from_url, 'file://///HOST/path')
 
436
        self.assertRaises(InvalidURL, from_url, 'file://////HOST/path')
 
437
        # check for file://C:/ instead of file:///C:/
 
438
        self.assertRaises(InvalidURL, from_url, 'file://C:/path')
 
439
 
 
440
    def test_win32_extract_drive_letter(self):
 
441
        extract = urlutils._win32_extract_drive_letter
 
442
        self.assertEqual(('file:///C:', '/foo'), extract('file://', '/C:/foo'))
 
443
        self.assertEqual(('file:///d|', '/path'), extract('file://', '/d|/path'))
 
444
        self.assertRaises(InvalidURL, extract, 'file://', '/path')
 
445
 
 
446
    def test_split(self):
 
447
        # Test bzrlib.urlutils.split()
 
448
        split = urlutils.split
 
449
        if sys.platform == 'win32':
 
450
            self.assertRaises(InvalidURL, split, 'file:///path/to/foo')
 
451
            self.assertEqual(('file:///C|/', 'foo'), split('file:///C|/foo'))
 
452
            self.assertEqual(('file:///C:/', ''), split('file:///C:/'))
 
453
        else:
 
454
            self.assertEqual(('file:///', 'foo'), split('file:///foo'))
 
455
            self.assertEqual(('file:///', ''), split('file:///'))
 
456
 
 
457
        self.assertEqual(('http://host/path/to', 'foo'), split('http://host/path/to/foo'))
 
458
        self.assertEqual(('http://host/path/to', 'foo'), split('http://host/path/to/foo/'))
 
459
        self.assertEqual(('http://host/path/to/foo', ''),
 
460
            split('http://host/path/to/foo/', exclude_trailing_slash=False))
 
461
        self.assertEqual(('http://host/', 'path'), split('http://host/path'))
 
462
        self.assertEqual(('http://host/', ''), split('http://host/'))
 
463
        self.assertEqual(('http://host', ''), split('http://host'))
 
464
        self.assertEqual(('http:///nohost', 'path'), split('http:///nohost/path'))
 
465
 
 
466
        self.assertEqual(('random+scheme://user:pass@ahost:port/', 'path'),
 
467
            split('random+scheme://user:pass@ahost:port/path'))
 
468
        self.assertEqual(('random+scheme://user:pass@ahost:port/', 'path'),
 
469
            split('random+scheme://user:pass@ahost:port/path/'))
 
470
        self.assertEqual(('random+scheme://user:pass@ahost:port/', ''),
 
471
            split('random+scheme://user:pass@ahost:port/'))
 
472
 
 
473
        # relative paths
 
474
        self.assertEqual(('path/to', 'foo'), split('path/to/foo'))
 
475
        self.assertEqual(('path/to', 'foo'), split('path/to/foo/'))
 
476
        self.assertEqual(('path/to/foo', ''),
 
477
            split('path/to/foo/', exclude_trailing_slash=False))
 
478
        self.assertEqual(('path/..', 'foo'), split('path/../foo'))
 
479
        self.assertEqual(('../path', 'foo'), split('../path/foo'))
 
480
 
 
481
    def test_split_segment_parameters_raw(self):
 
482
        split_segment_parameters_raw = urlutils.split_segment_parameters_raw
 
483
        self.assertEquals(("/some/path", []),
 
484
            split_segment_parameters_raw("/some/path"))
 
485
        self.assertEquals(("/some/path", ["tip"]),
 
486
            split_segment_parameters_raw("/some/path,tip"))
 
487
        self.assertEquals(("/some,dir/path", ["tip"]),
 
488
            split_segment_parameters_raw("/some,dir/path,tip"))
 
489
        self.assertEquals(("/somedir/path", ["heads%2Ftip"]),
 
490
            split_segment_parameters_raw("/somedir/path,heads%2Ftip"))
 
491
        self.assertEquals(("/somedir/path", ["heads%2Ftip", "bar"]),
 
492
            split_segment_parameters_raw("/somedir/path,heads%2Ftip,bar"))
 
493
        self.assertEquals(("/", ["key1=val1"]),
 
494
            split_segment_parameters_raw(",key1=val1"))
 
495
        self.assertEquals(("foo/", ["key1=val1"]),
 
496
            split_segment_parameters_raw("foo/,key1=val1"))
 
497
        self.assertEquals(("foo/base,la=bla/other/elements", []),
 
498
            split_segment_parameters_raw("foo/base,la=bla/other/elements"))
 
499
        self.assertEquals(("foo/base,la=bla/other/elements", ["a=b"]),
 
500
            split_segment_parameters_raw("foo/base,la=bla/other/elements,a=b"))
 
501
 
 
502
    def test_split_segment_parameters(self):
 
503
        split_segment_parameters = urlutils.split_segment_parameters
 
504
        self.assertEquals(("/some/path", {}),
 
505
            split_segment_parameters("/some/path"))
 
506
        self.assertEquals(("/some/path", {"branch": "tip"}),
 
507
            split_segment_parameters("/some/path,branch=tip"))
 
508
        self.assertEquals(("/some,dir/path", {"branch": "tip"}),
 
509
            split_segment_parameters("/some,dir/path,branch=tip"))
 
510
        self.assertEquals(("/somedir/path", {"ref": "heads%2Ftip"}),
 
511
            split_segment_parameters("/somedir/path,ref=heads%2Ftip"))
 
512
        self.assertEquals(("/somedir/path",
 
513
            {"ref": "heads%2Ftip", "key1": "val1"}),
 
514
            split_segment_parameters(
 
515
                "/somedir/path,ref=heads%2Ftip,key1=val1"))
 
516
        self.assertEquals(("/somedir/path", {"ref": "heads%2F=tip"}),
 
517
            split_segment_parameters("/somedir/path,ref=heads%2F=tip"))
 
518
        self.assertEquals(("/", {"key1": "val1"}),
 
519
            split_segment_parameters(",key1=val1"))
 
520
        self.assertEquals(("foo/", {"key1": "val1"}),
 
521
            split_segment_parameters("foo/,key1=val1"))
 
522
        self.assertEquals(("foo/base,key1=val1/other/elements", {}),
 
523
            split_segment_parameters("foo/base,key1=val1/other/elements"))
 
524
        self.assertEquals(("foo/base,key1=val1/other/elements",
 
525
            {"key2": "val2"}), split_segment_parameters(
 
526
                "foo/base,key1=val1/other/elements,key2=val2"))
 
527
 
 
528
    def test_win32_strip_local_trailing_slash(self):
 
529
        strip = urlutils._win32_strip_local_trailing_slash
 
530
        self.assertEqual('file://', strip('file://'))
 
531
        self.assertEqual('file:///', strip('file:///'))
 
532
        self.assertEqual('file:///C', strip('file:///C'))
 
533
        self.assertEqual('file:///C:', strip('file:///C:'))
 
534
        self.assertEqual('file:///d|', strip('file:///d|'))
 
535
        self.assertEqual('file:///C:/', strip('file:///C:/'))
 
536
        self.assertEqual('file:///C:/a', strip('file:///C:/a/'))
 
537
 
 
538
    def test_strip_trailing_slash(self):
 
539
        sts = urlutils.strip_trailing_slash
 
540
        if sys.platform == 'win32':
 
541
            self.assertEqual('file:///C|/', sts('file:///C|/'))
 
542
            self.assertEqual('file:///C:/foo', sts('file:///C:/foo'))
 
543
            self.assertEqual('file:///C|/foo', sts('file:///C|/foo/'))
 
544
        else:
 
545
            self.assertEqual('file:///', sts('file:///'))
 
546
            self.assertEqual('file:///foo', sts('file:///foo'))
 
547
            self.assertEqual('file:///foo', sts('file:///foo/'))
 
548
 
 
549
        self.assertEqual('http://host/', sts('http://host/'))
 
550
        self.assertEqual('http://host/foo', sts('http://host/foo'))
 
551
        self.assertEqual('http://host/foo', sts('http://host/foo/'))
 
552
 
 
553
        # No need to fail just because the slash is missing
 
554
        self.assertEqual('http://host', sts('http://host'))
 
555
        # TODO: jam 20060502 Should this raise InvalidURL?
 
556
        self.assertEqual('file://', sts('file://'))
 
557
 
 
558
        self.assertEqual('random+scheme://user:pass@ahost:port/path',
 
559
            sts('random+scheme://user:pass@ahost:port/path'))
 
560
        self.assertEqual('random+scheme://user:pass@ahost:port/path',
 
561
            sts('random+scheme://user:pass@ahost:port/path/'))
 
562
        self.assertEqual('random+scheme://user:pass@ahost:port/',
 
563
            sts('random+scheme://user:pass@ahost:port/'))
 
564
 
 
565
        # Make sure relative paths work too
 
566
        self.assertEqual('path/to/foo', sts('path/to/foo'))
 
567
        self.assertEqual('path/to/foo', sts('path/to/foo/'))
 
568
        self.assertEqual('../to/foo', sts('../to/foo/'))
 
569
        self.assertEqual('path/../foo', sts('path/../foo/'))
 
570
 
 
571
    def test_unescape_for_display_utf8(self):
 
572
        # Test that URLs are converted to nice unicode strings for display
 
573
        def test(expected, url, encoding='utf-8'):
 
574
            disp_url = urlutils.unescape_for_display(url, encoding=encoding)
 
575
            self.assertIsInstance(disp_url, unicode)
 
576
            self.assertEqual(expected, disp_url)
 
577
 
 
578
        test('http://foo', 'http://foo')
 
579
        if sys.platform == 'win32':
 
580
            test('C:/foo/path', 'file:///C|/foo/path')
 
581
            test('C:/foo/path', 'file:///C:/foo/path')
 
582
        else:
 
583
            test('/foo/path', 'file:///foo/path')
 
584
 
 
585
        test('http://foo/%2Fbaz', 'http://foo/%2Fbaz')
 
586
        test(u'http://host/r\xe4ksm\xf6rg\xe5s',
 
587
             'http://host/r%C3%A4ksm%C3%B6rg%C3%A5s')
 
588
 
 
589
        # Make sure special escaped characters stay escaped
 
590
        test(u'http://host/%3B%2F%3F%3A%40%26%3D%2B%24%2C%23',
 
591
             'http://host/%3B%2F%3F%3A%40%26%3D%2B%24%2C%23')
 
592
 
 
593
        # Can we handle sections that don't have utf-8 encoding?
 
594
        test(u'http://host/%EE%EE%EE/r\xe4ksm\xf6rg\xe5s',
 
595
             'http://host/%EE%EE%EE/r%C3%A4ksm%C3%B6rg%C3%A5s')
 
596
 
 
597
        # Test encoding into output that can handle some characters
 
598
        test(u'http://host/%EE%EE%EE/r\xe4ksm\xf6rg\xe5s',
 
599
             'http://host/%EE%EE%EE/r%C3%A4ksm%C3%B6rg%C3%A5s',
 
600
             encoding='iso-8859-1')
 
601
 
 
602
        # This one can be encoded into utf8
 
603
        test(u'http://host/\u062c\u0648\u062c\u0648',
 
604
             'http://host/%d8%ac%d9%88%d8%ac%d9%88',
 
605
             encoding='utf-8')
 
606
 
 
607
        # This can't be put into 8859-1 and so stays as escapes
 
608
        test(u'http://host/%d8%ac%d9%88%d8%ac%d9%88',
 
609
             'http://host/%d8%ac%d9%88%d8%ac%d9%88',
 
610
             encoding='iso-8859-1')
 
611
 
 
612
    def test_escape(self):
 
613
        self.assertEqual('%25', urlutils.escape('%'))
 
614
        self.assertEqual('%C3%A5', urlutils.escape(u'\xe5'))
 
615
        self.assertFalse(isinstance(urlutils.escape(u'\xe5'), unicode))
 
616
 
 
617
    def test_escape_tildes(self):
 
618
        self.assertEqual('~foo', urlutils.escape('~foo'))
 
619
 
 
620
    def test_unescape(self):
 
621
        self.assertEqual('%', urlutils.unescape('%25'))
 
622
        self.assertEqual(u'\xe5', urlutils.unescape('%C3%A5'))
 
623
 
 
624
        self.assertRaises(InvalidURL, urlutils.unescape, u'\xe5')
 
625
        self.assertRaises(InvalidURL, urlutils.unescape, '\xe5')
 
626
        self.assertRaises(InvalidURL, urlutils.unescape, '%E5')
 
627
 
 
628
    def test_escape_unescape(self):
 
629
        self.assertEqual(u'\xe5', urlutils.unescape(urlutils.escape(u'\xe5')))
 
630
        self.assertEqual('%', urlutils.unescape(urlutils.escape('%')))
 
631
 
 
632
    def test_relative_url(self):
 
633
        def test(expected, base, other):
 
634
            result = urlutils.relative_url(base, other)
 
635
            self.assertEqual(expected, result)
 
636
 
 
637
        test('a', 'http://host/', 'http://host/a')
 
638
        test('http://entirely/different', 'sftp://host/branch',
 
639
                    'http://entirely/different')
 
640
        test('../person/feature', 'http://host/branch/mainline',
 
641
                    'http://host/branch/person/feature')
 
642
        test('..', 'http://host/branch', 'http://host/')
 
643
        test('http://host2/branch', 'http://host1/branch', 'http://host2/branch')
 
644
        test('.', 'http://host1/branch', 'http://host1/branch')
 
645
        test('../../../branch/2b', 'file:///home/jelmer/foo/bar/2b',
 
646
                    'file:///home/jelmer/branch/2b')
 
647
        test('../../branch/2b', 'sftp://host/home/jelmer/bar/2b',
 
648
                    'sftp://host/home/jelmer/branch/2b')
 
649
        test('../../branch/feature/%2b', 'http://host/home/jelmer/bar/%2b',
 
650
                    'http://host/home/jelmer/branch/feature/%2b')
 
651
        test('../../branch/feature/2b', 'http://host/home/jelmer/bar/2b/',
 
652
                    'http://host/home/jelmer/branch/feature/2b')
 
653
        # relative_url should preserve a trailing slash
 
654
        test('../../branch/feature/2b/', 'http://host/home/jelmer/bar/2b/',
 
655
                    'http://host/home/jelmer/branch/feature/2b/')
 
656
        test('../../branch/feature/2b/', 'http://host/home/jelmer/bar/2b',
 
657
                    'http://host/home/jelmer/branch/feature/2b/')
 
658
 
 
659
        # TODO: treat http://host as http://host/
 
660
        #       relative_url is typically called from a branch.base or
 
661
        #       transport.base which always ends with a /
 
662
        #test('a', 'http://host', 'http://host/a')
 
663
        test('http://host/a', 'http://host', 'http://host/a')
 
664
        #test('.', 'http://host', 'http://host/')
 
665
        test('http://host/', 'http://host', 'http://host/')
 
666
        #test('.', 'http://host/', 'http://host')
 
667
        test('http://host', 'http://host/', 'http://host')
 
668
 
 
669
        # On Windows file:///C:/path/to and file:///D:/other/path
 
670
        # should not use relative url over the non-existent '/' directory.
 
671
        if sys.platform == 'win32':
 
672
            # on the same drive
 
673
            test('../../other/path',
 
674
                'file:///C:/path/to', 'file:///C:/other/path')
 
675
            #~next two tests is failed, i.e. urlutils.relative_url expects
 
676
            #~to see normalized file URLs?
 
677
            #~test('../../other/path',
 
678
            #~    'file:///C:/path/to', 'file:///c:/other/path')
 
679
            #~test('../../other/path',
 
680
            #~    'file:///C:/path/to', 'file:///C|/other/path')
 
681
 
 
682
            # check UNC paths too
 
683
            test('../../other/path',
 
684
                'file://HOST/base/path/to', 'file://HOST/base/other/path')
 
685
            # on different drives
 
686
            test('file:///D:/other/path',
 
687
                'file:///C:/path/to', 'file:///D:/other/path')
 
688
            # TODO: strictly saying in UNC path //HOST/base is full analog
 
689
            # of drive letter for hard disk, and this situation is also
 
690
            # should be exception from rules. [bialix 20071221]
 
691
 
 
692
 
 
693
class TestCwdToURL(TestCaseInTempDir):
 
694
    """Test that local_path_to_url works base on the cwd"""
 
695
 
 
696
    def test_dot(self):
 
697
        # This test will fail if getcwd is not ascii
 
698
        os.mkdir('mytest')
 
699
        os.chdir('mytest')
 
700
 
 
701
        url = urlutils.local_path_to_url('.')
 
702
        self.assertEndsWith(url, '/mytest')
 
703
 
 
704
    def test_non_ascii(self):
 
705
        if win32utils.winver == 'Windows 98':
 
706
            raise TestSkipped('Windows 98 cannot handle unicode filenames')
 
707
 
 
708
        try:
 
709
            os.mkdir(u'dod\xe9')
 
710
        except UnicodeError:
 
711
            raise TestSkipped('cannot create unicode directory')
 
712
 
 
713
        os.chdir(u'dod\xe9')
 
714
 
 
715
        # On Mac OSX this directory is actually:
 
716
        #   u'/dode\u0301' => '/dode\xcc\x81
 
717
        # but we should normalize it back to
 
718
        #   u'/dod\xe9' => '/dod\xc3\xa9'
 
719
        url = urlutils.local_path_to_url('.')
 
720
        self.assertEndsWith(url, '/dod%C3%A9')
 
721
 
 
722
 
 
723
class TestDeriveToLocation(TestCase):
 
724
    """Test that the mapping of FROM_LOCATION to TO_LOCATION works."""
 
725
 
 
726
    def test_to_locations_derived_from_paths(self):
 
727
        derive = urlutils.derive_to_location
 
728
        self.assertEqual("bar", derive("bar"))
 
729
        self.assertEqual("bar", derive("../bar"))
 
730
        self.assertEqual("bar", derive("/foo/bar"))
 
731
        self.assertEqual("bar", derive("c:/foo/bar"))
 
732
        self.assertEqual("bar", derive("c:bar"))
 
733
 
 
734
    def test_to_locations_derived_from_urls(self):
 
735
        derive = urlutils.derive_to_location
 
736
        self.assertEqual("bar", derive("http://foo/bar"))
 
737
        self.assertEqual("bar", derive("bzr+ssh://foo/bar"))
 
738
        self.assertEqual("foo-bar", derive("lp:foo-bar"))
 
739
 
 
740
 
 
741
class TestRebaseURL(TestCase):
 
742
    """Test the behavior of rebase_url."""
 
743
 
 
744
    def test_non_relative(self):
 
745
        result = urlutils.rebase_url('file://foo', 'file://foo',
 
746
                                     'file://foo/bar')
 
747
        self.assertEqual('file://foo', result)
 
748
        result = urlutils.rebase_url('/foo', 'file://foo',
 
749
                                     'file://foo/bar')
 
750
        self.assertEqual('/foo', result)
 
751
 
 
752
    def test_different_ports(self):
 
753
        e = self.assertRaises(InvalidRebaseURLs, urlutils.rebase_url,
 
754
                              'foo', 'http://bar:80', 'http://bar:81')
 
755
        self.assertEqual(str(e), "URLs differ by more than path:"
 
756
                         " 'http://bar:80' and 'http://bar:81'")
 
757
 
 
758
    def test_different_hosts(self):
 
759
        e = self.assertRaises(InvalidRebaseURLs, urlutils.rebase_url,
 
760
                              'foo', 'http://bar', 'http://baz')
 
761
        self.assertEqual(str(e), "URLs differ by more than path: 'http://bar'"
 
762
                         " and 'http://baz'")
 
763
 
 
764
    def test_different_protocol(self):
 
765
        e = self.assertRaises(InvalidRebaseURLs, urlutils.rebase_url,
 
766
                              'foo', 'http://bar', 'ftp://bar')
 
767
        self.assertEqual(str(e), "URLs differ by more than path: 'http://bar'"
 
768
                         " and 'ftp://bar'")
 
769
 
 
770
    def test_rebase_success(self):
 
771
        self.assertEqual('../bar', urlutils.rebase_url('bar', 'http://baz/',
 
772
                         'http://baz/qux'))
 
773
        self.assertEqual('qux/bar', urlutils.rebase_url('bar',
 
774
                         'http://baz/qux', 'http://baz/'))
 
775
        self.assertEqual('.', urlutils.rebase_url('foo',
 
776
                         'http://bar/', 'http://bar/foo/'))
 
777
        self.assertEqual('qux/bar', urlutils.rebase_url('../bar',
 
778
                         'http://baz/qux/foo', 'http://baz/'))
 
779
 
 
780
    def test_determine_relative_path(self):
 
781
        self.assertEqual('../../baz/bar',
 
782
                         urlutils.determine_relative_path(
 
783
                         '/qux/quxx', '/baz/bar'))
 
784
        self.assertEqual('..',
 
785
                         urlutils.determine_relative_path(
 
786
                         '/bar/baz', '/bar'))
 
787
        self.assertEqual('baz',
 
788
                         urlutils.determine_relative_path(
 
789
                         '/bar', '/bar/baz'))
 
790
        self.assertEqual('.', urlutils.determine_relative_path(
 
791
                         '/bar', '/bar'))
 
792
 
 
793
 
 
794
class TestParseURL(TestCase):
 
795
 
 
796
    def test_parse_url(self):
 
797
        self.assertEqual(urlutils.parse_url('http://example.com:80/one'),
 
798
            ('http', None, None, 'example.com', 80, '/one'))
 
799
        self.assertEqual(urlutils.parse_url('http://[1:2:3::40]/one'),
 
800
                ('http', None, None, '1:2:3::40', None, '/one'))
 
801
        self.assertEqual(urlutils.parse_url('http://[1:2:3::40]:80/one'),
 
802
                ('http', None, None, '1:2:3::40', 80, '/one'))