• 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_without_netloc(self):
669        # This example is straight from RFC 3261.  It looks like it
670        # should allow the username, hostname, and port to be filled
671        # in, but doesn't.  Since it's a URI and doesn't use the
672        # scheme://netloc syntax, the netloc and related attributes
673        # should be left empty.
674        uri = "sip:alice@atlanta.com;maddr=239.255.255.1;ttl=15"
675        p = urllib.parse.urlsplit(uri)
676        self.assertEqual(p.netloc, "")
677        self.assertEqual(p.username, None)
678        self.assertEqual(p.password, None)
679        self.assertEqual(p.hostname, None)
680        self.assertEqual(p.port, None)
681        self.assertEqual(p.geturl(), uri)
682
683        p = urllib.parse.urlparse(uri)
684        self.assertEqual(p.netloc, "")
685        self.assertEqual(p.username, None)
686        self.assertEqual(p.password, None)
687        self.assertEqual(p.hostname, None)
688        self.assertEqual(p.port, None)
689        self.assertEqual(p.geturl(), uri)
690
691        # You guessed it, repeating the test with bytes input
692        uri = b"sip:alice@atlanta.com;maddr=239.255.255.1;ttl=15"
693        p = urllib.parse.urlsplit(uri)
694        self.assertEqual(p.netloc, b"")
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, b"")
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    def test_noslash(self):
710        # Issue 1637: http://foo.com?query is legal
711        self.assertEqual(urllib.parse.urlparse("http://example.com?blahblah=/foo"),
712                         ('http', 'example.com', '', '', 'blahblah=/foo', ''))
713        self.assertEqual(urllib.parse.urlparse(b"http://example.com?blahblah=/foo"),
714                         (b'http', b'example.com', b'', b'', b'blahblah=/foo', b''))
715
716    def test_withoutscheme(self):
717        # Test urlparse without scheme
718        # Issue 754016: urlparse goes wrong with IP:port without scheme
719        # RFC 1808 specifies that netloc should start with //, urlparse expects
720        # the same, otherwise it classifies the portion of url as path.
721        self.assertEqual(urllib.parse.urlparse("path"),
722                ('','','path','','',''))
723        self.assertEqual(urllib.parse.urlparse("//www.python.org:80"),
724                ('','www.python.org:80','','','',''))
725        self.assertEqual(urllib.parse.urlparse("http://www.python.org:80"),
726                ('http','www.python.org:80','','','',''))
727        # Repeat for bytes input
728        self.assertEqual(urllib.parse.urlparse(b"path"),
729                (b'',b'',b'path',b'',b'',b''))
730        self.assertEqual(urllib.parse.urlparse(b"//www.python.org:80"),
731                (b'',b'www.python.org:80',b'',b'',b'',b''))
732        self.assertEqual(urllib.parse.urlparse(b"http://www.python.org:80"),
733                (b'http',b'www.python.org:80',b'',b'',b'',b''))
734
735    def test_portseparator(self):
736        # Issue 754016 makes changes for port separator ':' from scheme separator
737        self.assertEqual(urllib.parse.urlparse("http:80"), ('http','','80','','',''))
738        self.assertEqual(urllib.parse.urlparse("https:80"), ('https','','80','','',''))
739        self.assertEqual(urllib.parse.urlparse("path:80"), ('path','','80','','',''))
740        self.assertEqual(urllib.parse.urlparse("http:"),('http','','','','',''))
741        self.assertEqual(urllib.parse.urlparse("https:"),('https','','','','',''))
742        self.assertEqual(urllib.parse.urlparse("http://www.python.org:80"),
743                ('http','www.python.org:80','','','',''))
744        # As usual, need to check bytes input as well
745        self.assertEqual(urllib.parse.urlparse(b"http:80"), (b'http',b'',b'80',b'',b'',b''))
746        self.assertEqual(urllib.parse.urlparse(b"https:80"), (b'https',b'',b'80',b'',b'',b''))
747        self.assertEqual(urllib.parse.urlparse(b"path:80"), (b'path',b'',b'80',b'',b'',b''))
748        self.assertEqual(urllib.parse.urlparse(b"http:"),(b'http',b'',b'',b'',b'',b''))
749        self.assertEqual(urllib.parse.urlparse(b"https:"),(b'https',b'',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_usingsys(self):
754        # Issue 3314: sys module is used in the error
755        self.assertRaises(TypeError, urllib.parse.urlencode, "foo")
756
757    def test_anyscheme(self):
758        # Issue 7904: s3://foo.com/stuff has netloc "foo.com".
759        self.assertEqual(urllib.parse.urlparse("s3://foo.com/stuff"),
760                         ('s3', 'foo.com', '/stuff', '', '', ''))
761        self.assertEqual(urllib.parse.urlparse("x-newscheme://foo.com/stuff"),
762                         ('x-newscheme', 'foo.com', '/stuff', '', '', ''))
763        self.assertEqual(urllib.parse.urlparse("x-newscheme://foo.com/stuff?query#fragment"),
764                         ('x-newscheme', 'foo.com', '/stuff', '', 'query', 'fragment'))
765        self.assertEqual(urllib.parse.urlparse("x-newscheme://foo.com/stuff?query"),
766                         ('x-newscheme', 'foo.com', '/stuff', '', 'query', ''))
767
768        # And for bytes...
769        self.assertEqual(urllib.parse.urlparse(b"s3://foo.com/stuff"),
770                         (b's3', b'foo.com', b'/stuff', b'', b'', b''))
771        self.assertEqual(urllib.parse.urlparse(b"x-newscheme://foo.com/stuff"),
772                         (b'x-newscheme', b'foo.com', b'/stuff', b'', b'', b''))
773        self.assertEqual(urllib.parse.urlparse(b"x-newscheme://foo.com/stuff?query#fragment"),
774                         (b'x-newscheme', b'foo.com', b'/stuff', b'', b'query', b'fragment'))
775        self.assertEqual(urllib.parse.urlparse(b"x-newscheme://foo.com/stuff?query"),
776                         (b'x-newscheme', b'foo.com', b'/stuff', b'', b'query', b''))
777
778    def test_default_scheme(self):
779        # Exercise the scheme parameter of urlparse() and urlsplit()
780        for func in (urllib.parse.urlparse, urllib.parse.urlsplit):
781            with self.subTest(function=func):
782                result = func("http://example.net/", "ftp")
783                self.assertEqual(result.scheme, "http")
784                result = func(b"http://example.net/", b"ftp")
785                self.assertEqual(result.scheme, b"http")
786                self.assertEqual(func("path", "ftp").scheme, "ftp")
787                self.assertEqual(func("path", scheme="ftp").scheme, "ftp")
788                self.assertEqual(func(b"path", scheme=b"ftp").scheme, b"ftp")
789                self.assertEqual(func("path").scheme, "")
790                self.assertEqual(func(b"path").scheme, b"")
791                self.assertEqual(func(b"path", "").scheme, b"")
792
793    def test_parse_fragments(self):
794        # Exercise the allow_fragments parameter of urlparse() and urlsplit()
795        tests = (
796            ("http:#frag", "path", "frag"),
797            ("//example.net#frag", "path", "frag"),
798            ("index.html#frag", "path", "frag"),
799            (";a=b#frag", "params", "frag"),
800            ("?a=b#frag", "query", "frag"),
801            ("#frag", "path", "frag"),
802            ("abc#@frag", "path", "@frag"),
803            ("//abc#@frag", "path", "@frag"),
804            ("//abc:80#@frag", "path", "@frag"),
805            ("//abc#@frag:80", "path", "@frag:80"),
806        )
807        for url, attr, expected_frag in tests:
808            for func in (urllib.parse.urlparse, urllib.parse.urlsplit):
809                if attr == "params" and func is urllib.parse.urlsplit:
810                    attr = "path"
811                with self.subTest(url=url, function=func):
812                    result = func(url, allow_fragments=False)
813                    self.assertEqual(result.fragment, "")
814                    self.assertTrue(
815                            getattr(result, attr).endswith("#" + expected_frag))
816                    self.assertEqual(func(url, "", False).fragment, "")
817
818                    result = func(url, allow_fragments=True)
819                    self.assertEqual(result.fragment, expected_frag)
820                    self.assertFalse(
821                            getattr(result, attr).endswith(expected_frag))
822                    self.assertEqual(func(url, "", True).fragment,
823                                     expected_frag)
824                    self.assertEqual(func(url).fragment, expected_frag)
825
826    def test_mixed_types_rejected(self):
827        # Several functions that process either strings or ASCII encoded bytes
828        # accept multiple arguments. Check they reject mixed type input
829        with self.assertRaisesRegex(TypeError, "Cannot mix str"):
830            urllib.parse.urlparse("www.python.org", b"http")
831        with self.assertRaisesRegex(TypeError, "Cannot mix str"):
832            urllib.parse.urlparse(b"www.python.org", "http")
833        with self.assertRaisesRegex(TypeError, "Cannot mix str"):
834            urllib.parse.urlsplit("www.python.org", b"http")
835        with self.assertRaisesRegex(TypeError, "Cannot mix str"):
836            urllib.parse.urlsplit(b"www.python.org", "http")
837        with self.assertRaisesRegex(TypeError, "Cannot mix str"):
838            urllib.parse.urlunparse(( b"http", "www.python.org","","","",""))
839        with self.assertRaisesRegex(TypeError, "Cannot mix str"):
840            urllib.parse.urlunparse(("http", b"www.python.org","","","",""))
841        with self.assertRaisesRegex(TypeError, "Cannot mix str"):
842            urllib.parse.urlunsplit((b"http", "www.python.org","","",""))
843        with self.assertRaisesRegex(TypeError, "Cannot mix str"):
844            urllib.parse.urlunsplit(("http", b"www.python.org","","",""))
845        with self.assertRaisesRegex(TypeError, "Cannot mix str"):
846            urllib.parse.urljoin("http://python.org", b"http://python.org")
847        with self.assertRaisesRegex(TypeError, "Cannot mix str"):
848            urllib.parse.urljoin(b"http://python.org", "http://python.org")
849
850    def _check_result_type(self, str_type):
851        num_args = len(str_type._fields)
852        bytes_type = str_type._encoded_counterpart
853        self.assertIs(bytes_type._decoded_counterpart, str_type)
854        str_args = ('',) * num_args
855        bytes_args = (b'',) * num_args
856        str_result = str_type(*str_args)
857        bytes_result = bytes_type(*bytes_args)
858        encoding = 'ascii'
859        errors = 'strict'
860        self.assertEqual(str_result, str_args)
861        self.assertEqual(bytes_result.decode(), str_args)
862        self.assertEqual(bytes_result.decode(), str_result)
863        self.assertEqual(bytes_result.decode(encoding), str_args)
864        self.assertEqual(bytes_result.decode(encoding), str_result)
865        self.assertEqual(bytes_result.decode(encoding, errors), str_args)
866        self.assertEqual(bytes_result.decode(encoding, errors), str_result)
867        self.assertEqual(bytes_result, bytes_args)
868        self.assertEqual(str_result.encode(), bytes_args)
869        self.assertEqual(str_result.encode(), bytes_result)
870        self.assertEqual(str_result.encode(encoding), bytes_args)
871        self.assertEqual(str_result.encode(encoding), bytes_result)
872        self.assertEqual(str_result.encode(encoding, errors), bytes_args)
873        self.assertEqual(str_result.encode(encoding, errors), bytes_result)
874
875    def test_result_pairs(self):
876        # Check encoding and decoding between result pairs
877        result_types = [
878          urllib.parse.DefragResult,
879          urllib.parse.SplitResult,
880          urllib.parse.ParseResult,
881        ]
882        for result_type in result_types:
883            self._check_result_type(result_type)
884
885    def test_parse_qs_encoding(self):
886        result = urllib.parse.parse_qs("key=\u0141%E9", encoding="latin-1")
887        self.assertEqual(result, {'key': ['\u0141\xE9']})
888        result = urllib.parse.parse_qs("key=\u0141%C3%A9", encoding="utf-8")
889        self.assertEqual(result, {'key': ['\u0141\xE9']})
890        result = urllib.parse.parse_qs("key=\u0141%C3%A9", encoding="ascii")
891        self.assertEqual(result, {'key': ['\u0141\ufffd\ufffd']})
892        result = urllib.parse.parse_qs("key=\u0141%E9-", encoding="ascii")
893        self.assertEqual(result, {'key': ['\u0141\ufffd-']})
894        result = urllib.parse.parse_qs("key=\u0141%E9-", encoding="ascii",
895                                                          errors="ignore")
896        self.assertEqual(result, {'key': ['\u0141-']})
897
898    def test_parse_qsl_encoding(self):
899        result = urllib.parse.parse_qsl("key=\u0141%E9", encoding="latin-1")
900        self.assertEqual(result, [('key', '\u0141\xE9')])
901        result = urllib.parse.parse_qsl("key=\u0141%C3%A9", encoding="utf-8")
902        self.assertEqual(result, [('key', '\u0141\xE9')])
903        result = urllib.parse.parse_qsl("key=\u0141%C3%A9", encoding="ascii")
904        self.assertEqual(result, [('key', '\u0141\ufffd\ufffd')])
905        result = urllib.parse.parse_qsl("key=\u0141%E9-", encoding="ascii")
906        self.assertEqual(result, [('key', '\u0141\ufffd-')])
907        result = urllib.parse.parse_qsl("key=\u0141%E9-", encoding="ascii",
908                                                          errors="ignore")
909        self.assertEqual(result, [('key', '\u0141-')])
910
911    def test_parse_qsl_max_num_fields(self):
912        with self.assertRaises(ValueError):
913            urllib.parse.parse_qs('&'.join(['a=a']*11), max_num_fields=10)
914        urllib.parse.parse_qs('&'.join(['a=a']*10), max_num_fields=10)
915
916    def test_parse_qs_separator(self):
917        parse_qs_semicolon_cases = [
918            (";", {}),
919            (";;", {}),
920            (";a=b", {'a': ['b']}),
921            ("a=a+b;b=b+c", {'a': ['a b'], 'b': ['b c']}),
922            ("a=1;a=2", {'a': ['1', '2']}),
923            (b";", {}),
924            (b";;", {}),
925            (b";a=b", {b'a': [b'b']}),
926            (b"a=a+b;b=b+c", {b'a': [b'a b'], b'b': [b'b c']}),
927            (b"a=1;a=2", {b'a': [b'1', b'2']}),
928        ]
929        for orig, expect in parse_qs_semicolon_cases:
930            with self.subTest(f"Original: {orig!r}, Expected: {expect!r}"):
931                result = urllib.parse.parse_qs(orig, separator=';')
932                self.assertEqual(result, expect, "Error parsing %r" % orig)
933
934
935    def test_parse_qsl_separator(self):
936        parse_qsl_semicolon_cases = [
937            (";", []),
938            (";;", []),
939            (";a=b", [('a', 'b')]),
940            ("a=a+b;b=b+c", [('a', 'a b'), ('b', 'b c')]),
941            ("a=1;a=2", [('a', '1'), ('a', '2')]),
942            (b";", []),
943            (b";;", []),
944            (b";a=b", [(b'a', b'b')]),
945            (b"a=a+b;b=b+c", [(b'a', b'a b'), (b'b', b'b c')]),
946            (b"a=1;a=2", [(b'a', b'1'), (b'a', b'2')]),
947        ]
948        for orig, expect in parse_qsl_semicolon_cases:
949            with self.subTest(f"Original: {orig!r}, Expected: {expect!r}"):
950                result = urllib.parse.parse_qsl(orig, separator=';')
951                self.assertEqual(result, expect, "Error parsing %r" % orig)
952
953
954    def test_urlencode_sequences(self):
955        # Other tests incidentally urlencode things; test non-covered cases:
956        # Sequence and object values.
957        result = urllib.parse.urlencode({'a': [1, 2], 'b': (3, 4, 5)}, True)
958        # we cannot rely on ordering here
959        assert set(result.split('&')) == {'a=1', 'a=2', 'b=3', 'b=4', 'b=5'}
960
961        class Trivial:
962            def __str__(self):
963                return 'trivial'
964
965        result = urllib.parse.urlencode({'a': Trivial()}, True)
966        self.assertEqual(result, 'a=trivial')
967
968    def test_urlencode_quote_via(self):
969        result = urllib.parse.urlencode({'a': 'some value'})
970        self.assertEqual(result, "a=some+value")
971        result = urllib.parse.urlencode({'a': 'some value/another'},
972                                        quote_via=urllib.parse.quote)
973        self.assertEqual(result, "a=some%20value%2Fanother")
974        result = urllib.parse.urlencode({'a': 'some value/another'},
975                                        safe='/', quote_via=urllib.parse.quote)
976        self.assertEqual(result, "a=some%20value/another")
977
978    def test_quote_from_bytes(self):
979        self.assertRaises(TypeError, urllib.parse.quote_from_bytes, 'foo')
980        result = urllib.parse.quote_from_bytes(b'archaeological arcana')
981        self.assertEqual(result, 'archaeological%20arcana')
982        result = urllib.parse.quote_from_bytes(b'')
983        self.assertEqual(result, '')
984
985    def test_unquote_to_bytes(self):
986        result = urllib.parse.unquote_to_bytes('abc%20def')
987        self.assertEqual(result, b'abc def')
988        result = urllib.parse.unquote_to_bytes('')
989        self.assertEqual(result, b'')
990
991    def test_quote_errors(self):
992        self.assertRaises(TypeError, urllib.parse.quote, b'foo',
993                          encoding='utf-8')
994        self.assertRaises(TypeError, urllib.parse.quote, b'foo', errors='strict')
995
996    def test_issue14072(self):
997        p1 = urllib.parse.urlsplit('tel:+31-641044153')
998        self.assertEqual(p1.scheme, 'tel')
999        self.assertEqual(p1.path, '+31-641044153')
1000        p2 = urllib.parse.urlsplit('tel:+31641044153')
1001        self.assertEqual(p2.scheme, 'tel')
1002        self.assertEqual(p2.path, '+31641044153')
1003        # assert the behavior for urlparse
1004        p1 = urllib.parse.urlparse('tel:+31-641044153')
1005        self.assertEqual(p1.scheme, 'tel')
1006        self.assertEqual(p1.path, '+31-641044153')
1007        p2 = urllib.parse.urlparse('tel:+31641044153')
1008        self.assertEqual(p2.scheme, 'tel')
1009        self.assertEqual(p2.path, '+31641044153')
1010
1011    def test_port_casting_failure_message(self):
1012        message = "Port could not be cast to integer value as 'oracle'"
1013        p1 = urllib.parse.urlparse('http://Server=sde; Service=sde:oracle')
1014        with self.assertRaisesRegex(ValueError, message):
1015            p1.port
1016
1017        p2 = urllib.parse.urlsplit('http://Server=sde; Service=sde:oracle')
1018        with self.assertRaisesRegex(ValueError, message):
1019            p2.port
1020
1021    def test_telurl_params(self):
1022        p1 = urllib.parse.urlparse('tel:123-4;phone-context=+1-650-516')
1023        self.assertEqual(p1.scheme, 'tel')
1024        self.assertEqual(p1.path, '123-4')
1025        self.assertEqual(p1.params, 'phone-context=+1-650-516')
1026
1027        p1 = urllib.parse.urlparse('tel:+1-201-555-0123')
1028        self.assertEqual(p1.scheme, 'tel')
1029        self.assertEqual(p1.path, '+1-201-555-0123')
1030        self.assertEqual(p1.params, '')
1031
1032        p1 = urllib.parse.urlparse('tel:7042;phone-context=example.com')
1033        self.assertEqual(p1.scheme, 'tel')
1034        self.assertEqual(p1.path, '7042')
1035        self.assertEqual(p1.params, 'phone-context=example.com')
1036
1037        p1 = urllib.parse.urlparse('tel:863-1234;phone-context=+1-914-555')
1038        self.assertEqual(p1.scheme, 'tel')
1039        self.assertEqual(p1.path, '863-1234')
1040        self.assertEqual(p1.params, 'phone-context=+1-914-555')
1041
1042    def test_Quoter_repr(self):
1043        quoter = urllib.parse.Quoter(urllib.parse._ALWAYS_SAFE)
1044        self.assertIn('Quoter', repr(quoter))
1045
1046    def test_all(self):
1047        expected = []
1048        undocumented = {
1049            'splitattr', 'splithost', 'splitnport', 'splitpasswd',
1050            'splitport', 'splitquery', 'splittag', 'splittype', 'splituser',
1051            'splitvalue',
1052            'Quoter', 'ResultBase', 'clear_cache', 'to_bytes', 'unwrap',
1053        }
1054        for name in dir(urllib.parse):
1055            if name.startswith('_') or name in undocumented:
1056                continue
1057            object = getattr(urllib.parse, name)
1058            if getattr(object, '__module__', None) == 'urllib.parse':
1059                expected.append(name)
1060        self.assertCountEqual(urllib.parse.__all__, expected)
1061
1062    def test_urlsplit_normalization(self):
1063        # Certain characters should never occur in the netloc,
1064        # including under normalization.
1065        # Ensure that ALL of them are detected and cause an error
1066        illegal_chars = '/:#?@'
1067        hex_chars = {'{:04X}'.format(ord(c)) for c in illegal_chars}
1068        denorm_chars = [
1069            c for c in map(chr, range(128, sys.maxunicode))
1070            if (hex_chars & set(unicodedata.decomposition(c).split()))
1071            and c not in illegal_chars
1072        ]
1073        # Sanity check that we found at least one such character
1074        self.assertIn('\u2100', denorm_chars)
1075        self.assertIn('\uFF03', denorm_chars)
1076
1077        # bpo-36742: Verify port separators are ignored when they
1078        # existed prior to decomposition
1079        urllib.parse.urlsplit('http://\u30d5\u309a:80')
1080        with self.assertRaises(ValueError):
1081            urllib.parse.urlsplit('http://\u30d5\u309a\ufe1380')
1082
1083        for scheme in ["http", "https", "ftp"]:
1084            for netloc in ["netloc{}false.netloc", "n{}user@netloc"]:
1085                for c in denorm_chars:
1086                    url = "{}://{}/path".format(scheme, netloc.format(c))
1087                    with self.subTest(url=url, char='{:04X}'.format(ord(c))):
1088                        with self.assertRaises(ValueError):
1089                            urllib.parse.urlsplit(url)
1090
1091class Utility_Tests(unittest.TestCase):
1092    """Testcase to test the various utility functions in the urllib."""
1093    # In Python 2 this test class was in test_urllib.
1094
1095    def test_splittype(self):
1096        splittype = urllib.parse._splittype
1097        self.assertEqual(splittype('type:opaquestring'), ('type', 'opaquestring'))
1098        self.assertEqual(splittype('opaquestring'), (None, 'opaquestring'))
1099        self.assertEqual(splittype(':opaquestring'), (None, ':opaquestring'))
1100        self.assertEqual(splittype('type:'), ('type', ''))
1101        self.assertEqual(splittype('type:opaque:string'), ('type', 'opaque:string'))
1102
1103    def test_splithost(self):
1104        splithost = urllib.parse._splithost
1105        self.assertEqual(splithost('//www.example.org:80/foo/bar/baz.html'),
1106                         ('www.example.org:80', '/foo/bar/baz.html'))
1107        self.assertEqual(splithost('//www.example.org:80'),
1108                         ('www.example.org:80', ''))
1109        self.assertEqual(splithost('/foo/bar/baz.html'),
1110                         (None, '/foo/bar/baz.html'))
1111
1112        # bpo-30500: # starts a fragment.
1113        self.assertEqual(splithost('//127.0.0.1#@host.com'),
1114                         ('127.0.0.1', '/#@host.com'))
1115        self.assertEqual(splithost('//127.0.0.1#@host.com:80'),
1116                         ('127.0.0.1', '/#@host.com:80'))
1117        self.assertEqual(splithost('//127.0.0.1:80#@host.com'),
1118                         ('127.0.0.1:80', '/#@host.com'))
1119
1120        # Empty host is returned as empty string.
1121        self.assertEqual(splithost("///file"),
1122                         ('', '/file'))
1123
1124        # Trailing semicolon, question mark and hash symbol are kept.
1125        self.assertEqual(splithost("//example.net/file;"),
1126                         ('example.net', '/file;'))
1127        self.assertEqual(splithost("//example.net/file?"),
1128                         ('example.net', '/file?'))
1129        self.assertEqual(splithost("//example.net/file#"),
1130                         ('example.net', '/file#'))
1131
1132    def test_splituser(self):
1133        splituser = urllib.parse._splituser
1134        self.assertEqual(splituser('User:Pass@www.python.org:080'),
1135                         ('User:Pass', 'www.python.org:080'))
1136        self.assertEqual(splituser('@www.python.org:080'),
1137                         ('', 'www.python.org:080'))
1138        self.assertEqual(splituser('www.python.org:080'),
1139                         (None, 'www.python.org:080'))
1140        self.assertEqual(splituser('User:Pass@'),
1141                         ('User:Pass', ''))
1142        self.assertEqual(splituser('User@example.com:Pass@www.python.org:080'),
1143                         ('User@example.com:Pass', 'www.python.org:080'))
1144
1145    def test_splitpasswd(self):
1146        # Some of the password examples are not sensible, but it is added to
1147        # confirming to RFC2617 and addressing issue4675.
1148        splitpasswd = urllib.parse._splitpasswd
1149        self.assertEqual(splitpasswd('user:ab'), ('user', 'ab'))
1150        self.assertEqual(splitpasswd('user:a\nb'), ('user', 'a\nb'))
1151        self.assertEqual(splitpasswd('user:a\tb'), ('user', 'a\tb'))
1152        self.assertEqual(splitpasswd('user:a\rb'), ('user', 'a\rb'))
1153        self.assertEqual(splitpasswd('user:a\fb'), ('user', 'a\fb'))
1154        self.assertEqual(splitpasswd('user:a\vb'), ('user', 'a\vb'))
1155        self.assertEqual(splitpasswd('user:a:b'), ('user', 'a:b'))
1156        self.assertEqual(splitpasswd('user:a b'), ('user', 'a b'))
1157        self.assertEqual(splitpasswd('user 2:ab'), ('user 2', 'ab'))
1158        self.assertEqual(splitpasswd('user+1:a+b'), ('user+1', 'a+b'))
1159        self.assertEqual(splitpasswd('user:'), ('user', ''))
1160        self.assertEqual(splitpasswd('user'), ('user', None))
1161        self.assertEqual(splitpasswd(':ab'), ('', 'ab'))
1162
1163    def test_splitport(self):
1164        splitport = urllib.parse._splitport
1165        self.assertEqual(splitport('parrot:88'), ('parrot', '88'))
1166        self.assertEqual(splitport('parrot'), ('parrot', None))
1167        self.assertEqual(splitport('parrot:'), ('parrot', None))
1168        self.assertEqual(splitport('127.0.0.1'), ('127.0.0.1', None))
1169        self.assertEqual(splitport('parrot:cheese'), ('parrot:cheese', None))
1170        self.assertEqual(splitport('[::1]:88'), ('[::1]', '88'))
1171        self.assertEqual(splitport('[::1]'), ('[::1]', None))
1172        self.assertEqual(splitport(':88'), ('', '88'))
1173
1174    def test_splitnport(self):
1175        splitnport = urllib.parse._splitnport
1176        self.assertEqual(splitnport('parrot:88'), ('parrot', 88))
1177        self.assertEqual(splitnport('parrot'), ('parrot', -1))
1178        self.assertEqual(splitnport('parrot', 55), ('parrot', 55))
1179        self.assertEqual(splitnport('parrot:'), ('parrot', -1))
1180        self.assertEqual(splitnport('parrot:', 55), ('parrot', 55))
1181        self.assertEqual(splitnport('127.0.0.1'), ('127.0.0.1', -1))
1182        self.assertEqual(splitnport('127.0.0.1', 55), ('127.0.0.1', 55))
1183        self.assertEqual(splitnport('parrot:cheese'), ('parrot', None))
1184        self.assertEqual(splitnport('parrot:cheese', 55), ('parrot', None))
1185
1186    def test_splitquery(self):
1187        # Normal cases are exercised by other tests; ensure that we also
1188        # catch cases with no port specified (testcase ensuring coverage)
1189        splitquery = urllib.parse._splitquery
1190        self.assertEqual(splitquery('http://python.org/fake?foo=bar'),
1191                         ('http://python.org/fake', 'foo=bar'))
1192        self.assertEqual(splitquery('http://python.org/fake?foo=bar?'),
1193                         ('http://python.org/fake?foo=bar', ''))
1194        self.assertEqual(splitquery('http://python.org/fake'),
1195                         ('http://python.org/fake', None))
1196        self.assertEqual(splitquery('?foo=bar'), ('', 'foo=bar'))
1197
1198    def test_splittag(self):
1199        splittag = urllib.parse._splittag
1200        self.assertEqual(splittag('http://example.com?foo=bar#baz'),
1201                         ('http://example.com?foo=bar', 'baz'))
1202        self.assertEqual(splittag('http://example.com?foo=bar#'),
1203                         ('http://example.com?foo=bar', ''))
1204        self.assertEqual(splittag('#baz'), ('', 'baz'))
1205        self.assertEqual(splittag('http://example.com?foo=bar'),
1206                         ('http://example.com?foo=bar', None))
1207        self.assertEqual(splittag('http://example.com?foo=bar#baz#boo'),
1208                         ('http://example.com?foo=bar#baz', 'boo'))
1209
1210    def test_splitattr(self):
1211        splitattr = urllib.parse._splitattr
1212        self.assertEqual(splitattr('/path;attr1=value1;attr2=value2'),
1213                         ('/path', ['attr1=value1', 'attr2=value2']))
1214        self.assertEqual(splitattr('/path;'), ('/path', ['']))
1215        self.assertEqual(splitattr(';attr1=value1;attr2=value2'),
1216                         ('', ['attr1=value1', 'attr2=value2']))
1217        self.assertEqual(splitattr('/path'), ('/path', []))
1218
1219    def test_splitvalue(self):
1220        # Normal cases are exercised by other tests; test pathological cases
1221        # with no key/value pairs. (testcase ensuring coverage)
1222        splitvalue = urllib.parse._splitvalue
1223        self.assertEqual(splitvalue('foo=bar'), ('foo', 'bar'))
1224        self.assertEqual(splitvalue('foo='), ('foo', ''))
1225        self.assertEqual(splitvalue('=bar'), ('', 'bar'))
1226        self.assertEqual(splitvalue('foobar'), ('foobar', None))
1227        self.assertEqual(splitvalue('foo=bar=baz'), ('foo', 'bar=baz'))
1228
1229    def test_to_bytes(self):
1230        result = urllib.parse._to_bytes('http://www.python.org')
1231        self.assertEqual(result, 'http://www.python.org')
1232        self.assertRaises(UnicodeError, urllib.parse._to_bytes,
1233                          'http://www.python.org/medi\u00e6val')
1234
1235    def test_unwrap(self):
1236        for wrapped_url in ('<URL:scheme://host/path>', '<scheme://host/path>',
1237                            'URL:scheme://host/path', 'scheme://host/path'):
1238            url = urllib.parse.unwrap(wrapped_url)
1239            self.assertEqual(url, 'scheme://host/path')
1240
1241
1242class DeprecationTest(unittest.TestCase):
1243
1244    def test_splittype_deprecation(self):
1245        with self.assertWarns(DeprecationWarning) as cm:
1246            urllib.parse.splittype('')
1247        self.assertEqual(str(cm.warning),
1248                         'urllib.parse.splittype() is deprecated as of 3.8, '
1249                         'use urllib.parse.urlparse() instead')
1250
1251    def test_splithost_deprecation(self):
1252        with self.assertWarns(DeprecationWarning) as cm:
1253            urllib.parse.splithost('')
1254        self.assertEqual(str(cm.warning),
1255                         'urllib.parse.splithost() is deprecated as of 3.8, '
1256                         'use urllib.parse.urlparse() instead')
1257
1258    def test_splituser_deprecation(self):
1259        with self.assertWarns(DeprecationWarning) as cm:
1260            urllib.parse.splituser('')
1261        self.assertEqual(str(cm.warning),
1262                         'urllib.parse.splituser() is deprecated as of 3.8, '
1263                         'use urllib.parse.urlparse() instead')
1264
1265    def test_splitpasswd_deprecation(self):
1266        with self.assertWarns(DeprecationWarning) as cm:
1267            urllib.parse.splitpasswd('')
1268        self.assertEqual(str(cm.warning),
1269                         'urllib.parse.splitpasswd() is deprecated as of 3.8, '
1270                         'use urllib.parse.urlparse() instead')
1271
1272    def test_splitport_deprecation(self):
1273        with self.assertWarns(DeprecationWarning) as cm:
1274            urllib.parse.splitport('')
1275        self.assertEqual(str(cm.warning),
1276                         'urllib.parse.splitport() is deprecated as of 3.8, '
1277                         'use urllib.parse.urlparse() instead')
1278
1279    def test_splitnport_deprecation(self):
1280        with self.assertWarns(DeprecationWarning) as cm:
1281            urllib.parse.splitnport('')
1282        self.assertEqual(str(cm.warning),
1283                         'urllib.parse.splitnport() is deprecated as of 3.8, '
1284                         'use urllib.parse.urlparse() instead')
1285
1286    def test_splitquery_deprecation(self):
1287        with self.assertWarns(DeprecationWarning) as cm:
1288            urllib.parse.splitquery('')
1289        self.assertEqual(str(cm.warning),
1290                         'urllib.parse.splitquery() is deprecated as of 3.8, '
1291                         'use urllib.parse.urlparse() instead')
1292
1293    def test_splittag_deprecation(self):
1294        with self.assertWarns(DeprecationWarning) as cm:
1295            urllib.parse.splittag('')
1296        self.assertEqual(str(cm.warning),
1297                         'urllib.parse.splittag() is deprecated as of 3.8, '
1298                         'use urllib.parse.urlparse() instead')
1299
1300    def test_splitattr_deprecation(self):
1301        with self.assertWarns(DeprecationWarning) as cm:
1302            urllib.parse.splitattr('')
1303        self.assertEqual(str(cm.warning),
1304                         'urllib.parse.splitattr() is deprecated as of 3.8, '
1305                         'use urllib.parse.urlparse() instead')
1306
1307    def test_splitvalue_deprecation(self):
1308        with self.assertWarns(DeprecationWarning) as cm:
1309            urllib.parse.splitvalue('')
1310        self.assertEqual(str(cm.warning),
1311                         'urllib.parse.splitvalue() is deprecated as of 3.8, '
1312                         'use urllib.parse.parse_qsl() instead')
1313
1314    def test_to_bytes_deprecation(self):
1315        with self.assertWarns(DeprecationWarning) as cm:
1316            urllib.parse.to_bytes('')
1317        self.assertEqual(str(cm.warning),
1318                         'urllib.parse.to_bytes() is deprecated as of 3.8')
1319
1320
1321if __name__ == "__main__":
1322    unittest.main()
1323