• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import sys
2import unicodedata
3import unittest
4import urllib.parse
5
6RFC1808_BASE = "http://a/b/c/d;p?q#f"
7RFC2396_BASE = "http://a/b/c/d;p?q"
8RFC3986_BASE = 'http://a/b/c/d;p?q'
9SIMPLE_BASE  = 'http://a/b/c/d'
10
11# Each parse_qsl testcase is a two-tuple that contains
12# a string with the query and a list with the expected result.
13
14parse_qsl_test_cases = [
15    ("", []),
16    ("&", []),
17    ("&&", []),
18    ("=", [('', '')]),
19    ("=a", [('', 'a')]),
20    ("a", [('a', '')]),
21    ("a=", [('a', '')]),
22    ("&a=b", [('a', 'b')]),
23    ("a=a+b&b=b+c", [('a', 'a b'), ('b', 'b c')]),
24    ("a=1&a=2", [('a', '1'), ('a', '2')]),
25    (b"", []),
26    (b"&", []),
27    (b"&&", []),
28    (b"=", [(b'', b'')]),
29    (b"=a", [(b'', b'a')]),
30    (b"a", [(b'a', b'')]),
31    (b"a=", [(b'a', b'')]),
32    (b"&a=b", [(b'a', b'b')]),
33    (b"a=a+b&b=b+c", [(b'a', b'a b'), (b'b', b'b c')]),
34    (b"a=1&a=2", [(b'a', b'1'), (b'a', b'2')]),
35    (";a=b", [(';a', 'b')]),
36    ("a=a+b;b=b+c", [('a', 'a b;b=b c')]),
37    (b";a=b", [(b';a', b'b')]),
38    (b"a=a+b;b=b+c", [(b'a', b'a b;b=b c')]),
39]
40
41# Each parse_qs testcase is a two-tuple that contains
42# a string with the query and a dictionary with the expected result.
43
44parse_qs_test_cases = [
45    ("", {}),
46    ("&", {}),
47    ("&&", {}),
48    ("=", {'': ['']}),
49    ("=a", {'': ['a']}),
50    ("a", {'a': ['']}),
51    ("a=", {'a': ['']}),
52    ("&a=b", {'a': ['b']}),
53    ("a=a+b&b=b+c", {'a': ['a b'], 'b': ['b c']}),
54    ("a=1&a=2", {'a': ['1', '2']}),
55    (b"", {}),
56    (b"&", {}),
57    (b"&&", {}),
58    (b"=", {b'': [b'']}),
59    (b"=a", {b'': [b'a']}),
60    (b"a", {b'a': [b'']}),
61    (b"a=", {b'a': [b'']}),
62    (b"&a=b", {b'a': [b'b']}),
63    (b"a=a+b&b=b+c", {b'a': [b'a b'], b'b': [b'b c']}),
64    (b"a=1&a=2", {b'a': [b'1', b'2']}),
65    (";a=b", {';a': ['b']}),
66    ("a=a+b;b=b+c", {'a': ['a b;b=b c']}),
67    (b";a=b", {b';a': [b'b']}),
68    (b"a=a+b;b=b+c", {b'a':[ b'a b;b=b c']}),
69]
70
71class UrlParseTestCase(unittest.TestCase):
72
73    def checkRoundtrips(self, url, parsed, split):
74        result = urllib.parse.urlparse(url)
75        self.assertEqual(result, parsed)
76        t = (result.scheme, result.netloc, result.path,
77             result.params, result.query, result.fragment)
78        self.assertEqual(t, parsed)
79        # put it back together and it should be the same
80        result2 = urllib.parse.urlunparse(result)
81        self.assertEqual(result2, url)
82        self.assertEqual(result2, result.geturl())
83
84        # the result of geturl() is a fixpoint; we can always parse it
85        # again to get the same result:
86        result3 = urllib.parse.urlparse(result.geturl())
87        self.assertEqual(result3.geturl(), result.geturl())
88        self.assertEqual(result3,          result)
89        self.assertEqual(result3.scheme,   result.scheme)
90        self.assertEqual(result3.netloc,   result.netloc)
91        self.assertEqual(result3.path,     result.path)
92        self.assertEqual(result3.params,   result.params)
93        self.assertEqual(result3.query,    result.query)
94        self.assertEqual(result3.fragment, result.fragment)
95        self.assertEqual(result3.username, result.username)
96        self.assertEqual(result3.password, result.password)
97        self.assertEqual(result3.hostname, result.hostname)
98        self.assertEqual(result3.port,     result.port)
99
100        # check the roundtrip using urlsplit() as well
101        result = urllib.parse.urlsplit(url)
102        self.assertEqual(result, split)
103        t = (result.scheme, result.netloc, result.path,
104             result.query, result.fragment)
105        self.assertEqual(t, split)
106        result2 = urllib.parse.urlunsplit(result)
107        self.assertEqual(result2, url)
108        self.assertEqual(result2, result.geturl())
109
110        # check the fixpoint property of re-parsing the result of geturl()
111        result3 = urllib.parse.urlsplit(result.geturl())
112        self.assertEqual(result3.geturl(), result.geturl())
113        self.assertEqual(result3,          result)
114        self.assertEqual(result3.scheme,   result.scheme)
115        self.assertEqual(result3.netloc,   result.netloc)
116        self.assertEqual(result3.path,     result.path)
117        self.assertEqual(result3.query,    result.query)
118        self.assertEqual(result3.fragment, result.fragment)
119        self.assertEqual(result3.username, result.username)
120        self.assertEqual(result3.password, result.password)
121        self.assertEqual(result3.hostname, result.hostname)
122        self.assertEqual(result3.port,     result.port)
123
124    def test_qsl(self):
125        for orig, expect in parse_qsl_test_cases:
126            result = urllib.parse.parse_qsl(orig, keep_blank_values=True)
127            self.assertEqual(result, expect, "Error parsing %r" % orig)
128            expect_without_blanks = [v for v in expect if len(v[1])]
129            result = urllib.parse.parse_qsl(orig, keep_blank_values=False)
130            self.assertEqual(result, expect_without_blanks,
131                            "Error parsing %r" % orig)
132
133    def test_qs(self):
134        for orig, expect in parse_qs_test_cases:
135            result = urllib.parse.parse_qs(orig, keep_blank_values=True)
136            self.assertEqual(result, expect, "Error parsing %r" % orig)
137            expect_without_blanks = {v: expect[v]
138                                     for v in expect if len(expect[v][0])}
139            result = urllib.parse.parse_qs(orig, keep_blank_values=False)
140            self.assertEqual(result, expect_without_blanks,
141                            "Error parsing %r" % orig)
142
143    def test_roundtrips(self):
144        str_cases = [
145            ('file:///tmp/junk.txt',
146             ('file', '', '/tmp/junk.txt', '', '', ''),
147             ('file', '', '/tmp/junk.txt', '', '')),
148            ('imap://mail.python.org/mbox1',
149             ('imap', 'mail.python.org', '/mbox1', '', '', ''),
150             ('imap', 'mail.python.org', '/mbox1', '', '')),
151            ('mms://wms.sys.hinet.net/cts/Drama/09006251100.asf',
152             ('mms', 'wms.sys.hinet.net', '/cts/Drama/09006251100.asf',
153              '', '', ''),
154             ('mms', 'wms.sys.hinet.net', '/cts/Drama/09006251100.asf',
155              '', '')),
156            ('nfs://server/path/to/file.txt',
157             ('nfs', 'server', '/path/to/file.txt', '', '', ''),
158             ('nfs', 'server', '/path/to/file.txt', '', '')),
159            ('svn+ssh://svn.zope.org/repos/main/ZConfig/trunk/',
160             ('svn+ssh', 'svn.zope.org', '/repos/main/ZConfig/trunk/',
161              '', '', ''),
162             ('svn+ssh', 'svn.zope.org', '/repos/main/ZConfig/trunk/',
163              '', '')),
164            ('git+ssh://git@github.com/user/project.git',
165            ('git+ssh', 'git@github.com','/user/project.git',
166             '','',''),
167            ('git+ssh', 'git@github.com','/user/project.git',
168             '', '')),
169            ]
170        def _encode(t):
171            return (t[0].encode('ascii'),
172                    tuple(x.encode('ascii') for x in t[1]),
173                    tuple(x.encode('ascii') for x in t[2]))
174        bytes_cases = [_encode(x) for x in str_cases]
175        for url, parsed, split in str_cases + bytes_cases:
176            self.checkRoundtrips(url, parsed, split)
177
178    def test_http_roundtrips(self):
179        # urllib.parse.urlsplit treats 'http:' as an optimized special case,
180        # so we test both 'http:' and 'https:' in all the following.
181        # Three cheers for white box knowledge!
182        str_cases = [
183            ('://www.python.org',
184             ('www.python.org', '', '', '', ''),
185             ('www.python.org', '', '', '')),
186            ('://www.python.org#abc',
187             ('www.python.org', '', '', '', 'abc'),
188             ('www.python.org', '', '', 'abc')),
189            ('://www.python.org?q=abc',
190             ('www.python.org', '', '', 'q=abc', ''),
191             ('www.python.org', '', 'q=abc', '')),
192            ('://www.python.org/#abc',
193             ('www.python.org', '/', '', '', 'abc'),
194             ('www.python.org', '/', '', 'abc')),
195            ('://a/b/c/d;p?q#f',
196             ('a', '/b/c/d', 'p', 'q', 'f'),
197             ('a', '/b/c/d;p', 'q', 'f')),
198            ]
199        def _encode(t):
200            return (t[0].encode('ascii'),
201                    tuple(x.encode('ascii') for x in t[1]),
202                    tuple(x.encode('ascii') for x in t[2]))
203        bytes_cases = [_encode(x) for x in str_cases]
204        str_schemes = ('http', 'https')
205        bytes_schemes = (b'http', b'https')
206        str_tests = str_schemes, str_cases
207        bytes_tests = bytes_schemes, bytes_cases
208        for schemes, test_cases in (str_tests, bytes_tests):
209            for scheme in schemes:
210                for url, parsed, split in test_cases:
211                    url = scheme + url
212                    parsed = (scheme,) + parsed
213                    split = (scheme,) + split
214                    self.checkRoundtrips(url, parsed, split)
215
216    def checkJoin(self, base, relurl, expected):
217        str_components = (base, relurl, expected)
218        self.assertEqual(urllib.parse.urljoin(base, relurl), expected)
219        bytes_components = baseb, relurlb, expectedb = [
220                            x.encode('ascii') for x in str_components]
221        self.assertEqual(urllib.parse.urljoin(baseb, relurlb), expectedb)
222
223    def test_unparse_parse(self):
224        str_cases = ['Python', './Python','x-newscheme://foo.com/stuff','x://y','x:/y','x:/','/',]
225        bytes_cases = [x.encode('ascii') for x in str_cases]
226        for u in str_cases + bytes_cases:
227            self.assertEqual(urllib.parse.urlunsplit(urllib.parse.urlsplit(u)), u)
228            self.assertEqual(urllib.parse.urlunparse(urllib.parse.urlparse(u)), u)
229
230    def test_RFC1808(self):
231        # "normal" cases from RFC 1808:
232        self.checkJoin(RFC1808_BASE, 'g:h', 'g:h')
233        self.checkJoin(RFC1808_BASE, 'g', 'http://a/b/c/g')
234        self.checkJoin(RFC1808_BASE, './g', 'http://a/b/c/g')
235        self.checkJoin(RFC1808_BASE, 'g/', 'http://a/b/c/g/')
236        self.checkJoin(RFC1808_BASE, '/g', 'http://a/g')
237        self.checkJoin(RFC1808_BASE, '//g', 'http://g')
238        self.checkJoin(RFC1808_BASE, 'g?y', 'http://a/b/c/g?y')
239        self.checkJoin(RFC1808_BASE, 'g?y/./x', 'http://a/b/c/g?y/./x')
240        self.checkJoin(RFC1808_BASE, '#s', 'http://a/b/c/d;p?q#s')
241        self.checkJoin(RFC1808_BASE, 'g#s', 'http://a/b/c/g#s')
242        self.checkJoin(RFC1808_BASE, 'g#s/./x', 'http://a/b/c/g#s/./x')
243        self.checkJoin(RFC1808_BASE, 'g?y#s', 'http://a/b/c/g?y#s')
244        self.checkJoin(RFC1808_BASE, 'g;x', 'http://a/b/c/g;x')
245        self.checkJoin(RFC1808_BASE, 'g;x?y#s', 'http://a/b/c/g;x?y#s')
246        self.checkJoin(RFC1808_BASE, '.', 'http://a/b/c/')
247        self.checkJoin(RFC1808_BASE, './', 'http://a/b/c/')
248        self.checkJoin(RFC1808_BASE, '..', 'http://a/b/')
249        self.checkJoin(RFC1808_BASE, '../', 'http://a/b/')
250        self.checkJoin(RFC1808_BASE, '../g', 'http://a/b/g')
251        self.checkJoin(RFC1808_BASE, '../..', 'http://a/')
252        self.checkJoin(RFC1808_BASE, '../../', 'http://a/')
253        self.checkJoin(RFC1808_BASE, '../../g', 'http://a/g')
254
255        # "abnormal" cases from RFC 1808:
256        self.checkJoin(RFC1808_BASE, '', 'http://a/b/c/d;p?q#f')
257        self.checkJoin(RFC1808_BASE, 'g.', 'http://a/b/c/g.')
258        self.checkJoin(RFC1808_BASE, '.g', 'http://a/b/c/.g')
259        self.checkJoin(RFC1808_BASE, 'g..', 'http://a/b/c/g..')
260        self.checkJoin(RFC1808_BASE, '..g', 'http://a/b/c/..g')
261        self.checkJoin(RFC1808_BASE, './../g', 'http://a/b/g')
262        self.checkJoin(RFC1808_BASE, './g/.', 'http://a/b/c/g/')
263        self.checkJoin(RFC1808_BASE, 'g/./h', 'http://a/b/c/g/h')
264        self.checkJoin(RFC1808_BASE, 'g/../h', 'http://a/b/c/h')
265
266        # RFC 1808 and RFC 1630 disagree on these (according to RFC 1808),
267        # so we'll not actually run these tests (which expect 1808 behavior).
268        #self.checkJoin(RFC1808_BASE, 'http:g', 'http:g')
269        #self.checkJoin(RFC1808_BASE, 'http:', 'http:')
270
271        # XXX: The following tests are no longer compatible with RFC3986
272        # self.checkJoin(RFC1808_BASE, '../../../g', 'http://a/../g')
273        # self.checkJoin(RFC1808_BASE, '../../../../g', 'http://a/../../g')
274        # self.checkJoin(RFC1808_BASE, '/./g', 'http://a/./g')
275        # self.checkJoin(RFC1808_BASE, '/../g', 'http://a/../g')
276
277
278    def test_RFC2368(self):
279        # Issue 11467: path that starts with a number is not parsed correctly
280        self.assertEqual(urllib.parse.urlparse('mailto:1337@example.org'),
281                ('mailto', '', '1337@example.org', '', '', ''))
282
283    def test_RFC2396(self):
284        # cases from RFC 2396
285
286        self.checkJoin(RFC2396_BASE, 'g:h', 'g:h')
287        self.checkJoin(RFC2396_BASE, 'g', 'http://a/b/c/g')
288        self.checkJoin(RFC2396_BASE, './g', 'http://a/b/c/g')
289        self.checkJoin(RFC2396_BASE, 'g/', 'http://a/b/c/g/')
290        self.checkJoin(RFC2396_BASE, '/g', 'http://a/g')
291        self.checkJoin(RFC2396_BASE, '//g', 'http://g')
292        self.checkJoin(RFC2396_BASE, 'g?y', 'http://a/b/c/g?y')
293        self.checkJoin(RFC2396_BASE, '#s', 'http://a/b/c/d;p?q#s')
294        self.checkJoin(RFC2396_BASE, 'g#s', 'http://a/b/c/g#s')
295        self.checkJoin(RFC2396_BASE, 'g?y#s', 'http://a/b/c/g?y#s')
296        self.checkJoin(RFC2396_BASE, 'g;x', 'http://a/b/c/g;x')
297        self.checkJoin(RFC2396_BASE, 'g;x?y#s', 'http://a/b/c/g;x?y#s')
298        self.checkJoin(RFC2396_BASE, '.', 'http://a/b/c/')
299        self.checkJoin(RFC2396_BASE, './', 'http://a/b/c/')
300        self.checkJoin(RFC2396_BASE, '..', 'http://a/b/')
301        self.checkJoin(RFC2396_BASE, '../', 'http://a/b/')
302        self.checkJoin(RFC2396_BASE, '../g', 'http://a/b/g')
303        self.checkJoin(RFC2396_BASE, '../..', 'http://a/')
304        self.checkJoin(RFC2396_BASE, '../../', 'http://a/')
305        self.checkJoin(RFC2396_BASE, '../../g', 'http://a/g')
306        self.checkJoin(RFC2396_BASE, '', RFC2396_BASE)
307        self.checkJoin(RFC2396_BASE, 'g.', 'http://a/b/c/g.')
308        self.checkJoin(RFC2396_BASE, '.g', 'http://a/b/c/.g')
309        self.checkJoin(RFC2396_BASE, 'g..', 'http://a/b/c/g..')
310        self.checkJoin(RFC2396_BASE, '..g', 'http://a/b/c/..g')
311        self.checkJoin(RFC2396_BASE, './../g', 'http://a/b/g')
312        self.checkJoin(RFC2396_BASE, './g/.', 'http://a/b/c/g/')
313        self.checkJoin(RFC2396_BASE, 'g/./h', 'http://a/b/c/g/h')
314        self.checkJoin(RFC2396_BASE, 'g/../h', 'http://a/b/c/h')
315        self.checkJoin(RFC2396_BASE, 'g;x=1/./y', 'http://a/b/c/g;x=1/y')
316        self.checkJoin(RFC2396_BASE, 'g;x=1/../y', 'http://a/b/c/y')
317        self.checkJoin(RFC2396_BASE, 'g?y/./x', 'http://a/b/c/g?y/./x')
318        self.checkJoin(RFC2396_BASE, 'g?y/../x', 'http://a/b/c/g?y/../x')
319        self.checkJoin(RFC2396_BASE, 'g#s/./x', 'http://a/b/c/g#s/./x')
320        self.checkJoin(RFC2396_BASE, 'g#s/../x', 'http://a/b/c/g#s/../x')
321
322        # XXX: The following tests are no longer compatible with RFC3986
323        # self.checkJoin(RFC2396_BASE, '../../../g', 'http://a/../g')
324        # self.checkJoin(RFC2396_BASE, '../../../../g', 'http://a/../../g')
325        # self.checkJoin(RFC2396_BASE, '/./g', 'http://a/./g')
326        # self.checkJoin(RFC2396_BASE, '/../g', 'http://a/../g')
327
328    def test_RFC3986(self):
329        self.checkJoin(RFC3986_BASE, '?y','http://a/b/c/d;p?y')
330        self.checkJoin(RFC3986_BASE, ';x', 'http://a/b/c/;x')
331        self.checkJoin(RFC3986_BASE, 'g:h','g:h')
332        self.checkJoin(RFC3986_BASE, 'g','http://a/b/c/g')
333        self.checkJoin(RFC3986_BASE, './g','http://a/b/c/g')
334        self.checkJoin(RFC3986_BASE, 'g/','http://a/b/c/g/')
335        self.checkJoin(RFC3986_BASE, '/g','http://a/g')
336        self.checkJoin(RFC3986_BASE, '//g','http://g')
337        self.checkJoin(RFC3986_BASE, '?y','http://a/b/c/d;p?y')
338        self.checkJoin(RFC3986_BASE, 'g?y','http://a/b/c/g?y')
339        self.checkJoin(RFC3986_BASE, '#s','http://a/b/c/d;p?q#s')
340        self.checkJoin(RFC3986_BASE, 'g#s','http://a/b/c/g#s')
341        self.checkJoin(RFC3986_BASE, 'g?y#s','http://a/b/c/g?y#s')
342        self.checkJoin(RFC3986_BASE, ';x','http://a/b/c/;x')
343        self.checkJoin(RFC3986_BASE, 'g;x','http://a/b/c/g;x')
344        self.checkJoin(RFC3986_BASE, 'g;x?y#s','http://a/b/c/g;x?y#s')
345        self.checkJoin(RFC3986_BASE, '','http://a/b/c/d;p?q')
346        self.checkJoin(RFC3986_BASE, '.','http://a/b/c/')
347        self.checkJoin(RFC3986_BASE, './','http://a/b/c/')
348        self.checkJoin(RFC3986_BASE, '..','http://a/b/')
349        self.checkJoin(RFC3986_BASE, '../','http://a/b/')
350        self.checkJoin(RFC3986_BASE, '../g','http://a/b/g')
351        self.checkJoin(RFC3986_BASE, '../..','http://a/')
352        self.checkJoin(RFC3986_BASE, '../../','http://a/')
353        self.checkJoin(RFC3986_BASE, '../../g','http://a/g')
354        self.checkJoin(RFC3986_BASE, '../../../g', 'http://a/g')
355
356        # Abnormal Examples
357
358        # The 'abnormal scenarios' are incompatible with RFC2986 parsing
359        # Tests are here for reference.
360
361        self.checkJoin(RFC3986_BASE, '../../../g','http://a/g')
362        self.checkJoin(RFC3986_BASE, '../../../../g','http://a/g')
363        self.checkJoin(RFC3986_BASE, '/./g','http://a/g')
364        self.checkJoin(RFC3986_BASE, '/../g','http://a/g')
365        self.checkJoin(RFC3986_BASE, 'g.','http://a/b/c/g.')
366        self.checkJoin(RFC3986_BASE, '.g','http://a/b/c/.g')
367        self.checkJoin(RFC3986_BASE, 'g..','http://a/b/c/g..')
368        self.checkJoin(RFC3986_BASE, '..g','http://a/b/c/..g')
369        self.checkJoin(RFC3986_BASE, './../g','http://a/b/g')
370        self.checkJoin(RFC3986_BASE, './g/.','http://a/b/c/g/')
371        self.checkJoin(RFC3986_BASE, 'g/./h','http://a/b/c/g/h')
372        self.checkJoin(RFC3986_BASE, 'g/../h','http://a/b/c/h')
373        self.checkJoin(RFC3986_BASE, 'g;x=1/./y','http://a/b/c/g;x=1/y')
374        self.checkJoin(RFC3986_BASE, 'g;x=1/../y','http://a/b/c/y')
375        self.checkJoin(RFC3986_BASE, 'g?y/./x','http://a/b/c/g?y/./x')
376        self.checkJoin(RFC3986_BASE, 'g?y/../x','http://a/b/c/g?y/../x')
377        self.checkJoin(RFC3986_BASE, 'g#s/./x','http://a/b/c/g#s/./x')
378        self.checkJoin(RFC3986_BASE, 'g#s/../x','http://a/b/c/g#s/../x')
379        #self.checkJoin(RFC3986_BASE, 'http:g','http:g') # strict parser
380        self.checkJoin(RFC3986_BASE, 'http:g','http://a/b/c/g') #relaxed parser
381
382        # Test for issue9721
383        self.checkJoin('http://a/b/c/de', ';x','http://a/b/c/;x')
384
385    def test_urljoins(self):
386        self.checkJoin(SIMPLE_BASE, 'g:h','g:h')
387        self.checkJoin(SIMPLE_BASE, 'http:g','http://a/b/c/g')
388        self.checkJoin(SIMPLE_BASE, 'http:','http://a/b/c/d')
389        self.checkJoin(SIMPLE_BASE, 'g','http://a/b/c/g')
390        self.checkJoin(SIMPLE_BASE, './g','http://a/b/c/g')
391        self.checkJoin(SIMPLE_BASE, 'g/','http://a/b/c/g/')
392        self.checkJoin(SIMPLE_BASE, '/g','http://a/g')
393        self.checkJoin(SIMPLE_BASE, '//g','http://g')
394        self.checkJoin(SIMPLE_BASE, '?y','http://a/b/c/d?y')
395        self.checkJoin(SIMPLE_BASE, 'g?y','http://a/b/c/g?y')
396        self.checkJoin(SIMPLE_BASE, 'g?y/./x','http://a/b/c/g?y/./x')
397        self.checkJoin(SIMPLE_BASE, '.','http://a/b/c/')
398        self.checkJoin(SIMPLE_BASE, './','http://a/b/c/')
399        self.checkJoin(SIMPLE_BASE, '..','http://a/b/')
400        self.checkJoin(SIMPLE_BASE, '../','http://a/b/')
401        self.checkJoin(SIMPLE_BASE, '../g','http://a/b/g')
402        self.checkJoin(SIMPLE_BASE, '../..','http://a/')
403        self.checkJoin(SIMPLE_BASE, '../../g','http://a/g')
404        self.checkJoin(SIMPLE_BASE, './../g','http://a/b/g')
405        self.checkJoin(SIMPLE_BASE, './g/.','http://a/b/c/g/')
406        self.checkJoin(SIMPLE_BASE, 'g/./h','http://a/b/c/g/h')
407        self.checkJoin(SIMPLE_BASE, 'g/../h','http://a/b/c/h')
408        self.checkJoin(SIMPLE_BASE, 'http:g','http://a/b/c/g')
409        self.checkJoin(SIMPLE_BASE, 'http:','http://a/b/c/d')
410        self.checkJoin(SIMPLE_BASE, 'http:?y','http://a/b/c/d?y')
411        self.checkJoin(SIMPLE_BASE, 'http:g?y','http://a/b/c/g?y')
412        self.checkJoin(SIMPLE_BASE, 'http:g?y/./x','http://a/b/c/g?y/./x')
413        self.checkJoin('http:///', '..','http:///')
414        self.checkJoin('', 'http://a/b/c/g?y/./x','http://a/b/c/g?y/./x')
415        self.checkJoin('', 'http://a/./g', 'http://a/./g')
416        self.checkJoin('svn://pathtorepo/dir1', 'dir2', 'svn://pathtorepo/dir2')
417        self.checkJoin('svn+ssh://pathtorepo/dir1', 'dir2', 'svn+ssh://pathtorepo/dir2')
418        self.checkJoin('ws://a/b','g','ws://a/g')
419        self.checkJoin('wss://a/b','g','wss://a/g')
420
421        # XXX: The following tests are no longer compatible with RFC3986
422        # self.checkJoin(SIMPLE_BASE, '../../../g','http://a/../g')
423        # self.checkJoin(SIMPLE_BASE, '/./g','http://a/./g')
424
425        # test for issue22118 duplicate slashes
426        self.checkJoin(SIMPLE_BASE + '/', 'foo', SIMPLE_BASE + '/foo')
427
428        # Non-RFC-defined tests, covering variations of base and trailing
429        # slashes
430        self.checkJoin('http://a/b/c/d/e/', '../../f/g/', 'http://a/b/c/f/g/')
431        self.checkJoin('http://a/b/c/d/e', '../../f/g/', 'http://a/b/f/g/')
432        self.checkJoin('http://a/b/c/d/e/', '/../../f/g/', 'http://a/f/g/')
433        self.checkJoin('http://a/b/c/d/e', '/../../f/g/', 'http://a/f/g/')
434        self.checkJoin('http://a/b/c/d/e/', '../../f/g', 'http://a/b/c/f/g')
435        self.checkJoin('http://a/b/', '../../f/g/', 'http://a/f/g/')
436
437        # issue 23703: don't duplicate filename
438        self.checkJoin('a', 'b', 'b')
439
440    def test_RFC2732(self):
441        str_cases = [
442            ('http://Test.python.org:5432/foo/', 'test.python.org', 5432),
443            ('http://12.34.56.78:5432/foo/', '12.34.56.78', 5432),
444            ('http://[::1]:5432/foo/', '::1', 5432),
445            ('http://[dead:beef::1]:5432/foo/', 'dead:beef::1', 5432),
446            ('http://[dead:beef::]:5432/foo/', 'dead:beef::', 5432),
447            ('http://[dead:beef:cafe:5417:affe:8FA3:deaf:feed]:5432/foo/',
448             'dead:beef:cafe:5417:affe:8fa3:deaf:feed', 5432),
449            ('http://[::12.34.56.78]:5432/foo/', '::12.34.56.78', 5432),
450            ('http://[::ffff:12.34.56.78]:5432/foo/',
451             '::ffff:12.34.56.78', 5432),
452            ('http://Test.python.org/foo/', 'test.python.org', None),
453            ('http://12.34.56.78/foo/', '12.34.56.78', None),
454            ('http://[::1]/foo/', '::1', None),
455            ('http://[dead:beef::1]/foo/', 'dead:beef::1', None),
456            ('http://[dead:beef::]/foo/', 'dead:beef::', None),
457            ('http://[dead:beef:cafe:5417:affe:8FA3:deaf:feed]/foo/',
458             'dead:beef:cafe:5417:affe:8fa3:deaf:feed', None),
459            ('http://[::12.34.56.78]/foo/', '::12.34.56.78', None),
460            ('http://[::ffff:12.34.56.78]/foo/',
461             '::ffff:12.34.56.78', None),
462            ('http://Test.python.org:/foo/', 'test.python.org', None),
463            ('http://12.34.56.78:/foo/', '12.34.56.78', None),
464            ('http://[::1]:/foo/', '::1', None),
465            ('http://[dead:beef::1]:/foo/', 'dead:beef::1', None),
466            ('http://[dead:beef::]:/foo/', 'dead:beef::', None),
467            ('http://[dead:beef:cafe:5417:affe:8FA3:deaf:feed]:/foo/',
468             'dead:beef:cafe:5417:affe:8fa3:deaf:feed', None),
469            ('http://[::12.34.56.78]:/foo/', '::12.34.56.78', None),
470            ('http://[::ffff:12.34.56.78]:/foo/',
471             '::ffff:12.34.56.78', None),
472            ]
473        def _encode(t):
474            return t[0].encode('ascii'), t[1].encode('ascii'), t[2]
475        bytes_cases = [_encode(x) for x in str_cases]
476        for url, hostname, port in str_cases + bytes_cases:
477            urlparsed = urllib.parse.urlparse(url)
478            self.assertEqual((urlparsed.hostname, urlparsed.port) , (hostname, port))
479
480        str_cases = [
481                'http://::12.34.56.78]/',
482                'http://[::1/foo/',
483                'ftp://[::1/foo/bad]/bad',
484                'http://[::1/foo/bad]/bad',
485                'http://[::ffff:12.34.56.78']
486        bytes_cases = [x.encode('ascii') for x in str_cases]
487        for invalid_url in str_cases + bytes_cases:
488            self.assertRaises(ValueError, urllib.parse.urlparse, invalid_url)
489
490    def test_urldefrag(self):
491        str_cases = [
492            ('http://python.org#frag', 'http://python.org', 'frag'),
493            ('http://python.org', 'http://python.org', ''),
494            ('http://python.org/#frag', 'http://python.org/', 'frag'),
495            ('http://python.org/', 'http://python.org/', ''),
496            ('http://python.org/?q#frag', 'http://python.org/?q', 'frag'),
497            ('http://python.org/?q', 'http://python.org/?q', ''),
498            ('http://python.org/p#frag', 'http://python.org/p', 'frag'),
499            ('http://python.org/p?q', 'http://python.org/p?q', ''),
500            (RFC1808_BASE, 'http://a/b/c/d;p?q', 'f'),
501            (RFC2396_BASE, 'http://a/b/c/d;p?q', ''),
502        ]
503        def _encode(t):
504            return type(t)(x.encode('ascii') for x in t)
505        bytes_cases = [_encode(x) for x in str_cases]
506        for url, defrag, frag in str_cases + bytes_cases:
507            result = urllib.parse.urldefrag(url)
508            self.assertEqual(result.geturl(), url)
509            self.assertEqual(result, (defrag, frag))
510            self.assertEqual(result.url, defrag)
511            self.assertEqual(result.fragment, frag)
512
513    def test_urlsplit_scoped_IPv6(self):
514        p = urllib.parse.urlsplit('http://[FE80::822a:a8ff:fe49:470c%tESt]:1234')
515        self.assertEqual(p.hostname, "fe80::822a:a8ff:fe49:470c%tESt")
516        self.assertEqual(p.netloc, '[FE80::822a:a8ff:fe49:470c%tESt]:1234')
517
518        p = urllib.parse.urlsplit(b'http://[FE80::822a:a8ff:fe49:470c%tESt]:1234')
519        self.assertEqual(p.hostname, b"fe80::822a:a8ff:fe49:470c%tESt")
520        self.assertEqual(p.netloc, b'[FE80::822a:a8ff:fe49:470c%tESt]:1234')
521
522    def test_urlsplit_attributes(self):
523        url = "HTTP://WWW.PYTHON.ORG/doc/#frag"
524        p = urllib.parse.urlsplit(url)
525        self.assertEqual(p.scheme, "http")
526        self.assertEqual(p.netloc, "WWW.PYTHON.ORG")
527        self.assertEqual(p.path, "/doc/")
528        self.assertEqual(p.query, "")
529        self.assertEqual(p.fragment, "frag")
530        self.assertEqual(p.username, None)
531        self.assertEqual(p.password, None)
532        self.assertEqual(p.hostname, "www.python.org")
533        self.assertEqual(p.port, None)
534        # geturl() won't return exactly the original URL in this case
535        # since the scheme is always case-normalized
536        # We handle this by ignoring the first 4 characters of the URL
537        self.assertEqual(p.geturl()[4:], url[4:])
538
539        url = "http://User:Pass@www.python.org:080/doc/?query=yes#frag"
540        p = urllib.parse.urlsplit(url)
541        self.assertEqual(p.scheme, "http")
542        self.assertEqual(p.netloc, "User:Pass@www.python.org:080")
543        self.assertEqual(p.path, "/doc/")
544        self.assertEqual(p.query, "query=yes")
545        self.assertEqual(p.fragment, "frag")
546        self.assertEqual(p.username, "User")
547        self.assertEqual(p.password, "Pass")
548        self.assertEqual(p.hostname, "www.python.org")
549        self.assertEqual(p.port, 80)
550        self.assertEqual(p.geturl(), url)
551
552        # Addressing issue1698, which suggests Username can contain
553        # "@" characters.  Though not RFC compliant, many ftp sites allow
554        # and request email addresses as usernames.
555
556        url = "http://User@example.com:Pass@www.python.org:080/doc/?query=yes#frag"
557        p = urllib.parse.urlsplit(url)
558        self.assertEqual(p.scheme, "http")
559        self.assertEqual(p.netloc, "User@example.com:Pass@www.python.org:080")
560        self.assertEqual(p.path, "/doc/")
561        self.assertEqual(p.query, "query=yes")
562        self.assertEqual(p.fragment, "frag")
563        self.assertEqual(p.username, "User@example.com")
564        self.assertEqual(p.password, "Pass")
565        self.assertEqual(p.hostname, "www.python.org")
566        self.assertEqual(p.port, 80)
567        self.assertEqual(p.geturl(), url)
568
569        # And check them all again, only with bytes this time
570        url = b"HTTP://WWW.PYTHON.ORG/doc/#frag"
571        p = urllib.parse.urlsplit(url)
572        self.assertEqual(p.scheme, b"http")
573        self.assertEqual(p.netloc, b"WWW.PYTHON.ORG")
574        self.assertEqual(p.path, b"/doc/")
575        self.assertEqual(p.query, b"")
576        self.assertEqual(p.fragment, b"frag")
577        self.assertEqual(p.username, None)
578        self.assertEqual(p.password, None)
579        self.assertEqual(p.hostname, b"www.python.org")
580        self.assertEqual(p.port, None)
581        self.assertEqual(p.geturl()[4:], url[4:])
582
583        url = b"http://User:Pass@www.python.org:080/doc/?query=yes#frag"
584        p = urllib.parse.urlsplit(url)
585        self.assertEqual(p.scheme, b"http")
586        self.assertEqual(p.netloc, b"User:Pass@www.python.org:080")
587        self.assertEqual(p.path, b"/doc/")
588        self.assertEqual(p.query, b"query=yes")
589        self.assertEqual(p.fragment, b"frag")
590        self.assertEqual(p.username, b"User")
591        self.assertEqual(p.password, b"Pass")
592        self.assertEqual(p.hostname, b"www.python.org")
593        self.assertEqual(p.port, 80)
594        self.assertEqual(p.geturl(), url)
595
596        url = b"http://User@example.com:Pass@www.python.org:080/doc/?query=yes#frag"
597        p = urllib.parse.urlsplit(url)
598        self.assertEqual(p.scheme, b"http")
599        self.assertEqual(p.netloc, b"User@example.com:Pass@www.python.org:080")
600        self.assertEqual(p.path, b"/doc/")
601        self.assertEqual(p.query, b"query=yes")
602        self.assertEqual(p.fragment, b"frag")
603        self.assertEqual(p.username, b"User@example.com")
604        self.assertEqual(p.password, b"Pass")
605        self.assertEqual(p.hostname, b"www.python.org")
606        self.assertEqual(p.port, 80)
607        self.assertEqual(p.geturl(), url)
608
609        # Verify an illegal port raises ValueError
610        url = b"HTTP://WWW.PYTHON.ORG:65536/doc/#frag"
611        p = urllib.parse.urlsplit(url)
612        with self.assertRaisesRegex(ValueError, "out of range"):
613            p.port
614
615    def test_urlsplit_remove_unsafe_bytes(self):
616        # Remove ASCII tabs and newlines from input
617        url = "http\t://www.python\n.org\t/java\nscript:\talert('msg\r\n')/?query\n=\tsomething#frag\nment"
618        p = urllib.parse.urlsplit(url)
619        self.assertEqual(p.scheme, "http")
620        self.assertEqual(p.netloc, "www.python.org")
621        self.assertEqual(p.path, "/javascript:alert('msg')/")
622        self.assertEqual(p.query, "query=something")
623        self.assertEqual(p.fragment, "fragment")
624        self.assertEqual(p.username, None)
625        self.assertEqual(p.password, None)
626        self.assertEqual(p.hostname, "www.python.org")
627        self.assertEqual(p.port, None)
628        self.assertEqual(p.geturl(), "http://www.python.org/javascript:alert('msg')/?query=something#fragment")
629
630        # Remove ASCII tabs and newlines from input as bytes.
631        url = b"http\t://www.python\n.org\t/java\nscript:\talert('msg\r\n')/?query\n=\tsomething#frag\nment"
632        p = urllib.parse.urlsplit(url)
633        self.assertEqual(p.scheme, b"http")
634        self.assertEqual(p.netloc, b"www.python.org")
635        self.assertEqual(p.path, b"/javascript:alert('msg')/")
636        self.assertEqual(p.query, b"query=something")
637        self.assertEqual(p.fragment, b"fragment")
638        self.assertEqual(p.username, None)
639        self.assertEqual(p.password, None)
640        self.assertEqual(p.hostname, b"www.python.org")
641        self.assertEqual(p.port, None)
642        self.assertEqual(p.geturl(), b"http://www.python.org/javascript:alert('msg')/?query=something#fragment")
643
644        # with scheme as cache-key
645        url = "http://www.python.org/java\nscript:\talert('msg\r\n')/?query\n=\tsomething#frag\nment"
646        scheme = "ht\ntp"
647        for _ in range(2):
648            p = urllib.parse.urlsplit(url, scheme=scheme)
649            self.assertEqual(p.scheme, "http")
650            self.assertEqual(p.geturl(), "http://www.python.org/javascript:alert('msg')/?query=something#fragment")
651
652    def test_attributes_bad_port(self):
653        """Check handling of invalid ports."""
654        for bytes in (False, True):
655            for parse in (urllib.parse.urlsplit, urllib.parse.urlparse):
656                for port in ("foo", "1.5", "-1", "0x10"):
657                    with self.subTest(bytes=bytes, parse=parse, port=port):
658                        netloc = "www.example.net:" + port
659                        url = "http://" + netloc
660                        if bytes:
661                            netloc = netloc.encode("ascii")
662                            url = url.encode("ascii")
663                        p = parse(url)
664                        self.assertEqual(p.netloc, netloc)
665                        with self.assertRaises(ValueError):
666                            p.port
667
668    def test_attributes_bad_scheme(self):
669        """Check handling of invalid schemes."""
670        for bytes in (False, True):
671            for parse in (urllib.parse.urlsplit, urllib.parse.urlparse):
672                for scheme in (".", "+", "-", "0", "http&", "६http"):
673                    with self.subTest(bytes=bytes, parse=parse, scheme=scheme):
674                        url = scheme + "://www.example.net"
675                        if bytes:
676                            if url.isascii():
677                                url = url.encode("ascii")
678                            else:
679                                continue
680                        p = parse(url)
681                        if bytes:
682                            self.assertEqual(p.scheme, b"")
683                        else:
684                            self.assertEqual(p.scheme, "")
685
686    def test_attributes_without_netloc(self):
687        # This example is straight from RFC 3261.  It looks like it
688        # should allow the username, hostname, and port to be filled
689        # in, but doesn't.  Since it's a URI and doesn't use the
690        # scheme://netloc syntax, the netloc and related attributes
691        # should be left empty.
692        uri = "sip:alice@atlanta.com;maddr=239.255.255.1;ttl=15"
693        p = urllib.parse.urlsplit(uri)
694        self.assertEqual(p.netloc, "")
695        self.assertEqual(p.username, None)
696        self.assertEqual(p.password, None)
697        self.assertEqual(p.hostname, None)
698        self.assertEqual(p.port, None)
699        self.assertEqual(p.geturl(), uri)
700
701        p = urllib.parse.urlparse(uri)
702        self.assertEqual(p.netloc, "")
703        self.assertEqual(p.username, None)
704        self.assertEqual(p.password, None)
705        self.assertEqual(p.hostname, None)
706        self.assertEqual(p.port, None)
707        self.assertEqual(p.geturl(), uri)
708
709        # You guessed it, repeating the test with bytes input
710        uri = b"sip:alice@atlanta.com;maddr=239.255.255.1;ttl=15"
711        p = urllib.parse.urlsplit(uri)
712        self.assertEqual(p.netloc, b"")
713        self.assertEqual(p.username, None)
714        self.assertEqual(p.password, None)
715        self.assertEqual(p.hostname, None)
716        self.assertEqual(p.port, None)
717        self.assertEqual(p.geturl(), uri)
718
719        p = urllib.parse.urlparse(uri)
720        self.assertEqual(p.netloc, b"")
721        self.assertEqual(p.username, None)
722        self.assertEqual(p.password, None)
723        self.assertEqual(p.hostname, None)
724        self.assertEqual(p.port, None)
725        self.assertEqual(p.geturl(), uri)
726
727    def test_noslash(self):
728        # Issue 1637: http://foo.com?query is legal
729        self.assertEqual(urllib.parse.urlparse("http://example.com?blahblah=/foo"),
730                         ('http', 'example.com', '', '', 'blahblah=/foo', ''))
731        self.assertEqual(urllib.parse.urlparse(b"http://example.com?blahblah=/foo"),
732                         (b'http', b'example.com', b'', b'', b'blahblah=/foo', b''))
733
734    def test_withoutscheme(self):
735        # Test urlparse without scheme
736        # Issue 754016: urlparse goes wrong with IP:port without scheme
737        # RFC 1808 specifies that netloc should start with //, urlparse expects
738        # the same, otherwise it classifies the portion of url as path.
739        self.assertEqual(urllib.parse.urlparse("path"),
740                ('','','path','','',''))
741        self.assertEqual(urllib.parse.urlparse("//www.python.org:80"),
742                ('','www.python.org:80','','','',''))
743        self.assertEqual(urllib.parse.urlparse("http://www.python.org:80"),
744                ('http','www.python.org:80','','','',''))
745        # Repeat for bytes input
746        self.assertEqual(urllib.parse.urlparse(b"path"),
747                (b'',b'',b'path',b'',b'',b''))
748        self.assertEqual(urllib.parse.urlparse(b"//www.python.org:80"),
749                (b'',b'www.python.org:80',b'',b'',b'',b''))
750        self.assertEqual(urllib.parse.urlparse(b"http://www.python.org:80"),
751                (b'http',b'www.python.org:80',b'',b'',b'',b''))
752
753    def test_portseparator(self):
754        # Issue 754016 makes changes for port separator ':' from scheme separator
755        self.assertEqual(urllib.parse.urlparse("http:80"), ('http','','80','','',''))
756        self.assertEqual(urllib.parse.urlparse("https:80"), ('https','','80','','',''))
757        self.assertEqual(urllib.parse.urlparse("path:80"), ('path','','80','','',''))
758        self.assertEqual(urllib.parse.urlparse("http:"),('http','','','','',''))
759        self.assertEqual(urllib.parse.urlparse("https:"),('https','','','','',''))
760        self.assertEqual(urllib.parse.urlparse("http://www.python.org:80"),
761                ('http','www.python.org:80','','','',''))
762        # As usual, need to check bytes input as well
763        self.assertEqual(urllib.parse.urlparse(b"http:80"), (b'http',b'',b'80',b'',b'',b''))
764        self.assertEqual(urllib.parse.urlparse(b"https:80"), (b'https',b'',b'80',b'',b'',b''))
765        self.assertEqual(urllib.parse.urlparse(b"path:80"), (b'path',b'',b'80',b'',b'',b''))
766        self.assertEqual(urllib.parse.urlparse(b"http:"),(b'http',b'',b'',b'',b'',b''))
767        self.assertEqual(urllib.parse.urlparse(b"https:"),(b'https',b'',b'',b'',b'',b''))
768        self.assertEqual(urllib.parse.urlparse(b"http://www.python.org:80"),
769                (b'http',b'www.python.org:80',b'',b'',b'',b''))
770
771    def test_usingsys(self):
772        # Issue 3314: sys module is used in the error
773        self.assertRaises(TypeError, urllib.parse.urlencode, "foo")
774
775    def test_anyscheme(self):
776        # Issue 7904: s3://foo.com/stuff has netloc "foo.com".
777        self.assertEqual(urllib.parse.urlparse("s3://foo.com/stuff"),
778                         ('s3', 'foo.com', '/stuff', '', '', ''))
779        self.assertEqual(urllib.parse.urlparse("x-newscheme://foo.com/stuff"),
780                         ('x-newscheme', 'foo.com', '/stuff', '', '', ''))
781        self.assertEqual(urllib.parse.urlparse("x-newscheme://foo.com/stuff?query#fragment"),
782                         ('x-newscheme', 'foo.com', '/stuff', '', 'query', 'fragment'))
783        self.assertEqual(urllib.parse.urlparse("x-newscheme://foo.com/stuff?query"),
784                         ('x-newscheme', 'foo.com', '/stuff', '', 'query', ''))
785
786        # And for bytes...
787        self.assertEqual(urllib.parse.urlparse(b"s3://foo.com/stuff"),
788                         (b's3', b'foo.com', b'/stuff', b'', b'', b''))
789        self.assertEqual(urllib.parse.urlparse(b"x-newscheme://foo.com/stuff"),
790                         (b'x-newscheme', b'foo.com', b'/stuff', b'', b'', b''))
791        self.assertEqual(urllib.parse.urlparse(b"x-newscheme://foo.com/stuff?query#fragment"),
792                         (b'x-newscheme', b'foo.com', b'/stuff', b'', b'query', b'fragment'))
793        self.assertEqual(urllib.parse.urlparse(b"x-newscheme://foo.com/stuff?query"),
794                         (b'x-newscheme', b'foo.com', b'/stuff', b'', b'query', b''))
795
796    def test_default_scheme(self):
797        # Exercise the scheme parameter of urlparse() and urlsplit()
798        for func in (urllib.parse.urlparse, urllib.parse.urlsplit):
799            with self.subTest(function=func):
800                result = func("http://example.net/", "ftp")
801                self.assertEqual(result.scheme, "http")
802                result = func(b"http://example.net/", b"ftp")
803                self.assertEqual(result.scheme, b"http")
804                self.assertEqual(func("path", "ftp").scheme, "ftp")
805                self.assertEqual(func("path", scheme="ftp").scheme, "ftp")
806                self.assertEqual(func(b"path", scheme=b"ftp").scheme, b"ftp")
807                self.assertEqual(func("path").scheme, "")
808                self.assertEqual(func(b"path").scheme, b"")
809                self.assertEqual(func(b"path", "").scheme, b"")
810
811    def test_parse_fragments(self):
812        # Exercise the allow_fragments parameter of urlparse() and urlsplit()
813        tests = (
814            ("http:#frag", "path", "frag"),
815            ("//example.net#frag", "path", "frag"),
816            ("index.html#frag", "path", "frag"),
817            (";a=b#frag", "params", "frag"),
818            ("?a=b#frag", "query", "frag"),
819            ("#frag", "path", "frag"),
820            ("abc#@frag", "path", "@frag"),
821            ("//abc#@frag", "path", "@frag"),
822            ("//abc:80#@frag", "path", "@frag"),
823            ("//abc#@frag:80", "path", "@frag:80"),
824        )
825        for url, attr, expected_frag in tests:
826            for func in (urllib.parse.urlparse, urllib.parse.urlsplit):
827                if attr == "params" and func is urllib.parse.urlsplit:
828                    attr = "path"
829                with self.subTest(url=url, function=func):
830                    result = func(url, allow_fragments=False)
831                    self.assertEqual(result.fragment, "")
832                    self.assertTrue(
833                            getattr(result, attr).endswith("#" + expected_frag))
834                    self.assertEqual(func(url, "", False).fragment, "")
835
836                    result = func(url, allow_fragments=True)
837                    self.assertEqual(result.fragment, expected_frag)
838                    self.assertFalse(
839                            getattr(result, attr).endswith(expected_frag))
840                    self.assertEqual(func(url, "", True).fragment,
841                                     expected_frag)
842                    self.assertEqual(func(url).fragment, expected_frag)
843
844    def test_mixed_types_rejected(self):
845        # Several functions that process either strings or ASCII encoded bytes
846        # accept multiple arguments. Check they reject mixed type input
847        with self.assertRaisesRegex(TypeError, "Cannot mix str"):
848            urllib.parse.urlparse("www.python.org", b"http")
849        with self.assertRaisesRegex(TypeError, "Cannot mix str"):
850            urllib.parse.urlparse(b"www.python.org", "http")
851        with self.assertRaisesRegex(TypeError, "Cannot mix str"):
852            urllib.parse.urlsplit("www.python.org", b"http")
853        with self.assertRaisesRegex(TypeError, "Cannot mix str"):
854            urllib.parse.urlsplit(b"www.python.org", "http")
855        with self.assertRaisesRegex(TypeError, "Cannot mix str"):
856            urllib.parse.urlunparse(( b"http", "www.python.org","","","",""))
857        with self.assertRaisesRegex(TypeError, "Cannot mix str"):
858            urllib.parse.urlunparse(("http", b"www.python.org","","","",""))
859        with self.assertRaisesRegex(TypeError, "Cannot mix str"):
860            urllib.parse.urlunsplit((b"http", "www.python.org","","",""))
861        with self.assertRaisesRegex(TypeError, "Cannot mix str"):
862            urllib.parse.urlunsplit(("http", b"www.python.org","","",""))
863        with self.assertRaisesRegex(TypeError, "Cannot mix str"):
864            urllib.parse.urljoin("http://python.org", b"http://python.org")
865        with self.assertRaisesRegex(TypeError, "Cannot mix str"):
866            urllib.parse.urljoin(b"http://python.org", "http://python.org")
867
868    def _check_result_type(self, str_type):
869        num_args = len(str_type._fields)
870        bytes_type = str_type._encoded_counterpart
871        self.assertIs(bytes_type._decoded_counterpart, str_type)
872        str_args = ('',) * num_args
873        bytes_args = (b'',) * num_args
874        str_result = str_type(*str_args)
875        bytes_result = bytes_type(*bytes_args)
876        encoding = 'ascii'
877        errors = 'strict'
878        self.assertEqual(str_result, str_args)
879        self.assertEqual(bytes_result.decode(), str_args)
880        self.assertEqual(bytes_result.decode(), str_result)
881        self.assertEqual(bytes_result.decode(encoding), str_args)
882        self.assertEqual(bytes_result.decode(encoding), str_result)
883        self.assertEqual(bytes_result.decode(encoding, errors), str_args)
884        self.assertEqual(bytes_result.decode(encoding, errors), str_result)
885        self.assertEqual(bytes_result, bytes_args)
886        self.assertEqual(str_result.encode(), bytes_args)
887        self.assertEqual(str_result.encode(), bytes_result)
888        self.assertEqual(str_result.encode(encoding), bytes_args)
889        self.assertEqual(str_result.encode(encoding), bytes_result)
890        self.assertEqual(str_result.encode(encoding, errors), bytes_args)
891        self.assertEqual(str_result.encode(encoding, errors), bytes_result)
892
893    def test_result_pairs(self):
894        # Check encoding and decoding between result pairs
895        result_types = [
896          urllib.parse.DefragResult,
897          urllib.parse.SplitResult,
898          urllib.parse.ParseResult,
899        ]
900        for result_type in result_types:
901            self._check_result_type(result_type)
902
903    def test_parse_qs_encoding(self):
904        result = urllib.parse.parse_qs("key=\u0141%E9", encoding="latin-1")
905        self.assertEqual(result, {'key': ['\u0141\xE9']})
906        result = urllib.parse.parse_qs("key=\u0141%C3%A9", encoding="utf-8")
907        self.assertEqual(result, {'key': ['\u0141\xE9']})
908        result = urllib.parse.parse_qs("key=\u0141%C3%A9", encoding="ascii")
909        self.assertEqual(result, {'key': ['\u0141\ufffd\ufffd']})
910        result = urllib.parse.parse_qs("key=\u0141%E9-", encoding="ascii")
911        self.assertEqual(result, {'key': ['\u0141\ufffd-']})
912        result = urllib.parse.parse_qs("key=\u0141%E9-", encoding="ascii",
913                                                          errors="ignore")
914        self.assertEqual(result, {'key': ['\u0141-']})
915
916    def test_parse_qsl_encoding(self):
917        result = urllib.parse.parse_qsl("key=\u0141%E9", encoding="latin-1")
918        self.assertEqual(result, [('key', '\u0141\xE9')])
919        result = urllib.parse.parse_qsl("key=\u0141%C3%A9", encoding="utf-8")
920        self.assertEqual(result, [('key', '\u0141\xE9')])
921        result = urllib.parse.parse_qsl("key=\u0141%C3%A9", encoding="ascii")
922        self.assertEqual(result, [('key', '\u0141\ufffd\ufffd')])
923        result = urllib.parse.parse_qsl("key=\u0141%E9-", encoding="ascii")
924        self.assertEqual(result, [('key', '\u0141\ufffd-')])
925        result = urllib.parse.parse_qsl("key=\u0141%E9-", encoding="ascii",
926                                                          errors="ignore")
927        self.assertEqual(result, [('key', '\u0141-')])
928
929    def test_parse_qsl_max_num_fields(self):
930        with self.assertRaises(ValueError):
931            urllib.parse.parse_qs('&'.join(['a=a']*11), max_num_fields=10)
932        urllib.parse.parse_qs('&'.join(['a=a']*10), max_num_fields=10)
933
934    def test_parse_qs_separator(self):
935        parse_qs_semicolon_cases = [
936            (";", {}),
937            (";;", {}),
938            (";a=b", {'a': ['b']}),
939            ("a=a+b;b=b+c", {'a': ['a b'], 'b': ['b c']}),
940            ("a=1;a=2", {'a': ['1', '2']}),
941            (b";", {}),
942            (b";;", {}),
943            (b";a=b", {b'a': [b'b']}),
944            (b"a=a+b;b=b+c", {b'a': [b'a b'], b'b': [b'b c']}),
945            (b"a=1;a=2", {b'a': [b'1', b'2']}),
946        ]
947        for orig, expect in parse_qs_semicolon_cases:
948            with self.subTest(f"Original: {orig!r}, Expected: {expect!r}"):
949                result = urllib.parse.parse_qs(orig, separator=';')
950                self.assertEqual(result, expect, "Error parsing %r" % orig)
951                result_bytes = urllib.parse.parse_qs(orig, separator=b';')
952                self.assertEqual(result_bytes, expect, "Error parsing %r" % orig)
953
954
955    def test_parse_qsl_separator(self):
956        parse_qsl_semicolon_cases = [
957            (";", []),
958            (";;", []),
959            (";a=b", [('a', 'b')]),
960            ("a=a+b;b=b+c", [('a', 'a b'), ('b', 'b c')]),
961            ("a=1;a=2", [('a', '1'), ('a', '2')]),
962            (b";", []),
963            (b";;", []),
964            (b";a=b", [(b'a', b'b')]),
965            (b"a=a+b;b=b+c", [(b'a', b'a b'), (b'b', b'b c')]),
966            (b"a=1;a=2", [(b'a', b'1'), (b'a', b'2')]),
967        ]
968        for orig, expect in parse_qsl_semicolon_cases:
969            with self.subTest(f"Original: {orig!r}, Expected: {expect!r}"):
970                result = urllib.parse.parse_qsl(orig, separator=';')
971                self.assertEqual(result, expect, "Error parsing %r" % orig)
972                result_bytes = urllib.parse.parse_qsl(orig, separator=b';')
973                self.assertEqual(result_bytes, expect, "Error parsing %r" % orig)
974
975
976    def test_urlencode_sequences(self):
977        # Other tests incidentally urlencode things; test non-covered cases:
978        # Sequence and object values.
979        result = urllib.parse.urlencode({'a': [1, 2], 'b': (3, 4, 5)}, True)
980        # we cannot rely on ordering here
981        assert set(result.split('&')) == {'a=1', 'a=2', 'b=3', 'b=4', 'b=5'}
982
983        class Trivial:
984            def __str__(self):
985                return 'trivial'
986
987        result = urllib.parse.urlencode({'a': Trivial()}, True)
988        self.assertEqual(result, 'a=trivial')
989
990    def test_urlencode_quote_via(self):
991        result = urllib.parse.urlencode({'a': 'some value'})
992        self.assertEqual(result, "a=some+value")
993        result = urllib.parse.urlencode({'a': 'some value/another'},
994                                        quote_via=urllib.parse.quote)
995        self.assertEqual(result, "a=some%20value%2Fanother")
996        result = urllib.parse.urlencode({'a': 'some value/another'},
997                                        safe='/', quote_via=urllib.parse.quote)
998        self.assertEqual(result, "a=some%20value/another")
999
1000    def test_quote_from_bytes(self):
1001        self.assertRaises(TypeError, urllib.parse.quote_from_bytes, 'foo')
1002        result = urllib.parse.quote_from_bytes(b'archaeological arcana')
1003        self.assertEqual(result, 'archaeological%20arcana')
1004        result = urllib.parse.quote_from_bytes(b'')
1005        self.assertEqual(result, '')
1006
1007    def test_unquote_to_bytes(self):
1008        result = urllib.parse.unquote_to_bytes('abc%20def')
1009        self.assertEqual(result, b'abc def')
1010        result = urllib.parse.unquote_to_bytes('')
1011        self.assertEqual(result, b'')
1012
1013    def test_quote_errors(self):
1014        self.assertRaises(TypeError, urllib.parse.quote, b'foo',
1015                          encoding='utf-8')
1016        self.assertRaises(TypeError, urllib.parse.quote, b'foo', errors='strict')
1017
1018    def test_issue14072(self):
1019        p1 = urllib.parse.urlsplit('tel:+31-641044153')
1020        self.assertEqual(p1.scheme, 'tel')
1021        self.assertEqual(p1.path, '+31-641044153')
1022        p2 = urllib.parse.urlsplit('tel:+31641044153')
1023        self.assertEqual(p2.scheme, 'tel')
1024        self.assertEqual(p2.path, '+31641044153')
1025        # assert the behavior for urlparse
1026        p1 = urllib.parse.urlparse('tel:+31-641044153')
1027        self.assertEqual(p1.scheme, 'tel')
1028        self.assertEqual(p1.path, '+31-641044153')
1029        p2 = urllib.parse.urlparse('tel:+31641044153')
1030        self.assertEqual(p2.scheme, 'tel')
1031        self.assertEqual(p2.path, '+31641044153')
1032
1033    def test_port_casting_failure_message(self):
1034        message = "Port could not be cast to integer value as 'oracle'"
1035        p1 = urllib.parse.urlparse('http://Server=sde; Service=sde:oracle')
1036        with self.assertRaisesRegex(ValueError, message):
1037            p1.port
1038
1039        p2 = urllib.parse.urlsplit('http://Server=sde; Service=sde:oracle')
1040        with self.assertRaisesRegex(ValueError, message):
1041            p2.port
1042
1043    def test_telurl_params(self):
1044        p1 = urllib.parse.urlparse('tel:123-4;phone-context=+1-650-516')
1045        self.assertEqual(p1.scheme, 'tel')
1046        self.assertEqual(p1.path, '123-4')
1047        self.assertEqual(p1.params, 'phone-context=+1-650-516')
1048
1049        p1 = urllib.parse.urlparse('tel:+1-201-555-0123')
1050        self.assertEqual(p1.scheme, 'tel')
1051        self.assertEqual(p1.path, '+1-201-555-0123')
1052        self.assertEqual(p1.params, '')
1053
1054        p1 = urllib.parse.urlparse('tel:7042;phone-context=example.com')
1055        self.assertEqual(p1.scheme, 'tel')
1056        self.assertEqual(p1.path, '7042')
1057        self.assertEqual(p1.params, 'phone-context=example.com')
1058
1059        p1 = urllib.parse.urlparse('tel:863-1234;phone-context=+1-914-555')
1060        self.assertEqual(p1.scheme, 'tel')
1061        self.assertEqual(p1.path, '863-1234')
1062        self.assertEqual(p1.params, 'phone-context=+1-914-555')
1063
1064    def test_Quoter_repr(self):
1065        quoter = urllib.parse.Quoter(urllib.parse._ALWAYS_SAFE)
1066        self.assertIn('Quoter', repr(quoter))
1067
1068    def test_all(self):
1069        expected = []
1070        undocumented = {
1071            'splitattr', 'splithost', 'splitnport', 'splitpasswd',
1072            'splitport', 'splitquery', 'splittag', 'splittype', 'splituser',
1073            'splitvalue',
1074            'Quoter', 'ResultBase', 'clear_cache', 'to_bytes', 'unwrap',
1075        }
1076        for name in dir(urllib.parse):
1077            if name.startswith('_') or name in undocumented:
1078                continue
1079            object = getattr(urllib.parse, name)
1080            if getattr(object, '__module__', None) == 'urllib.parse':
1081                expected.append(name)
1082        self.assertCountEqual(urllib.parse.__all__, expected)
1083
1084    def test_urlsplit_normalization(self):
1085        # Certain characters should never occur in the netloc,
1086        # including under normalization.
1087        # Ensure that ALL of them are detected and cause an error
1088        illegal_chars = '/:#?@'
1089        hex_chars = {'{:04X}'.format(ord(c)) for c in illegal_chars}
1090        denorm_chars = [
1091            c for c in map(chr, range(128, sys.maxunicode))
1092            if (hex_chars & set(unicodedata.decomposition(c).split()))
1093            and c not in illegal_chars
1094        ]
1095        # Sanity check that we found at least one such character
1096        self.assertIn('\u2100', denorm_chars)
1097        self.assertIn('\uFF03', denorm_chars)
1098
1099        # bpo-36742: Verify port separators are ignored when they
1100        # existed prior to decomposition
1101        urllib.parse.urlsplit('http://\u30d5\u309a:80')
1102        with self.assertRaises(ValueError):
1103            urllib.parse.urlsplit('http://\u30d5\u309a\ufe1380')
1104
1105        for scheme in ["http", "https", "ftp"]:
1106            for netloc in ["netloc{}false.netloc", "n{}user@netloc"]:
1107                for c in denorm_chars:
1108                    url = "{}://{}/path".format(scheme, netloc.format(c))
1109                    with self.subTest(url=url, char='{:04X}'.format(ord(c))):
1110                        with self.assertRaises(ValueError):
1111                            urllib.parse.urlsplit(url)
1112
1113class Utility_Tests(unittest.TestCase):
1114    """Testcase to test the various utility functions in the urllib."""
1115    # In Python 2 this test class was in test_urllib.
1116
1117    def test_splittype(self):
1118        splittype = urllib.parse._splittype
1119        self.assertEqual(splittype('type:opaquestring'), ('type', 'opaquestring'))
1120        self.assertEqual(splittype('opaquestring'), (None, 'opaquestring'))
1121        self.assertEqual(splittype(':opaquestring'), (None, ':opaquestring'))
1122        self.assertEqual(splittype('type:'), ('type', ''))
1123        self.assertEqual(splittype('type:opaque:string'), ('type', 'opaque:string'))
1124
1125    def test_splithost(self):
1126        splithost = urllib.parse._splithost
1127        self.assertEqual(splithost('//www.example.org:80/foo/bar/baz.html'),
1128                         ('www.example.org:80', '/foo/bar/baz.html'))
1129        self.assertEqual(splithost('//www.example.org:80'),
1130                         ('www.example.org:80', ''))
1131        self.assertEqual(splithost('/foo/bar/baz.html'),
1132                         (None, '/foo/bar/baz.html'))
1133
1134        # bpo-30500: # starts a fragment.
1135        self.assertEqual(splithost('//127.0.0.1#@host.com'),
1136                         ('127.0.0.1', '/#@host.com'))
1137        self.assertEqual(splithost('//127.0.0.1#@host.com:80'),
1138                         ('127.0.0.1', '/#@host.com:80'))
1139        self.assertEqual(splithost('//127.0.0.1:80#@host.com'),
1140                         ('127.0.0.1:80', '/#@host.com'))
1141
1142        # Empty host is returned as empty string.
1143        self.assertEqual(splithost("///file"),
1144                         ('', '/file'))
1145
1146        # Trailing semicolon, question mark and hash symbol are kept.
1147        self.assertEqual(splithost("//example.net/file;"),
1148                         ('example.net', '/file;'))
1149        self.assertEqual(splithost("//example.net/file?"),
1150                         ('example.net', '/file?'))
1151        self.assertEqual(splithost("//example.net/file#"),
1152                         ('example.net', '/file#'))
1153
1154    def test_splituser(self):
1155        splituser = urllib.parse._splituser
1156        self.assertEqual(splituser('User:Pass@www.python.org:080'),
1157                         ('User:Pass', 'www.python.org:080'))
1158        self.assertEqual(splituser('@www.python.org:080'),
1159                         ('', 'www.python.org:080'))
1160        self.assertEqual(splituser('www.python.org:080'),
1161                         (None, 'www.python.org:080'))
1162        self.assertEqual(splituser('User:Pass@'),
1163                         ('User:Pass', ''))
1164        self.assertEqual(splituser('User@example.com:Pass@www.python.org:080'),
1165                         ('User@example.com:Pass', 'www.python.org:080'))
1166
1167    def test_splitpasswd(self):
1168        # Some of the password examples are not sensible, but it is added to
1169        # confirming to RFC2617 and addressing issue4675.
1170        splitpasswd = urllib.parse._splitpasswd
1171        self.assertEqual(splitpasswd('user:ab'), ('user', 'ab'))
1172        self.assertEqual(splitpasswd('user:a\nb'), ('user', 'a\nb'))
1173        self.assertEqual(splitpasswd('user:a\tb'), ('user', 'a\tb'))
1174        self.assertEqual(splitpasswd('user:a\rb'), ('user', 'a\rb'))
1175        self.assertEqual(splitpasswd('user:a\fb'), ('user', 'a\fb'))
1176        self.assertEqual(splitpasswd('user:a\vb'), ('user', 'a\vb'))
1177        self.assertEqual(splitpasswd('user:a:b'), ('user', 'a:b'))
1178        self.assertEqual(splitpasswd('user:a b'), ('user', 'a b'))
1179        self.assertEqual(splitpasswd('user 2:ab'), ('user 2', 'ab'))
1180        self.assertEqual(splitpasswd('user+1:a+b'), ('user+1', 'a+b'))
1181        self.assertEqual(splitpasswd('user:'), ('user', ''))
1182        self.assertEqual(splitpasswd('user'), ('user', None))
1183        self.assertEqual(splitpasswd(':ab'), ('', 'ab'))
1184
1185    def test_splitport(self):
1186        splitport = urllib.parse._splitport
1187        self.assertEqual(splitport('parrot:88'), ('parrot', '88'))
1188        self.assertEqual(splitport('parrot'), ('parrot', None))
1189        self.assertEqual(splitport('parrot:'), ('parrot', None))
1190        self.assertEqual(splitport('127.0.0.1'), ('127.0.0.1', None))
1191        self.assertEqual(splitport('parrot:cheese'), ('parrot:cheese', None))
1192        self.assertEqual(splitport('[::1]:88'), ('[::1]', '88'))
1193        self.assertEqual(splitport('[::1]'), ('[::1]', None))
1194        self.assertEqual(splitport(':88'), ('', '88'))
1195
1196    def test_splitnport(self):
1197        splitnport = urllib.parse._splitnport
1198        self.assertEqual(splitnport('parrot:88'), ('parrot', 88))
1199        self.assertEqual(splitnport('parrot'), ('parrot', -1))
1200        self.assertEqual(splitnport('parrot', 55), ('parrot', 55))
1201        self.assertEqual(splitnport('parrot:'), ('parrot', -1))
1202        self.assertEqual(splitnport('parrot:', 55), ('parrot', 55))
1203        self.assertEqual(splitnport('127.0.0.1'), ('127.0.0.1', -1))
1204        self.assertEqual(splitnport('127.0.0.1', 55), ('127.0.0.1', 55))
1205        self.assertEqual(splitnport('parrot:cheese'), ('parrot', None))
1206        self.assertEqual(splitnport('parrot:cheese', 55), ('parrot', None))
1207
1208    def test_splitquery(self):
1209        # Normal cases are exercised by other tests; ensure that we also
1210        # catch cases with no port specified (testcase ensuring coverage)
1211        splitquery = urllib.parse._splitquery
1212        self.assertEqual(splitquery('http://python.org/fake?foo=bar'),
1213                         ('http://python.org/fake', 'foo=bar'))
1214        self.assertEqual(splitquery('http://python.org/fake?foo=bar?'),
1215                         ('http://python.org/fake?foo=bar', ''))
1216        self.assertEqual(splitquery('http://python.org/fake'),
1217                         ('http://python.org/fake', None))
1218        self.assertEqual(splitquery('?foo=bar'), ('', 'foo=bar'))
1219
1220    def test_splittag(self):
1221        splittag = urllib.parse._splittag
1222        self.assertEqual(splittag('http://example.com?foo=bar#baz'),
1223                         ('http://example.com?foo=bar', 'baz'))
1224        self.assertEqual(splittag('http://example.com?foo=bar#'),
1225                         ('http://example.com?foo=bar', ''))
1226        self.assertEqual(splittag('#baz'), ('', 'baz'))
1227        self.assertEqual(splittag('http://example.com?foo=bar'),
1228                         ('http://example.com?foo=bar', None))
1229        self.assertEqual(splittag('http://example.com?foo=bar#baz#boo'),
1230                         ('http://example.com?foo=bar#baz', 'boo'))
1231
1232    def test_splitattr(self):
1233        splitattr = urllib.parse._splitattr
1234        self.assertEqual(splitattr('/path;attr1=value1;attr2=value2'),
1235                         ('/path', ['attr1=value1', 'attr2=value2']))
1236        self.assertEqual(splitattr('/path;'), ('/path', ['']))
1237        self.assertEqual(splitattr(';attr1=value1;attr2=value2'),
1238                         ('', ['attr1=value1', 'attr2=value2']))
1239        self.assertEqual(splitattr('/path'), ('/path', []))
1240
1241    def test_splitvalue(self):
1242        # Normal cases are exercised by other tests; test pathological cases
1243        # with no key/value pairs. (testcase ensuring coverage)
1244        splitvalue = urllib.parse._splitvalue
1245        self.assertEqual(splitvalue('foo=bar'), ('foo', 'bar'))
1246        self.assertEqual(splitvalue('foo='), ('foo', ''))
1247        self.assertEqual(splitvalue('=bar'), ('', 'bar'))
1248        self.assertEqual(splitvalue('foobar'), ('foobar', None))
1249        self.assertEqual(splitvalue('foo=bar=baz'), ('foo', 'bar=baz'))
1250
1251    def test_to_bytes(self):
1252        result = urllib.parse._to_bytes('http://www.python.org')
1253        self.assertEqual(result, 'http://www.python.org')
1254        self.assertRaises(UnicodeError, urllib.parse._to_bytes,
1255                          'http://www.python.org/medi\u00e6val')
1256
1257    def test_unwrap(self):
1258        for wrapped_url in ('<URL:scheme://host/path>', '<scheme://host/path>',
1259                            'URL:scheme://host/path', 'scheme://host/path'):
1260            url = urllib.parse.unwrap(wrapped_url)
1261            self.assertEqual(url, 'scheme://host/path')
1262
1263
1264class DeprecationTest(unittest.TestCase):
1265
1266    def test_splittype_deprecation(self):
1267        with self.assertWarns(DeprecationWarning) as cm:
1268            urllib.parse.splittype('')
1269        self.assertEqual(str(cm.warning),
1270                         'urllib.parse.splittype() is deprecated as of 3.8, '
1271                         'use urllib.parse.urlparse() instead')
1272
1273    def test_splithost_deprecation(self):
1274        with self.assertWarns(DeprecationWarning) as cm:
1275            urllib.parse.splithost('')
1276        self.assertEqual(str(cm.warning),
1277                         'urllib.parse.splithost() is deprecated as of 3.8, '
1278                         'use urllib.parse.urlparse() instead')
1279
1280    def test_splituser_deprecation(self):
1281        with self.assertWarns(DeprecationWarning) as cm:
1282            urllib.parse.splituser('')
1283        self.assertEqual(str(cm.warning),
1284                         'urllib.parse.splituser() is deprecated as of 3.8, '
1285                         'use urllib.parse.urlparse() instead')
1286
1287    def test_splitpasswd_deprecation(self):
1288        with self.assertWarns(DeprecationWarning) as cm:
1289            urllib.parse.splitpasswd('')
1290        self.assertEqual(str(cm.warning),
1291                         'urllib.parse.splitpasswd() is deprecated as of 3.8, '
1292                         'use urllib.parse.urlparse() instead')
1293
1294    def test_splitport_deprecation(self):
1295        with self.assertWarns(DeprecationWarning) as cm:
1296            urllib.parse.splitport('')
1297        self.assertEqual(str(cm.warning),
1298                         'urllib.parse.splitport() is deprecated as of 3.8, '
1299                         'use urllib.parse.urlparse() instead')
1300
1301    def test_splitnport_deprecation(self):
1302        with self.assertWarns(DeprecationWarning) as cm:
1303            urllib.parse.splitnport('')
1304        self.assertEqual(str(cm.warning),
1305                         'urllib.parse.splitnport() is deprecated as of 3.8, '
1306                         'use urllib.parse.urlparse() instead')
1307
1308    def test_splitquery_deprecation(self):
1309        with self.assertWarns(DeprecationWarning) as cm:
1310            urllib.parse.splitquery('')
1311        self.assertEqual(str(cm.warning),
1312                         'urllib.parse.splitquery() is deprecated as of 3.8, '
1313                         'use urllib.parse.urlparse() instead')
1314
1315    def test_splittag_deprecation(self):
1316        with self.assertWarns(DeprecationWarning) as cm:
1317            urllib.parse.splittag('')
1318        self.assertEqual(str(cm.warning),
1319                         'urllib.parse.splittag() is deprecated as of 3.8, '
1320                         'use urllib.parse.urlparse() instead')
1321
1322    def test_splitattr_deprecation(self):
1323        with self.assertWarns(DeprecationWarning) as cm:
1324            urllib.parse.splitattr('')
1325        self.assertEqual(str(cm.warning),
1326                         'urllib.parse.splitattr() is deprecated as of 3.8, '
1327                         'use urllib.parse.urlparse() instead')
1328
1329    def test_splitvalue_deprecation(self):
1330        with self.assertWarns(DeprecationWarning) as cm:
1331            urllib.parse.splitvalue('')
1332        self.assertEqual(str(cm.warning),
1333                         'urllib.parse.splitvalue() is deprecated as of 3.8, '
1334                         'use urllib.parse.parse_qsl() instead')
1335
1336    def test_to_bytes_deprecation(self):
1337        with self.assertWarns(DeprecationWarning) as cm:
1338            urllib.parse.to_bytes('')
1339        self.assertEqual(str(cm.warning),
1340                         'urllib.parse.to_bytes() is deprecated as of 3.8')
1341
1342
1343if __name__ == "__main__":
1344    unittest.main()
1345