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, urlutils.local_path_to_url)
340
self.assertEqual(urlutils._win32_local_path_from_url, urlutils.local_path_from_url)
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)
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'))
351
result = to_url(u'/path/to/r\xe4ksm\xf6rg\xe5s')
353
raise TestSkipped("local encoding cannot handle unicode")
355
self.assertEqual('file:///path/to/r%C3%A4ksm%C3%B6rg%C3%A5s', result)
356
self.assertFalse(isinstance(result, unicode))
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'))
369
self.assertRaises(InvalidURL, from_url, '/path/to/foo')
371
InvalidURL, from_url,
372
'file://remotehost/path/to/r%c3%a4ksm%c3%b6rg%c3%a5s')
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'))
387
self.assertEqual('file:///', to_url('/'))
390
result = to_url(u'd:/path/to/r\xe4ksm\xf6rg\xe5s')
392
raise TestSkipped("local encoding cannot handle unicode")
394
self.assertEqual('file:///D:/path/to/r%C3%A4ksm%C3%B6rg%C3%A5s', result)
395
self.assertFalse(isinstance(result, unicode))
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'))
405
result = to_url(u'//HOST/path/to/r\xe4ksm\xf6rg\xe5s')
407
raise TestSkipped("local encoding cannot handle unicode")
409
self.assertEqual('file://HOST/path/to/r%C3%A4ksm%C3%B6rg%C3%A5s', result)
410
self.assertFalse(isinstance(result, unicode))
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:///'))
422
self.assertRaises(InvalidURL, from_url, '/path/to/foo')
423
# Not a valid _win32 url, no drive letter
424
self.assertRaises(InvalidURL, from_url, 'file:///path/to/foo')
426
def test_win32_unc_path_from_url(self):
427
from_url = urlutils._win32_local_path_from_url
428
self.assertEqual('//HOST/path', from_url('file://HOST/path'))
429
# despite IE allows 2, 4, 5 and 6 slashes in URL to another machine
430
# we want to use only 2 slashes
431
# Firefox understand only 5 slashes in URL, but it's ugly
432
self.assertRaises(InvalidURL, from_url, 'file:////HOST/path')
433
self.assertRaises(InvalidURL, from_url, 'file://///HOST/path')
434
self.assertRaises(InvalidURL, from_url, 'file://////HOST/path')
435
# check for file://C:/ instead of file:///C:/
436
self.assertRaises(InvalidURL, from_url, 'file://C:/path')
438
def test_win32_extract_drive_letter(self):
439
extract = urlutils._win32_extract_drive_letter
440
self.assertEqual(('file:///C:', '/foo'), extract('file://', '/C:/foo'))
441
self.assertEqual(('file:///d|', '/path'), extract('file://', '/d|/path'))
442
self.assertRaises(InvalidURL, extract, 'file://', '/path')
444
def test_split(self):
445
# Test bzrlib.urlutils.split()
446
split = urlutils.split
447
if sys.platform == 'win32':
448
self.assertRaises(InvalidURL, split, 'file:///path/to/foo')
449
self.assertEqual(('file:///C|/', 'foo'), split('file:///C|/foo'))
450
self.assertEqual(('file:///C:/', ''), split('file:///C:/'))
452
self.assertEqual(('file:///', 'foo'), split('file:///foo'))
453
self.assertEqual(('file:///', ''), split('file:///'))
455
self.assertEqual(('http://host/path/to', 'foo'), split('http://host/path/to/foo'))
456
self.assertEqual(('http://host/path/to', 'foo'), split('http://host/path/to/foo/'))
457
self.assertEqual(('http://host/path/to/foo', ''),
458
split('http://host/path/to/foo/', exclude_trailing_slash=False))
459
self.assertEqual(('http://host/', 'path'), split('http://host/path'))
460
self.assertEqual(('http://host/', ''), split('http://host/'))
461
self.assertEqual(('http://host', ''), split('http://host'))
462
self.assertEqual(('http:///nohost', 'path'), split('http:///nohost/path'))
464
self.assertEqual(('random+scheme://user:pass@ahost:port/', 'path'),
465
split('random+scheme://user:pass@ahost:port/path'))
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/', ''),
469
split('random+scheme://user:pass@ahost:port/'))
472
self.assertEqual(('path/to', 'foo'), split('path/to/foo'))
473
self.assertEqual(('path/to', 'foo'), split('path/to/foo/'))
474
self.assertEqual(('path/to/foo', ''),
475
split('path/to/foo/', exclude_trailing_slash=False))
476
self.assertEqual(('path/..', 'foo'), split('path/../foo'))
477
self.assertEqual(('../path', 'foo'), split('../path/foo'))
479
def test_split_segment_parameters_raw(self):
480
split_segment_parameters_raw = urlutils.split_segment_parameters_raw
481
self.assertEquals(("/some/path", []),
482
split_segment_parameters_raw("/some/path"))
483
self.assertEquals(("/some/path", ["tip"]),
484
split_segment_parameters_raw("/some/path,tip"))
485
self.assertEquals(("/some,dir/path", ["tip"]),
486
split_segment_parameters_raw("/some,dir/path,tip"))
487
self.assertEquals(("/somedir/path", ["heads%2Ftip"]),
488
split_segment_parameters_raw("/somedir/path,heads%2Ftip"))
489
self.assertEquals(("/somedir/path", ["heads%2Ftip", "bar"]),
490
split_segment_parameters_raw("/somedir/path,heads%2Ftip,bar"))
491
self.assertEquals(("/", ["key1=val1"]),
492
split_segment_parameters_raw(",key1=val1"))
493
self.assertEquals(("foo/", ["key1=val1"]),
494
split_segment_parameters_raw("foo/,key1=val1"))
495
self.assertEquals(("foo/base,la=bla/other/elements", []),
496
split_segment_parameters_raw("foo/base,la=bla/other/elements"))
497
self.assertEquals(("foo/base,la=bla/other/elements", ["a=b"]),
498
split_segment_parameters_raw("foo/base,la=bla/other/elements,a=b"))
500
def test_split_segment_parameters(self):
501
split_segment_parameters = urlutils.split_segment_parameters
502
self.assertEquals(("/some/path", {}),
503
split_segment_parameters("/some/path"))
504
self.assertEquals(("/some/path", {"branch": "tip"}),
505
split_segment_parameters("/some/path,branch=tip"))
506
self.assertEquals(("/some,dir/path", {"branch": "tip"}),
507
split_segment_parameters("/some,dir/path,branch=tip"))
508
self.assertEquals(("/somedir/path", {"ref": "heads%2Ftip"}),
509
split_segment_parameters("/somedir/path,ref=heads%2Ftip"))
510
self.assertEquals(("/somedir/path",
511
{"ref": "heads%2Ftip", "key1": "val1"}),
512
split_segment_parameters(
513
"/somedir/path,ref=heads%2Ftip,key1=val1"))
514
self.assertEquals(("/somedir/path", {"ref": "heads%2F=tip"}),
515
split_segment_parameters("/somedir/path,ref=heads%2F=tip"))
516
self.assertEquals(("/", {"key1": "val1"}),
517
split_segment_parameters(",key1=val1"))
518
self.assertEquals(("foo/", {"key1": "val1"}),
519
split_segment_parameters("foo/,key1=val1"))
520
self.assertEquals(("foo/base,key1=val1/other/elements", {}),
521
split_segment_parameters("foo/base,key1=val1/other/elements"))
522
self.assertEquals(("foo/base,key1=val1/other/elements",
523
{"key2": "val2"}), split_segment_parameters(
524
"foo/base,key1=val1/other/elements,key2=val2"))
526
def test_win32_strip_local_trailing_slash(self):
527
strip = urlutils._win32_strip_local_trailing_slash
528
self.assertEqual('file://', strip('file://'))
529
self.assertEqual('file:///', strip('file:///'))
530
self.assertEqual('file:///C', strip('file:///C'))
531
self.assertEqual('file:///C:', strip('file:///C:'))
532
self.assertEqual('file:///d|', strip('file:///d|'))
533
self.assertEqual('file:///C:/', strip('file:///C:/'))
534
self.assertEqual('file:///C:/a', strip('file:///C:/a/'))
536
def test_strip_trailing_slash(self):
537
sts = urlutils.strip_trailing_slash
538
if sys.platform == 'win32':
539
self.assertEqual('file:///C|/', sts('file:///C|/'))
540
self.assertEqual('file:///C:/foo', sts('file:///C:/foo'))
541
self.assertEqual('file:///C|/foo', sts('file:///C|/foo/'))
543
self.assertEqual('file:///', sts('file:///'))
544
self.assertEqual('file:///foo', sts('file:///foo'))
545
self.assertEqual('file:///foo', sts('file:///foo/'))
547
self.assertEqual('http://host/', sts('http://host/'))
548
self.assertEqual('http://host/foo', sts('http://host/foo'))
549
self.assertEqual('http://host/foo', sts('http://host/foo/'))
551
# No need to fail just because the slash is missing
552
self.assertEqual('http://host', sts('http://host'))
553
# TODO: jam 20060502 Should this raise InvalidURL?
554
self.assertEqual('file://', sts('file://'))
556
self.assertEqual('random+scheme://user:pass@ahost:port/path',
557
sts('random+scheme://user:pass@ahost:port/path'))
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/',
561
sts('random+scheme://user:pass@ahost:port/'))
563
# Make sure relative paths work too
564
self.assertEqual('path/to/foo', sts('path/to/foo'))
565
self.assertEqual('path/to/foo', sts('path/to/foo/'))
566
self.assertEqual('../to/foo', sts('../to/foo/'))
567
self.assertEqual('path/../foo', sts('path/../foo/'))
569
def test_unescape_for_display_utf8(self):
570
# Test that URLs are converted to nice unicode strings for display
571
def test(expected, url, encoding='utf-8'):
572
disp_url = urlutils.unescape_for_display(url, encoding=encoding)
573
self.assertIsInstance(disp_url, unicode)
574
self.assertEqual(expected, disp_url)
576
test('http://foo', 'http://foo')
577
if sys.platform == 'win32':
578
test('C:/foo/path', 'file:///C|/foo/path')
579
test('C:/foo/path', 'file:///C:/foo/path')
581
test('/foo/path', 'file:///foo/path')
583
test('http://foo/%2Fbaz', 'http://foo/%2Fbaz')
584
test(u'http://host/r\xe4ksm\xf6rg\xe5s',
585
'http://host/r%C3%A4ksm%C3%B6rg%C3%A5s')
587
# Make sure special escaped characters stay escaped
588
test(u'http://host/%3B%2F%3F%3A%40%26%3D%2B%24%2C%23',
589
'http://host/%3B%2F%3F%3A%40%26%3D%2B%24%2C%23')
591
# Can we handle sections that don't have utf-8 encoding?
592
test(u'http://host/%EE%EE%EE/r\xe4ksm\xf6rg\xe5s',
593
'http://host/%EE%EE%EE/r%C3%A4ksm%C3%B6rg%C3%A5s')
595
# Test encoding into output that can handle some characters
596
test(u'http://host/%EE%EE%EE/r\xe4ksm\xf6rg\xe5s',
597
'http://host/%EE%EE%EE/r%C3%A4ksm%C3%B6rg%C3%A5s',
598
encoding='iso-8859-1')
600
# This one can be encoded into utf8
601
test(u'http://host/\u062c\u0648\u062c\u0648',
602
'http://host/%d8%ac%d9%88%d8%ac%d9%88',
605
# This can't be put into 8859-1 and so stays as escapes
606
test(u'http://host/%d8%ac%d9%88%d8%ac%d9%88',
607
'http://host/%d8%ac%d9%88%d8%ac%d9%88',
608
encoding='iso-8859-1')
610
def test_escape(self):
611
self.assertEqual('%25', urlutils.escape('%'))
612
self.assertEqual('%C3%A5', urlutils.escape(u'\xe5'))
613
self.assertFalse(isinstance(urlutils.escape(u'\xe5'), unicode))
615
def test_escape_tildes(self):
616
self.assertEqual('~foo', urlutils.escape('~foo'))
618
def test_unescape(self):
619
self.assertEqual('%', urlutils.unescape('%25'))
620
self.assertEqual(u'\xe5', urlutils.unescape('%C3%A5'))
622
self.assertRaises(InvalidURL, urlutils.unescape, u'\xe5')
623
self.assertRaises(InvalidURL, urlutils.unescape, '\xe5')
624
self.assertRaises(InvalidURL, urlutils.unescape, '%E5')
626
def test_escape_unescape(self):
627
self.assertEqual(u'\xe5', urlutils.unescape(urlutils.escape(u'\xe5')))
628
self.assertEqual('%', urlutils.unescape(urlutils.escape('%')))
630
def test_relative_url(self):
631
def test(expected, base, other):
632
result = urlutils.relative_url(base, other)
633
self.assertEqual(expected, result)
635
test('a', 'http://host/', 'http://host/a')
636
test('http://entirely/different', 'sftp://host/branch',
637
'http://entirely/different')
638
test('../person/feature', 'http://host/branch/mainline',
639
'http://host/branch/person/feature')
640
test('..', 'http://host/branch', 'http://host/')
641
test('http://host2/branch', 'http://host1/branch', 'http://host2/branch')
642
test('.', 'http://host1/branch', 'http://host1/branch')
643
test('../../../branch/2b', 'file:///home/jelmer/foo/bar/2b',
644
'file:///home/jelmer/branch/2b')
645
test('../../branch/2b', 'sftp://host/home/jelmer/bar/2b',
646
'sftp://host/home/jelmer/branch/2b')
647
test('../../branch/feature/%2b', 'http://host/home/jelmer/bar/%2b',
648
'http://host/home/jelmer/branch/feature/%2b')
649
test('../../branch/feature/2b', 'http://host/home/jelmer/bar/2b/',
650
'http://host/home/jelmer/branch/feature/2b')
651
# relative_url should preserve a trailing slash
652
test('../../branch/feature/2b/', 'http://host/home/jelmer/bar/2b/',
653
'http://host/home/jelmer/branch/feature/2b/')
654
test('../../branch/feature/2b/', 'http://host/home/jelmer/bar/2b',
655
'http://host/home/jelmer/branch/feature/2b/')
657
# TODO: treat http://host as http://host/
658
# relative_url is typically called from a branch.base or
659
# transport.base which always ends with a /
660
#test('a', 'http://host', 'http://host/a')
661
test('http://host/a', 'http://host', 'http://host/a')
662
#test('.', 'http://host', 'http://host/')
663
test('http://host/', 'http://host', 'http://host/')
664
#test('.', 'http://host/', 'http://host')
665
test('http://host', 'http://host/', 'http://host')
667
# On Windows file:///C:/path/to and file:///D:/other/path
668
# should not use relative url over the non-existent '/' directory.
669
if sys.platform == 'win32':
671
test('../../other/path',
672
'file:///C:/path/to', 'file:///C:/other/path')
673
#~next two tests is failed, i.e. urlutils.relative_url expects
674
#~to see normalized file URLs?
675
#~test('../../other/path',
676
#~ 'file:///C:/path/to', 'file:///c:/other/path')
677
#~test('../../other/path',
678
#~ 'file:///C:/path/to', 'file:///C|/other/path')
680
# check UNC paths too
681
test('../../other/path',
682
'file://HOST/base/path/to', 'file://HOST/base/other/path')
683
# on different drives
684
test('file:///D:/other/path',
685
'file:///C:/path/to', 'file:///D:/other/path')
686
# TODO: strictly saying in UNC path //HOST/base is full analog
687
# of drive letter for hard disk, and this situation is also
688
# should be exception from rules. [bialix 20071221]
691
class TestCwdToURL(TestCaseInTempDir):
692
"""Test that local_path_to_url works base on the cwd"""
695
# This test will fail if getcwd is not ascii
699
url = urlutils.local_path_to_url('.')
700
self.assertEndsWith(url, '/mytest')
702
def test_non_ascii(self):
703
if win32utils.winver == 'Windows 98':
704
raise TestSkipped('Windows 98 cannot handle unicode filenames')
709
raise TestSkipped('cannot create unicode directory')
713
# On Mac OSX this directory is actually:
714
# u'/dode\u0301' => '/dode\xcc\x81
715
# but we should normalize it back to
716
# u'/dod\xe9' => '/dod\xc3\xa9'
717
url = urlutils.local_path_to_url('.')
718
self.assertEndsWith(url, '/dod%C3%A9')
721
class TestDeriveToLocation(TestCase):
722
"""Test that the mapping of FROM_LOCATION to TO_LOCATION works."""
724
def test_to_locations_derived_from_paths(self):
725
derive = urlutils.derive_to_location
726
self.assertEqual("bar", derive("bar"))
727
self.assertEqual("bar", derive("../bar"))
728
self.assertEqual("bar", derive("/foo/bar"))
729
self.assertEqual("bar", derive("c:/foo/bar"))
730
self.assertEqual("bar", derive("c:bar"))
732
def test_to_locations_derived_from_urls(self):
733
derive = urlutils.derive_to_location
734
self.assertEqual("bar", derive("http://foo/bar"))
735
self.assertEqual("bar", derive("bzr+ssh://foo/bar"))
736
self.assertEqual("foo-bar", derive("lp:foo-bar"))
739
class TestRebaseURL(TestCase):
740
"""Test the behavior of rebase_url."""
742
def test_non_relative(self):
743
result = urlutils.rebase_url('file://foo', 'file://foo',
745
self.assertEqual('file://foo', result)
746
result = urlutils.rebase_url('/foo', 'file://foo',
748
self.assertEqual('/foo', result)
750
def test_different_ports(self):
751
e = self.assertRaises(InvalidRebaseURLs, urlutils.rebase_url,
752
'foo', 'http://bar:80', 'http://bar:81')
753
self.assertEqual(str(e), "URLs differ by more than path:"
754
" 'http://bar:80' and 'http://bar:81'")
756
def test_different_hosts(self):
757
e = self.assertRaises(InvalidRebaseURLs, urlutils.rebase_url,
758
'foo', 'http://bar', 'http://baz')
759
self.assertEqual(str(e), "URLs differ by more than path: 'http://bar'"
762
def test_different_protocol(self):
763
e = self.assertRaises(InvalidRebaseURLs, urlutils.rebase_url,
764
'foo', 'http://bar', 'ftp://bar')
765
self.assertEqual(str(e), "URLs differ by more than path: 'http://bar'"
768
def test_rebase_success(self):
769
self.assertEqual('../bar', urlutils.rebase_url('bar', 'http://baz/',
771
self.assertEqual('qux/bar', urlutils.rebase_url('bar',
772
'http://baz/qux', 'http://baz/'))
773
self.assertEqual('.', urlutils.rebase_url('foo',
774
'http://bar/', 'http://bar/foo/'))
775
self.assertEqual('qux/bar', urlutils.rebase_url('../bar',
776
'http://baz/qux/foo', 'http://baz/'))
778
def test_determine_relative_path(self):
779
self.assertEqual('../../baz/bar',
780
urlutils.determine_relative_path(
781
'/qux/quxx', '/baz/bar'))
782
self.assertEqual('..',
783
urlutils.determine_relative_path(
785
self.assertEqual('baz',
786
urlutils.determine_relative_path(
788
self.assertEqual('.', urlutils.determine_relative_path(
792
class TestParseURL(TestCase):
794
def test_parse_url(self):
795
self.assertEqual(urlutils.parse_url('http://example.com:80/one'),
796
('http', None, None, 'example.com', 80, '/one'))
797
self.assertEqual(urlutils.parse_url('http://[1:2:3::40]/one'),
798
('http', None, None, '1:2:3::40', None, '/one'))
799
self.assertEqual(urlutils.parse_url('http://[1:2:3::40]:80/one'),
800
('http', None, None, '1:2:3::40', 80, '/one'))