1
# Copyright (C) 2006-2010 Canonical Ltd
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.
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.
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
17
"""Tests for the urlutils wrapper."""
22
from bzrlib import osutils, urlutils, win32utils
23
from bzrlib.errors import InvalidURL, InvalidURLJoin, InvalidRebaseURLs
24
from bzrlib.tests import TestCaseInTempDir, TestCase, TestSkipped
27
class TestUrlToPath(TestCase):
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:/'))
39
self.assertEqual('foo', basename('file:///foo'))
40
self.assertEqual('', basename('file:///'))
42
self.assertEqual('foo', basename('http://host/path/to/foo'))
43
self.assertEqual('foo', basename('http://host/path/to/foo/'))
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'))
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/'))
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'))
63
def test_normalize_url_files(self):
64
# Test that local paths are properly normalized
65
normalize_url = urlutils.normalize_url
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:'):]
73
url = url[len('file://'):]
75
self.assertEndsWith(url, expected)
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')
81
# Local paths are assumed to *not* be escaped at all
83
u'uni/\xb5'.encode(osutils.get_user_encoding())
85
# locale cannot handle unicode
88
norm_file('uni/%C2%B5', u'uni/\xb5')
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', "' ;/?:@&=+$,#")
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
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$'))
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'))
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'))
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/ ')
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
132
:param scheme_and_path: The (scheme, path) that should be matched
133
can be None, to indicate it should not match
135
m = urlutils._url_scheme_re.match(url)
136
if scheme_and_path is None:
137
self.assertEqual(None, m)
139
self.assertEqual(scheme_and_path[0], m.group('scheme'))
140
self.assertEqual(scheme_and_path[1], m.group('path'))
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)
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'))
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'))
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|/'))
172
self.assertEqual('file:///', dirname('file:///foo'))
173
self.assertEqual('file:///', dirname('file:///'))
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'))
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/'))
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'))
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'))
214
def test(expected, *args):
215
joined = urlutils.join(*args)
216
self.assertEqual(expected, joined)
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')
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')
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', '')
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,
255
# Joining from a path explicitly under the root.
256
self.assertRaises(InvalidURLJoin, urlutils.join,
257
'http://foo/a', '../../b')
259
def test_joinpath(self):
260
def test(expected, *args):
261
joined = urlutils.joinpath(*args)
262
self.assertEqual(expected, joined)
264
# Test a single element
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')
274
# Test joining to an absolute path
276
test('/foo', '/foo', '.')
277
test('/foo/bar', '/foo', 'bar')
278
test('/', '/foo', '..')
280
# Test joining with an absolute path
281
test('/bar', 'foo', '/bar')
283
# Test joining to a path with a trailing slash
284
test('foo/bar', 'foo/', 'bar')
287
# Cannot go above root
288
self.assertRaises(InvalidURLJoin, urlutils.joinpath, '/', '../baz')
289
self.assertRaises(InvalidURLJoin, urlutils.joinpath, '/', '..')
290
self.assertRaises(InvalidURLJoin, urlutils.joinpath, '/', '/..')
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",
306
self.assertRaises(TypeError, join_segment_parameters_raw,
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", {
326
self.assertEquals("/somedir/path,key1=val2",
327
join_segment_parameters("/somedir/path,key1=val1", {
329
self.assertEquals("/somedir,exist=some/path,key1=val1",
330
join_segment_parameters("/somedir,exist=some/path",
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})
337
def test_function_type(self):
338
if sys.platform == 'win32':
339
self.assertEqual(urlutils._win32_local_path_to_url,
340
urlutils.local_path_to_url)
341
self.assertEqual(urlutils._win32_local_path_from_url,
342
urlutils.local_path_from_url)
344
self.assertEqual(urlutils._posix_local_path_to_url,
345
urlutils.local_path_to_url)
346
self.assertEqual(urlutils._posix_local_path_from_url,
347
urlutils.local_path_from_url)
349
def test_posix_local_path_to_url(self):
350
to_url = urlutils._posix_local_path_to_url
351
self.assertEqual('file:///path/to/foo',
352
to_url('/path/to/foo'))
354
self.assertEqual('file:///path/to/foo%2Cbar',
355
to_url('/path/to/foo,bar'))
358
result = to_url(u'/path/to/r\xe4ksm\xf6rg\xe5s')
360
raise TestSkipped("local encoding cannot handle unicode")
362
self.assertEqual('file:///path/to/r%C3%A4ksm%C3%B6rg%C3%A5s', result)
363
self.assertFalse(isinstance(result, unicode))
365
def test_posix_local_path_from_url(self):
366
from_url = urlutils._posix_local_path_from_url
367
self.assertEqual('/path/to/foo',
368
from_url('file:///path/to/foo'))
369
self.assertEqual('/path/to/foo',
370
from_url('file:///path/to/foo,branch=foo'))
371
self.assertEqual(u'/path/to/r\xe4ksm\xf6rg\xe5s',
372
from_url('file:///path/to/r%C3%A4ksm%C3%B6rg%C3%A5s'))
373
self.assertEqual(u'/path/to/r\xe4ksm\xf6rg\xe5s',
374
from_url('file:///path/to/r%c3%a4ksm%c3%b6rg%c3%a5s'))
375
self.assertEqual(u'/path/to/r\xe4ksm\xf6rg\xe5s',
376
from_url('file://localhost/path/to/r%c3%a4ksm%c3%b6rg%c3%a5s'))
378
self.assertRaises(InvalidURL, from_url, '/path/to/foo')
380
InvalidURL, from_url,
381
'file://remotehost/path/to/r%c3%a4ksm%c3%b6rg%c3%a5s')
383
def test_win32_local_path_to_url(self):
384
to_url = urlutils._win32_local_path_to_url
385
self.assertEqual('file:///C:/path/to/foo',
386
to_url('C:/path/to/foo'))
387
# BOGUS: on win32, ntpath.abspath will strip trailing
388
# whitespace, so this will always fail
389
# Though under linux, it fakes abspath support
390
# and thus will succeed
391
# self.assertEqual('file:///C:/path/to/foo%20',
392
# to_url('C:/path/to/foo '))
393
self.assertEqual('file:///C:/path/to/f%20oo',
394
to_url('C:/path/to/f oo'))
396
self.assertEqual('file:///', to_url('/'))
398
self.assertEqual('file:///C:/path/to/foo%2Cbar',
399
to_url('C:/path/to/foo,bar'))
401
result = to_url(u'd:/path/to/r\xe4ksm\xf6rg\xe5s')
403
raise TestSkipped("local encoding cannot handle unicode")
405
self.assertEqual('file:///D:/path/to/r%C3%A4ksm%C3%B6rg%C3%A5s', result)
406
self.assertFalse(isinstance(result, unicode))
408
def test_win32_unc_path_to_url(self):
409
to_url = urlutils._win32_local_path_to_url
410
self.assertEqual('file://HOST/path',
411
to_url(r'\\HOST\path'))
412
self.assertEqual('file://HOST/path',
413
to_url('//HOST/path'))
416
result = to_url(u'//HOST/path/to/r\xe4ksm\xf6rg\xe5s')
418
raise TestSkipped("local encoding cannot handle unicode")
420
self.assertEqual('file://HOST/path/to/r%C3%A4ksm%C3%B6rg%C3%A5s', result)
421
self.assertFalse(isinstance(result, unicode))
423
def test_win32_local_path_from_url(self):
424
from_url = urlutils._win32_local_path_from_url
425
self.assertEqual('C:/path/to/foo',
426
from_url('file:///C|/path/to/foo'))
427
self.assertEqual(u'D:/path/to/r\xe4ksm\xf6rg\xe5s',
428
from_url('file:///d|/path/to/r%C3%A4ksm%C3%B6rg%C3%A5s'))
429
self.assertEqual(u'D:/path/to/r\xe4ksm\xf6rg\xe5s',
430
from_url('file:///d:/path/to/r%c3%a4ksm%c3%b6rg%c3%a5s'))
431
self.assertEqual('/', from_url('file:///'))
432
self.assertEqual('C:/path/to/foo',
433
from_url('file:///C|/path/to/foo,branch=foo'))
435
self.assertRaises(InvalidURL, from_url, 'file:///C:')
436
self.assertRaises(InvalidURL, from_url, 'file:///c')
437
self.assertRaises(InvalidURL, from_url, '/path/to/foo')
438
# Not a valid _win32 url, no drive letter
439
self.assertRaises(InvalidURL, from_url, 'file:///path/to/foo')
441
def test_win32_unc_path_from_url(self):
442
from_url = urlutils._win32_local_path_from_url
443
self.assertEqual('//HOST/path', from_url('file://HOST/path'))
444
self.assertEqual('//HOST/path',
445
from_url('file://HOST/path,branch=foo'))
446
# despite IE allows 2, 4, 5 and 6 slashes in URL to another machine
447
# we want to use only 2 slashes
448
# Firefox understand only 5 slashes in URL, but it's ugly
449
self.assertRaises(InvalidURL, from_url, 'file:////HOST/path')
450
self.assertRaises(InvalidURL, from_url, 'file://///HOST/path')
451
self.assertRaises(InvalidURL, from_url, 'file://////HOST/path')
452
# check for file://C:/ instead of file:///C:/
453
self.assertRaises(InvalidURL, from_url, 'file://C:/path')
455
def test_win32_extract_drive_letter(self):
456
extract = urlutils._win32_extract_drive_letter
457
self.assertEqual(('file:///C:', '/foo'), extract('file://', '/C:/foo'))
458
self.assertEqual(('file:///d|', '/path'), extract('file://', '/d|/path'))
459
self.assertRaises(InvalidURL, extract, 'file://', '/path')
460
# Root drives without slash treated as invalid, see bug #841322
461
self.assertEqual(('file:///C:', '/'), extract('file://', '/C:/'))
462
self.assertRaises(InvalidURL, extract, 'file://', '/C:')
463
# Invalid without drive separator or following forward slash
464
self.assertRaises(InvalidURL, extract, 'file://', '/C')
465
self.assertRaises(InvalidURL, extract, 'file://', '/C:ool')
467
def test_split(self):
468
# Test bzrlib.urlutils.split()
469
split = urlutils.split
470
if sys.platform == 'win32':
471
self.assertRaises(InvalidURL, split, 'file:///path/to/foo')
472
self.assertEqual(('file:///C|/', 'foo'), split('file:///C|/foo'))
473
self.assertEqual(('file:///C:/', ''), split('file:///C:/'))
475
self.assertEqual(('file:///', 'foo'), split('file:///foo'))
476
self.assertEqual(('file:///', ''), split('file:///'))
478
self.assertEqual(('http://host/path/to', 'foo'), split('http://host/path/to/foo'))
479
self.assertEqual(('http://host/path/to', 'foo'), split('http://host/path/to/foo/'))
480
self.assertEqual(('http://host/path/to/foo', ''),
481
split('http://host/path/to/foo/', exclude_trailing_slash=False))
482
self.assertEqual(('http://host/', 'path'), split('http://host/path'))
483
self.assertEqual(('http://host/', ''), split('http://host/'))
484
self.assertEqual(('http://host', ''), split('http://host'))
485
self.assertEqual(('http:///nohost', 'path'), split('http:///nohost/path'))
487
self.assertEqual(('random+scheme://user:pass@ahost:port/', 'path'),
488
split('random+scheme://user:pass@ahost:port/path'))
489
self.assertEqual(('random+scheme://user:pass@ahost:port/', 'path'),
490
split('random+scheme://user:pass@ahost:port/path/'))
491
self.assertEqual(('random+scheme://user:pass@ahost:port/', ''),
492
split('random+scheme://user:pass@ahost:port/'))
495
self.assertEqual(('path/to', 'foo'), split('path/to/foo'))
496
self.assertEqual(('path/to', 'foo'), split('path/to/foo/'))
497
self.assertEqual(('path/to/foo', ''),
498
split('path/to/foo/', exclude_trailing_slash=False))
499
self.assertEqual(('path/..', 'foo'), split('path/../foo'))
500
self.assertEqual(('../path', 'foo'), split('../path/foo'))
502
def test_split_segment_parameters_raw(self):
503
split_segment_parameters_raw = urlutils.split_segment_parameters_raw
504
self.assertEquals(("/some/path", []),
505
split_segment_parameters_raw("/some/path"))
506
self.assertEquals(("/some/path", ["tip"]),
507
split_segment_parameters_raw("/some/path,tip"))
508
self.assertEquals(("/some,dir/path", ["tip"]),
509
split_segment_parameters_raw("/some,dir/path,tip"))
510
self.assertEquals(("/somedir/path", ["heads%2Ftip"]),
511
split_segment_parameters_raw("/somedir/path,heads%2Ftip"))
512
self.assertEquals(("/somedir/path", ["heads%2Ftip", "bar"]),
513
split_segment_parameters_raw("/somedir/path,heads%2Ftip,bar"))
514
self.assertEquals(("/", ["key1=val1"]),
515
split_segment_parameters_raw(",key1=val1"))
516
self.assertEquals(("foo/", ["key1=val1"]),
517
split_segment_parameters_raw("foo/,key1=val1"))
518
self.assertEquals(("/foo", ["key1=val1"]),
519
split_segment_parameters_raw("foo,key1=val1"))
520
self.assertEquals(("foo/base,la=bla/other/elements", []),
521
split_segment_parameters_raw("foo/base,la=bla/other/elements"))
522
self.assertEquals(("foo/base,la=bla/other/elements", ["a=b"]),
523
split_segment_parameters_raw("foo/base,la=bla/other/elements,a=b"))
525
def test_split_segment_parameters(self):
526
split_segment_parameters = urlutils.split_segment_parameters
527
self.assertEquals(("/some/path", {}),
528
split_segment_parameters("/some/path"))
529
self.assertEquals(("/some/path", {"branch": "tip"}),
530
split_segment_parameters("/some/path,branch=tip"))
531
self.assertEquals(("/some,dir/path", {"branch": "tip"}),
532
split_segment_parameters("/some,dir/path,branch=tip"))
533
self.assertEquals(("/somedir/path", {"ref": "heads%2Ftip"}),
534
split_segment_parameters("/somedir/path,ref=heads%2Ftip"))
535
self.assertEquals(("/somedir/path",
536
{"ref": "heads%2Ftip", "key1": "val1"}),
537
split_segment_parameters(
538
"/somedir/path,ref=heads%2Ftip,key1=val1"))
539
self.assertEquals(("/somedir/path", {"ref": "heads%2F=tip"}),
540
split_segment_parameters("/somedir/path,ref=heads%2F=tip"))
541
self.assertEquals(("/", {"key1": "val1"}),
542
split_segment_parameters(",key1=val1"))
543
self.assertEquals(("foo/", {"key1": "val1"}),
544
split_segment_parameters("foo/,key1=val1"))
545
self.assertEquals(("foo/base,key1=val1/other/elements", {}),
546
split_segment_parameters("foo/base,key1=val1/other/elements"))
547
self.assertEquals(("foo/base,key1=val1/other/elements",
548
{"key2": "val2"}), split_segment_parameters(
549
"foo/base,key1=val1/other/elements,key2=val2"))
551
def test_win32_strip_local_trailing_slash(self):
552
strip = urlutils._win32_strip_local_trailing_slash
553
self.assertEqual('file://', strip('file://'))
554
self.assertEqual('file:///', strip('file:///'))
555
self.assertEqual('file:///C', strip('file:///C'))
556
self.assertEqual('file:///C:', strip('file:///C:'))
557
self.assertEqual('file:///d|', strip('file:///d|'))
558
self.assertEqual('file:///C:/', strip('file:///C:/'))
559
self.assertEqual('file:///C:/a', strip('file:///C:/a/'))
561
def test_strip_trailing_slash(self):
562
sts = urlutils.strip_trailing_slash
563
if sys.platform == 'win32':
564
self.assertEqual('file:///C|/', sts('file:///C|/'))
565
self.assertEqual('file:///C:/foo', sts('file:///C:/foo'))
566
self.assertEqual('file:///C|/foo', sts('file:///C|/foo/'))
568
self.assertEqual('file:///', sts('file:///'))
569
self.assertEqual('file:///foo', sts('file:///foo'))
570
self.assertEqual('file:///foo', sts('file:///foo/'))
572
self.assertEqual('http://host/', sts('http://host/'))
573
self.assertEqual('http://host/foo', sts('http://host/foo'))
574
self.assertEqual('http://host/foo', sts('http://host/foo/'))
576
# No need to fail just because the slash is missing
577
self.assertEqual('http://host', sts('http://host'))
578
# TODO: jam 20060502 Should this raise InvalidURL?
579
self.assertEqual('file://', sts('file://'))
581
self.assertEqual('random+scheme://user:pass@ahost:port/path',
582
sts('random+scheme://user:pass@ahost:port/path'))
583
self.assertEqual('random+scheme://user:pass@ahost:port/path',
584
sts('random+scheme://user:pass@ahost:port/path/'))
585
self.assertEqual('random+scheme://user:pass@ahost:port/',
586
sts('random+scheme://user:pass@ahost:port/'))
588
# Make sure relative paths work too
589
self.assertEqual('path/to/foo', sts('path/to/foo'))
590
self.assertEqual('path/to/foo', sts('path/to/foo/'))
591
self.assertEqual('../to/foo', sts('../to/foo/'))
592
self.assertEqual('path/../foo', sts('path/../foo/'))
594
def test_unescape_for_display_utf8(self):
595
# Test that URLs are converted to nice unicode strings for display
596
def test(expected, url, encoding='utf-8'):
597
disp_url = urlutils.unescape_for_display(url, encoding=encoding)
598
self.assertIsInstance(disp_url, unicode)
599
self.assertEqual(expected, disp_url)
601
test('http://foo', 'http://foo')
602
if sys.platform == 'win32':
603
test('C:/foo/path', 'file:///C|/foo/path')
604
test('C:/foo/path', 'file:///C:/foo/path')
606
test('/foo/path', 'file:///foo/path')
608
test('http://foo/%2Fbaz', 'http://foo/%2Fbaz')
609
test(u'http://host/r\xe4ksm\xf6rg\xe5s',
610
'http://host/r%C3%A4ksm%C3%B6rg%C3%A5s')
612
# Make sure special escaped characters stay escaped
613
test(u'http://host/%3B%2F%3F%3A%40%26%3D%2B%24%2C%23',
614
'http://host/%3B%2F%3F%3A%40%26%3D%2B%24%2C%23')
616
# Can we handle sections that don't have utf-8 encoding?
617
test(u'http://host/%EE%EE%EE/r\xe4ksm\xf6rg\xe5s',
618
'http://host/%EE%EE%EE/r%C3%A4ksm%C3%B6rg%C3%A5s')
620
# Test encoding into output that can handle some characters
621
test(u'http://host/%EE%EE%EE/r\xe4ksm\xf6rg\xe5s',
622
'http://host/%EE%EE%EE/r%C3%A4ksm%C3%B6rg%C3%A5s',
623
encoding='iso-8859-1')
625
# This one can be encoded into utf8
626
test(u'http://host/\u062c\u0648\u062c\u0648',
627
'http://host/%d8%ac%d9%88%d8%ac%d9%88',
630
# This can't be put into 8859-1 and so stays as escapes
631
test(u'http://host/%d8%ac%d9%88%d8%ac%d9%88',
632
'http://host/%d8%ac%d9%88%d8%ac%d9%88',
633
encoding='iso-8859-1')
635
def test_escape(self):
636
self.assertEqual('%25', urlutils.escape('%'))
637
self.assertEqual('%C3%A5', urlutils.escape(u'\xe5'))
638
self.assertFalse(isinstance(urlutils.escape(u'\xe5'), unicode))
640
def test_escape_tildes(self):
641
self.assertEqual('~foo', urlutils.escape('~foo'))
643
def test_unescape(self):
644
self.assertEqual('%', urlutils.unescape('%25'))
645
self.assertEqual(u'\xe5', urlutils.unescape('%C3%A5'))
647
self.assertRaises(InvalidURL, urlutils.unescape, u'\xe5')
648
self.assertRaises(InvalidURL, urlutils.unescape, '\xe5')
649
self.assertRaises(InvalidURL, urlutils.unescape, '%E5')
651
def test_escape_unescape(self):
652
self.assertEqual(u'\xe5', urlutils.unescape(urlutils.escape(u'\xe5')))
653
self.assertEqual('%', urlutils.unescape(urlutils.escape('%')))
655
def test_relative_url(self):
656
def test(expected, base, other):
657
result = urlutils.relative_url(base, other)
658
self.assertEqual(expected, result)
660
test('a', 'http://host/', 'http://host/a')
661
test('http://entirely/different', 'sftp://host/branch',
662
'http://entirely/different')
663
test('../person/feature', 'http://host/branch/mainline',
664
'http://host/branch/person/feature')
665
test('..', 'http://host/branch', 'http://host/')
666
test('http://host2/branch', 'http://host1/branch', 'http://host2/branch')
667
test('.', 'http://host1/branch', 'http://host1/branch')
668
test('../../../branch/2b', 'file:///home/jelmer/foo/bar/2b',
669
'file:///home/jelmer/branch/2b')
670
test('../../branch/2b', 'sftp://host/home/jelmer/bar/2b',
671
'sftp://host/home/jelmer/branch/2b')
672
test('../../branch/feature/%2b', 'http://host/home/jelmer/bar/%2b',
673
'http://host/home/jelmer/branch/feature/%2b')
674
test('../../branch/feature/2b', 'http://host/home/jelmer/bar/2b/',
675
'http://host/home/jelmer/branch/feature/2b')
676
# relative_url should preserve a trailing slash
677
test('../../branch/feature/2b/', 'http://host/home/jelmer/bar/2b/',
678
'http://host/home/jelmer/branch/feature/2b/')
679
test('../../branch/feature/2b/', 'http://host/home/jelmer/bar/2b',
680
'http://host/home/jelmer/branch/feature/2b/')
682
# TODO: treat http://host as http://host/
683
# relative_url is typically called from a branch.base or
684
# transport.base which always ends with a /
685
#test('a', 'http://host', 'http://host/a')
686
test('http://host/a', 'http://host', 'http://host/a')
687
#test('.', 'http://host', 'http://host/')
688
test('http://host/', 'http://host', 'http://host/')
689
#test('.', 'http://host/', 'http://host')
690
test('http://host', 'http://host/', 'http://host')
692
# On Windows file:///C:/path/to and file:///D:/other/path
693
# should not use relative url over the non-existent '/' directory.
694
if sys.platform == 'win32':
696
test('../../other/path',
697
'file:///C:/path/to', 'file:///C:/other/path')
698
#~next two tests is failed, i.e. urlutils.relative_url expects
699
#~to see normalized file URLs?
700
#~test('../../other/path',
701
#~ 'file:///C:/path/to', 'file:///c:/other/path')
702
#~test('../../other/path',
703
#~ 'file:///C:/path/to', 'file:///C|/other/path')
705
# check UNC paths too
706
test('../../other/path',
707
'file://HOST/base/path/to', 'file://HOST/base/other/path')
708
# on different drives
709
test('file:///D:/other/path',
710
'file:///C:/path/to', 'file:///D:/other/path')
711
# TODO: strictly saying in UNC path //HOST/base is full analog
712
# of drive letter for hard disk, and this situation is also
713
# should be exception from rules. [bialix 20071221]
716
class TestCwdToURL(TestCaseInTempDir):
717
"""Test that local_path_to_url works base on the cwd"""
720
# This test will fail if getcwd is not ascii
724
url = urlutils.local_path_to_url('.')
725
self.assertEndsWith(url, '/mytest')
727
def test_non_ascii(self):
728
if win32utils.winver == 'Windows 98':
729
raise TestSkipped('Windows 98 cannot handle unicode filenames')
734
raise TestSkipped('cannot create unicode directory')
738
# On Mac OSX this directory is actually:
739
# u'/dode\u0301' => '/dode\xcc\x81
740
# but we should normalize it back to
741
# u'/dod\xe9' => '/dod\xc3\xa9'
742
url = urlutils.local_path_to_url('.')
743
self.assertEndsWith(url, '/dod%C3%A9')
746
class TestDeriveToLocation(TestCase):
747
"""Test that the mapping of FROM_LOCATION to TO_LOCATION works."""
749
def test_to_locations_derived_from_paths(self):
750
derive = urlutils.derive_to_location
751
self.assertEqual("bar", derive("bar"))
752
self.assertEqual("bar", derive("../bar"))
753
self.assertEqual("bar", derive("/foo/bar"))
754
self.assertEqual("bar", derive("c:/foo/bar"))
755
self.assertEqual("bar", derive("c:bar"))
757
def test_to_locations_derived_from_urls(self):
758
derive = urlutils.derive_to_location
759
self.assertEqual("bar", derive("http://foo/bar"))
760
self.assertEqual("bar", derive("bzr+ssh://foo/bar"))
761
self.assertEqual("foo-bar", derive("lp:foo-bar"))
764
class TestRebaseURL(TestCase):
765
"""Test the behavior of rebase_url."""
767
def test_non_relative(self):
768
result = urlutils.rebase_url('file://foo', 'file://foo',
770
self.assertEqual('file://foo', result)
771
result = urlutils.rebase_url('/foo', 'file://foo',
773
self.assertEqual('/foo', result)
775
def test_different_ports(self):
776
e = self.assertRaises(InvalidRebaseURLs, urlutils.rebase_url,
777
'foo', 'http://bar:80', 'http://bar:81')
778
self.assertEqual(str(e), "URLs differ by more than path:"
779
" 'http://bar:80' and 'http://bar:81'")
781
def test_different_hosts(self):
782
e = self.assertRaises(InvalidRebaseURLs, urlutils.rebase_url,
783
'foo', 'http://bar', 'http://baz')
784
self.assertEqual(str(e), "URLs differ by more than path: 'http://bar'"
787
def test_different_protocol(self):
788
e = self.assertRaises(InvalidRebaseURLs, urlutils.rebase_url,
789
'foo', 'http://bar', 'ftp://bar')
790
self.assertEqual(str(e), "URLs differ by more than path: 'http://bar'"
793
def test_rebase_success(self):
794
self.assertEqual('../bar', urlutils.rebase_url('bar', 'http://baz/',
796
self.assertEqual('qux/bar', urlutils.rebase_url('bar',
797
'http://baz/qux', 'http://baz/'))
798
self.assertEqual('.', urlutils.rebase_url('foo',
799
'http://bar/', 'http://bar/foo/'))
800
self.assertEqual('qux/bar', urlutils.rebase_url('../bar',
801
'http://baz/qux/foo', 'http://baz/'))
803
def test_determine_relative_path(self):
804
self.assertEqual('../../baz/bar',
805
urlutils.determine_relative_path(
806
'/qux/quxx', '/baz/bar'))
807
self.assertEqual('..',
808
urlutils.determine_relative_path(
810
self.assertEqual('baz',
811
urlutils.determine_relative_path(
813
self.assertEqual('.', urlutils.determine_relative_path(
817
class TestParseURL(TestCase):
819
def test_parse_simple(self):
820
parsed = urlutils.parse_url('http://example.com:80/one')
821
self.assertEquals(('http', None, None, 'example.com', 80, '/one'),
825
parsed = urlutils.parse_url('http://[1:2:3::40]/one')
826
self.assertEquals(('http', None, None, '1:2:3::40', None, '/one'),
829
def test_ipv6_port(self):
830
parsed = urlutils.parse_url('http://[1:2:3::40]:80/one')
831
self.assertEquals(('http', None, None, '1:2:3::40', 80, '/one'),
835
class TestURL(TestCase):
837
def test_parse_simple(self):
838
parsed = urlutils.URL.from_string('http://example.com:80/one')
839
self.assertEquals('http', parsed.scheme)
840
self.assertIs(None, parsed.user)
841
self.assertIs(None, parsed.password)
842
self.assertEquals('example.com', parsed.host)
843
self.assertEquals(80, parsed.port)
844
self.assertEquals('/one', parsed.path)
847
parsed = urlutils.URL.from_string('http://[1:2:3::40]/one')
848
self.assertEquals('http', parsed.scheme)
849
self.assertIs(None, parsed.port)
850
self.assertIs(None, parsed.user)
851
self.assertIs(None, parsed.password)
852
self.assertEquals('1:2:3::40', parsed.host)
853
self.assertEquals('/one', parsed.path)
855
def test_ipv6_port(self):
856
parsed = urlutils.URL.from_string('http://[1:2:3::40]:80/one')
857
self.assertEquals('http', parsed.scheme)
858
self.assertEquals('1:2:3::40', parsed.host)
859
self.assertIs(None, parsed.user)
860
self.assertIs(None, parsed.password)
861
self.assertEquals(80, parsed.port)
862
self.assertEquals('/one', parsed.path)
864
def test_quoted(self):
865
parsed = urlutils.URL.from_string(
866
'http://ro%62ey:h%40t@ex%41mple.com:2222/path')
867
self.assertEquals(parsed.quoted_host, 'ex%41mple.com')
868
self.assertEquals(parsed.host, 'exAmple.com')
869
self.assertEquals(parsed.port, 2222)
870
self.assertEquals(parsed.quoted_user, 'ro%62ey')
871
self.assertEquals(parsed.user, 'robey')
872
self.assertEquals(parsed.quoted_password, 'h%40t')
873
self.assertEquals(parsed.password, 'h@t')
874
self.assertEquals(parsed.path, '/path')
877
parsed1 = urlutils.URL.from_string('http://[1:2:3::40]:80/one')
878
parsed2 = urlutils.URL.from_string('http://[1:2:3::40]:80/one')
879
self.assertEquals(parsed1, parsed2)
880
self.assertEquals(parsed1, parsed1)
881
parsed2.path = '/two'
882
self.assertNotEquals(parsed1, parsed2)
885
parsed = urlutils.URL.from_string('http://[1:2:3::40]:80/one')
887
"<URL('http', None, None, '1:2:3::40', 80, '/one')>",
891
parsed = urlutils.URL.from_string('http://[1:2:3::40]:80/one')
892
self.assertEquals('http://[1:2:3::40]:80/one', str(parsed))
894
def test__combine_paths(self):
895
combine = urlutils.URL._combine_paths
896
self.assertEqual('/home/sarah/project/foo',
897
combine('/home/sarah', 'project/foo'))
898
self.assertEqual('/etc',
899
combine('/home/sarah', '../../etc'))
900
self.assertEqual('/etc',
901
combine('/home/sarah', '../../../etc'))
902
self.assertEqual('/etc',
903
combine('/home/sarah', '/etc'))
905
def test_clone(self):
906
url = urlutils.URL.from_string('http://[1:2:3::40]:80/one')
907
url1 = url.clone("two")
908
self.assertEquals("/one/two", url1.path)
909
url2 = url.clone("/two")
910
self.assertEquals("/two", url2.path)
912
self.assertIsNot(url, url3)
913
self.assertEquals(url, url3)