• 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                result_bytes = urllib.parse.parse_qs(orig, separator=b';')
934                self.assertEqual(result_bytes, expect, "Error parsing %r" % orig)
935
936
937    def test_parse_qsl_separator(self):
938        parse_qsl_semicolon_cases = [
939            (";", []),
940            (";;", []),
941            (";a=b", [('a', 'b')]),
942            ("a=a+b;b=b+c", [('a', 'a b'), ('b', 'b c')]),
943            ("a=1;a=2", [('a', '1'), ('a', '2')]),
944            (b";", []),
945            (b";;", []),
946            (b";a=b", [(b'a', b'b')]),
947            (b"a=a+b;b=b+c", [(b'a', b'a b'), (b'b', b'b c')]),
948            (b"a=1;a=2", [(b'a', b'1'), (b'a', b'2')]),
949        ]
950        for orig, expect in parse_qsl_semicolon_cases:
951            with self.subTest(f"Original: {orig!r}, Expected: {expect!r}"):
952                result = urllib.parse.parse_qsl(orig, separator=';')
953                self.assertEqual(result, expect, "Error parsing %r" % orig)
954                result_bytes = urllib.parse.parse_qsl(orig, separator=b';')
955                self.assertEqual(result_bytes, expect, "Error parsing %r" % orig)
956
957
958    def test_urlencode_sequences(self):
959        # Other tests incidentally urlencode things; test non-covered cases:
960        # Sequence and object values.
961        result = urllib.parse.urlencode({'a': [1, 2], 'b': (3, 4, 5)}, True)
962        # we cannot rely on ordering here
963        assert set(result.split('&')) == {'a=1', 'a=2', 'b=3', 'b=4', 'b=5'}
964
965        class Trivial:
966            def __str__(self):
967                return 'trivial'
968
969        result = urllib.parse.urlencode({'a': Trivial()}, True)
970        self.assertEqual(result, 'a=trivial')
971
972    def test_urlencode_quote_via(self):
973        result = urllib.parse.urlencode({'a': 'some value'})
974        self.assertEqual(result, "a=some+value")
975        result = urllib.parse.urlencode({'a': 'some value/another'},
976                                        quote_via=urllib.parse.quote)
977        self.assertEqual(result, "a=some%20value%2Fanother")
978        result = urllib.parse.urlencode({'a': 'some value/another'},
979                                        safe='/', quote_via=urllib.parse.quote)
980        self.assertEqual(result, "a=some%20value/another")
981
982    def test_quote_from_bytes(self):
983        self.assertRaises(TypeError, urllib.parse.quote_from_bytes, 'foo')
984        result = urllib.parse.quote_from_bytes(b'archaeological arcana')
985        self.assertEqual(result, 'archaeological%20arcana')
986        result = urllib.parse.quote_from_bytes(b'')
987        self.assertEqual(result, '')
988
989    def test_unquote_to_bytes(self):
990        result = urllib.parse.unquote_to_bytes('abc%20def')
991        self.assertEqual(result, b'abc def')
992        result = urllib.parse.unquote_to_bytes('')
993        self.assertEqual(result, b'')
994
995    def test_quote_errors(self):
996        self.assertRaises(TypeError, urllib.parse.quote, b'foo',
997                          encoding='utf-8')
998        self.assertRaises(TypeError, urllib.parse.quote, b'foo', errors='strict')
999
1000    def test_issue14072(self):
1001        p1 = urllib.parse.urlsplit('tel:+31-641044153')
1002        self.assertEqual(p1.scheme, 'tel')
1003        self.assertEqual(p1.path, '+31-641044153')
1004        p2 = urllib.parse.urlsplit('tel:+31641044153')
1005        self.assertEqual(p2.scheme, 'tel')
1006        self.assertEqual(p2.path, '+31641044153')
1007        # assert the behavior for urlparse
1008        p1 = urllib.parse.urlparse('tel:+31-641044153')
1009        self.assertEqual(p1.scheme, 'tel')
1010        self.assertEqual(p1.path, '+31-641044153')
1011        p2 = urllib.parse.urlparse('tel:+31641044153')
1012        self.assertEqual(p2.scheme, 'tel')
1013        self.assertEqual(p2.path, '+31641044153')
1014
1015    def test_port_casting_failure_message(self):
1016        message = "Port could not be cast to integer value as 'oracle'"
1017        p1 = urllib.parse.urlparse('http://Server=sde; Service=sde:oracle')
1018        with self.assertRaisesRegex(ValueError, message):
1019            p1.port
1020
1021        p2 = urllib.parse.urlsplit('http://Server=sde; Service=sde:oracle')
1022        with self.assertRaisesRegex(ValueError, message):
1023            p2.port
1024
1025    def test_telurl_params(self):
1026        p1 = urllib.parse.urlparse('tel:123-4;phone-context=+1-650-516')
1027        self.assertEqual(p1.scheme, 'tel')
1028        self.assertEqual(p1.path, '123-4')
1029        self.assertEqual(p1.params, 'phone-context=+1-650-516')
1030
1031        p1 = urllib.parse.urlparse('tel:+1-201-555-0123')
1032        self.assertEqual(p1.scheme, 'tel')
1033        self.assertEqual(p1.path, '+1-201-555-0123')
1034        self.assertEqual(p1.params, '')
1035
1036        p1 = urllib.parse.urlparse('tel:7042;phone-context=example.com')
1037        self.assertEqual(p1.scheme, 'tel')
1038        self.assertEqual(p1.path, '7042')
1039        self.assertEqual(p1.params, 'phone-context=example.com')
1040
1041        p1 = urllib.parse.urlparse('tel:863-1234;phone-context=+1-914-555')
1042        self.assertEqual(p1.scheme, 'tel')
1043        self.assertEqual(p1.path, '863-1234')
1044        self.assertEqual(p1.params, 'phone-context=+1-914-555')
1045
1046    def test_Quoter_repr(self):
1047        quoter = urllib.parse.Quoter(urllib.parse._ALWAYS_SAFE)
1048        self.assertIn('Quoter', repr(quoter))
1049
1050    def test_all(self):
1051        expected = []
1052        undocumented = {
1053            'splitattr', 'splithost', 'splitnport', 'splitpasswd',
1054            'splitport', 'splitquery', 'splittag', 'splittype', 'splituser',
1055            'splitvalue',
1056            'Quoter', 'ResultBase', 'clear_cache', 'to_bytes', 'unwrap',
1057        }
1058        for name in dir(urllib.parse):
1059            if name.startswith('_') or name in undocumented:
1060                continue
1061            object = getattr(urllib.parse, name)
1062            if getattr(object, '__module__', None) == 'urllib.parse':
1063                expected.append(name)
1064        self.assertCountEqual(urllib.parse.__all__, expected)
1065
1066    def test_urlsplit_normalization(self):
1067        # Certain characters should never occur in the netloc,
1068        # including under normalization.
1069        # Ensure that ALL of them are detected and cause an error
1070        illegal_chars = '/:#?@'
1071        hex_chars = {'{:04X}'.format(ord(c)) for c in illegal_chars}
1072        denorm_chars = [
1073            c for c in map(chr, range(128, sys.maxunicode))
1074            if (hex_chars & set(unicodedata.decomposition(c).split()))
1075            and c not in illegal_chars
1076        ]
1077        # Sanity check that we found at least one such character
1078        self.assertIn('\u2100', denorm_chars)
1079        self.assertIn('\uFF03', denorm_chars)
1080
1081        # bpo-36742: Verify port separators are ignored when they
1082        # existed prior to decomposition
1083        urllib.parse.urlsplit('http://\u30d5\u309a:80')
1084        with self.assertRaises(ValueError):
1085            urllib.parse.urlsplit('http://\u30d5\u309a\ufe1380')
1086
1087        for scheme in ["http", "https", "ftp"]:
1088            for netloc in ["netloc{}false.netloc", "n{}user@netloc"]:
1089                for c in denorm_chars:
1090                    url = "{}://{}/path".format(scheme, netloc.format(c))
1091                    with self.subTest(url=url, char='{:04X}'.format(ord(c))):
1092                        with self.assertRaises(ValueError):
1093                            urllib.parse.urlsplit(url)
1094
1095class Utility_Tests(unittest.TestCase):
1096    """Testcase to test the various utility functions in the urllib."""
1097    # In Python 2 this test class was in test_urllib.
1098
1099    def test_splittype(self):
1100        splittype = urllib.parse._splittype
1101        self.assertEqual(splittype('type:opaquestring'), ('type', 'opaquestring'))
1102        self.assertEqual(splittype('opaquestring'), (None, 'opaquestring'))
1103        self.assertEqual(splittype(':opaquestring'), (None, ':opaquestring'))
1104        self.assertEqual(splittype('type:'), ('type', ''))
1105        self.assertEqual(splittype('type:opaque:string'), ('type', 'opaque:string'))
1106
1107    def test_splithost(self):
1108        splithost = urllib.parse._splithost
1109        self.assertEqual(splithost('//www.example.org:80/foo/bar/baz.html'),
1110                         ('www.example.org:80', '/foo/bar/baz.html'))
1111        self.assertEqual(splithost('//www.example.org:80'),
1112                         ('www.example.org:80', ''))
1113        self.assertEqual(splithost('/foo/bar/baz.html'),
1114                         (None, '/foo/bar/baz.html'))
1115
1116        # bpo-30500: # starts a fragment.
1117        self.assertEqual(splithost('//127.0.0.1#@host.com'),
1118                         ('127.0.0.1', '/#@host.com'))
1119        self.assertEqual(splithost('//127.0.0.1#@host.com:80'),
1120                         ('127.0.0.1', '/#@host.com:80'))
1121        self.assertEqual(splithost('//127.0.0.1:80#@host.com'),
1122                         ('127.0.0.1:80', '/#@host.com'))
1123
1124        # Empty host is returned as empty string.
1125        self.assertEqual(splithost("///file"),
1126                         ('', '/file'))
1127
1128        # Trailing semicolon, question mark and hash symbol are kept.
1129        self.assertEqual(splithost("//example.net/file;"),
1130                         ('example.net', '/file;'))
1131        self.assertEqual(splithost("//example.net/file?"),
1132                         ('example.net', '/file?'))
1133        self.assertEqual(splithost("//example.net/file#"),
1134                         ('example.net', '/file#'))
1135
1136    def test_splituser(self):
1137        splituser = urllib.parse._splituser
1138        self.assertEqual(splituser('User:Pass@www.python.org:080'),
1139                         ('User:Pass', 'www.python.org:080'))
1140        self.assertEqual(splituser('@www.python.org:080'),
1141                         ('', 'www.python.org:080'))
1142        self.assertEqual(splituser('www.python.org:080'),
1143                         (None, 'www.python.org:080'))
1144        self.assertEqual(splituser('User:Pass@'),
1145                         ('User:Pass', ''))
1146        self.assertEqual(splituser('User@example.com:Pass@www.python.org:080'),
1147                         ('User@example.com:Pass', 'www.python.org:080'))
1148
1149    def test_splitpasswd(self):
1150        # Some of the password examples are not sensible, but it is added to
1151        # confirming to RFC2617 and addressing issue4675.
1152        splitpasswd = urllib.parse._splitpasswd
1153        self.assertEqual(splitpasswd('user:ab'), ('user', 'ab'))
1154        self.assertEqual(splitpasswd('user:a\nb'), ('user', 'a\nb'))
1155        self.assertEqual(splitpasswd('user:a\tb'), ('user', 'a\tb'))
1156        self.assertEqual(splitpasswd('user:a\rb'), ('user', 'a\rb'))
1157        self.assertEqual(splitpasswd('user:a\fb'), ('user', 'a\fb'))
1158        self.assertEqual(splitpasswd('user:a\vb'), ('user', 'a\vb'))
1159        self.assertEqual(splitpasswd('user:a:b'), ('user', 'a:b'))
1160        self.assertEqual(splitpasswd('user:a b'), ('user', 'a b'))
1161        self.assertEqual(splitpasswd('user 2:ab'), ('user 2', 'ab'))
1162        self.assertEqual(splitpasswd('user+1:a+b'), ('user+1', 'a+b'))
1163        self.assertEqual(splitpasswd('user:'), ('user', ''))
1164        self.assertEqual(splitpasswd('user'), ('user', None))
1165        self.assertEqual(splitpasswd(':ab'), ('', 'ab'))
1166
1167    def test_splitport(self):
1168        splitport = urllib.parse._splitport
1169        self.assertEqual(splitport('parrot:88'), ('parrot', '88'))
1170        self.assertEqual(splitport('parrot'), ('parrot', None))
1171        self.assertEqual(splitport('parrot:'), ('parrot', None))
1172        self.assertEqual(splitport('127.0.0.1'), ('127.0.0.1', None))
1173        self.assertEqual(splitport('parrot:cheese'), ('parrot:cheese', None))
1174        self.assertEqual(splitport('[::1]:88'), ('[::1]', '88'))
1175        self.assertEqual(splitport('[::1]'), ('[::1]', None))
1176        self.assertEqual(splitport(':88'), ('', '88'))
1177
1178    def test_splitnport(self):
1179        splitnport = urllib.parse._splitnport
1180        self.assertEqual(splitnport('parrot:88'), ('parrot', 88))
1181        self.assertEqual(splitnport('parrot'), ('parrot', -1))
1182        self.assertEqual(splitnport('parrot', 55), ('parrot', 55))
1183        self.assertEqual(splitnport('parrot:'), ('parrot', -1))
1184        self.assertEqual(splitnport('parrot:', 55), ('parrot', 55))
1185        self.assertEqual(splitnport('127.0.0.1'), ('127.0.0.1', -1))
1186        self.assertEqual(splitnport('127.0.0.1', 55), ('127.0.0.1', 55))
1187        self.assertEqual(splitnport('parrot:cheese'), ('parrot', None))
1188        self.assertEqual(splitnport('parrot:cheese', 55), ('parrot', None))
1189
1190    def test_splitquery(self):
1191        # Normal cases are exercised by other tests; ensure that we also
1192        # catch cases with no port specified (testcase ensuring coverage)
1193        splitquery = urllib.parse._splitquery
1194        self.assertEqual(splitquery('http://python.org/fake?foo=bar'),
1195                         ('http://python.org/fake', 'foo=bar'))
1196        self.assertEqual(splitquery('http://python.org/fake?foo=bar?'),
1197                         ('http://python.org/fake?foo=bar', ''))
1198        self.assertEqual(splitquery('http://python.org/fake'),
1199                         ('http://python.org/fake', None))
1200        self.assertEqual(splitquery('?foo=bar'), ('', 'foo=bar'))
1201
1202    def test_splittag(self):
1203        splittag = urllib.parse._splittag
1204        self.assertEqual(splittag('http://example.com?foo=bar#baz'),
1205                         ('http://example.com?foo=bar', 'baz'))
1206        self.assertEqual(splittag('http://example.com?foo=bar#'),
1207                         ('http://example.com?foo=bar', ''))
1208        self.assertEqual(splittag('#baz'), ('', 'baz'))
1209        self.assertEqual(splittag('http://example.com?foo=bar'),
1210                         ('http://example.com?foo=bar', None))
1211        self.assertEqual(splittag('http://example.com?foo=bar#baz#boo'),
1212                         ('http://example.com?foo=bar#baz', 'boo'))
1213
1214    def test_splitattr(self):
1215        splitattr = urllib.parse._splitattr
1216        self.assertEqual(splitattr('/path;attr1=value1;attr2=value2'),
1217                         ('/path', ['attr1=value1', 'attr2=value2']))
1218        self.assertEqual(splitattr('/path;'), ('/path', ['']))
1219        self.assertEqual(splitattr(';attr1=value1;attr2=value2'),
1220                         ('', ['attr1=value1', 'attr2=value2']))
1221        self.assertEqual(splitattr('/path'), ('/path', []))
1222
1223    def test_splitvalue(self):
1224        # Normal cases are exercised by other tests; test pathological cases
1225        # with no key/value pairs. (testcase ensuring coverage)
1226        splitvalue = urllib.parse._splitvalue
1227        self.assertEqual(splitvalue('foo=bar'), ('foo', 'bar'))
1228        self.assertEqual(splitvalue('foo='), ('foo', ''))
1229        self.assertEqual(splitvalue('=bar'), ('', 'bar'))
1230        self.assertEqual(splitvalue('foobar'), ('foobar', None))
1231        self.assertEqual(splitvalue('foo=bar=baz'), ('foo', 'bar=baz'))
1232
1233    def test_to_bytes(self):
1234        result = urllib.parse._to_bytes('http://www.python.org')
1235        self.assertEqual(result, 'http://www.python.org')
1236        self.assertRaises(UnicodeError, urllib.parse._to_bytes,
1237                          'http://www.python.org/medi\u00e6val')
1238
1239    def test_unwrap(self):
1240        for wrapped_url in ('<URL:scheme://host/path>', '<scheme://host/path>',
1241                            'URL:scheme://host/path', 'scheme://host/path'):
1242            url = urllib.parse.unwrap(wrapped_url)
1243            self.assertEqual(url, 'scheme://host/path')
1244
1245
1246class DeprecationTest(unittest.TestCase):
1247
1248    def test_splittype_deprecation(self):
1249        with self.assertWarns(DeprecationWarning) as cm:
1250            urllib.parse.splittype('')
1251        self.assertEqual(str(cm.warning),
1252                         'urllib.parse.splittype() is deprecated as of 3.8, '
1253                         'use urllib.parse.urlparse() instead')
1254
1255    def test_splithost_deprecation(self):
1256        with self.assertWarns(DeprecationWarning) as cm:
1257            urllib.parse.splithost('')
1258        self.assertEqual(str(cm.warning),
1259                         'urllib.parse.splithost() is deprecated as of 3.8, '
1260                         'use urllib.parse.urlparse() instead')
1261
1262    def test_splituser_deprecation(self):
1263        with self.assertWarns(DeprecationWarning) as cm:
1264            urllib.parse.splituser('')
1265        self.assertEqual(str(cm.warning),
1266                         'urllib.parse.splituser() is deprecated as of 3.8, '
1267                         'use urllib.parse.urlparse() instead')
1268
1269    def test_splitpasswd_deprecation(self):
1270        with self.assertWarns(DeprecationWarning) as cm:
1271            urllib.parse.splitpasswd('')
1272        self.assertEqual(str(cm.warning),
1273                         'urllib.parse.splitpasswd() is deprecated as of 3.8, '
1274                         'use urllib.parse.urlparse() instead')
1275
1276    def test_splitport_deprecation(self):
1277        with self.assertWarns(DeprecationWarning) as cm:
1278            urllib.parse.splitport('')
1279        self.assertEqual(str(cm.warning),
1280                         'urllib.parse.splitport() is deprecated as of 3.8, '
1281                         'use urllib.parse.urlparse() instead')
1282
1283    def test_splitnport_deprecation(self):
1284        with self.assertWarns(DeprecationWarning) as cm:
1285            urllib.parse.splitnport('')
1286        self.assertEqual(str(cm.warning),
1287                         'urllib.parse.splitnport() is deprecated as of 3.8, '
1288                         'use urllib.parse.urlparse() instead')
1289
1290    def test_splitquery_deprecation(self):
1291        with self.assertWarns(DeprecationWarning) as cm:
1292            urllib.parse.splitquery('')
1293        self.assertEqual(str(cm.warning),
1294                         'urllib.parse.splitquery() is deprecated as of 3.8, '
1295                         'use urllib.parse.urlparse() instead')
1296
1297    def test_splittag_deprecation(self):
1298        with self.assertWarns(DeprecationWarning) as cm:
1299            urllib.parse.splittag('')
1300        self.assertEqual(str(cm.warning),
1301                         'urllib.parse.splittag() is deprecated as of 3.8, '
1302                         'use urllib.parse.urlparse() instead')
1303
1304    def test_splitattr_deprecation(self):
1305        with self.assertWarns(DeprecationWarning) as cm:
1306            urllib.parse.splitattr('')
1307        self.assertEqual(str(cm.warning),
1308                         'urllib.parse.splitattr() is deprecated as of 3.8, '
1309                         'use urllib.parse.urlparse() instead')
1310
1311    def test_splitvalue_deprecation(self):
1312        with self.assertWarns(DeprecationWarning) as cm:
1313            urllib.parse.splitvalue('')
1314        self.assertEqual(str(cm.warning),
1315                         'urllib.parse.splitvalue() is deprecated as of 3.8, '
1316                         'use urllib.parse.parse_qsl() instead')
1317
1318    def test_to_bytes_deprecation(self):
1319        with self.assertWarns(DeprecationWarning) as cm:
1320            urllib.parse.to_bytes('')
1321        self.assertEqual(str(cm.warning),
1322                         'urllib.parse.to_bytes() is deprecated as of 3.8')
1323
1324
1325if __name__ == "__main__":
1326    unittest.main()
1327