1import sys
2import os
3import distutils.errors
4import platform
5import urllib.request
6import urllib.error
7import http.client
8
9import mock
10import pytest
11
12import setuptools.package_index
13from .textwrap import DALS
14
15
16class TestPackageIndex:
17    def test_regex(self):
18        hash_url = 'http://other_url?:action=show_md5&'
19        hash_url += 'digest=0123456789abcdef0123456789abcdef'
20        doc = """
21            <a href="http://some_url">Name</a>
22            (<a title="MD5 hash"
23            href="{hash_url}">md5</a>)
24        """.lstrip().format(**locals())
25        assert setuptools.package_index.PYPI_MD5.match(doc)
26
27    def test_bad_url_bad_port(self):
28        index = setuptools.package_index.PackageIndex()
29        url = 'http://127.0.0.1:0/nonesuch/test_package_index'
30        try:
31            v = index.open_url(url)
32        except Exception as v:
33            assert url in str(v)
34        else:
35            assert isinstance(v, urllib.error.HTTPError)
36
37    def test_bad_url_typo(self):
38        # issue 16
39        # easy_install inquant.contentmirror.plone breaks because of a typo
40        # in its home URL
41        index = setuptools.package_index.PackageIndex(
42            hosts=('www.example.com',)
43        )
44
45        url = (
46            'url:%20https://svn.plone.org/svn'
47            '/collective/inquant.contentmirror.plone/trunk'
48        )
49        try:
50            v = index.open_url(url)
51        except Exception as v:
52            assert url in str(v)
53        else:
54            assert isinstance(v, urllib.error.HTTPError)
55
56    def test_bad_url_bad_status_line(self):
57        index = setuptools.package_index.PackageIndex(
58            hosts=('www.example.com',)
59        )
60
61        def _urlopen(*args):
62            raise http.client.BadStatusLine('line')
63
64        index.opener = _urlopen
65        url = 'http://example.com'
66        try:
67            index.open_url(url)
68        except Exception as exc:
69            assert 'line' in str(exc)
70        else:
71            raise AssertionError('Should have raise here!')
72
73    def test_bad_url_double_scheme(self):
74        """
75        A bad URL with a double scheme should raise a DistutilsError.
76        """
77        index = setuptools.package_index.PackageIndex(
78            hosts=('www.example.com',)
79        )
80
81        # issue 20
82        url = 'http://http://svn.pythonpaste.org/Paste/wphp/trunk'
83        try:
84            index.open_url(url)
85        except distutils.errors.DistutilsError as error:
86            msg = str(error)
87            assert (
88                'nonnumeric port' in msg
89                or 'getaddrinfo failed' in msg
90                or 'Name or service not known' in msg
91            )
92            return
93        raise RuntimeError("Did not raise")
94
95    def test_bad_url_screwy_href(self):
96        index = setuptools.package_index.PackageIndex(
97            hosts=('www.example.com',)
98        )
99
100        # issue #160
101        if sys.version_info[0] == 2 and sys.version_info[1] == 7:
102            # this should not fail
103            url = 'http://example.com'
104            page = ('<a href="http://www.famfamfam.com]('
105                    'http://www.famfamfam.com/">')
106            index.process_index(url, page)
107
108    def test_url_ok(self):
109        index = setuptools.package_index.PackageIndex(
110            hosts=('www.example.com',)
111        )
112        url = 'file:///tmp/test_package_index'
113        assert index.url_ok(url, True)
114
115    def test_parse_bdist_wininst(self):
116        parse = setuptools.package_index.parse_bdist_wininst
117
118        actual = parse('reportlab-2.5.win32-py2.4.exe')
119        expected = 'reportlab-2.5', '2.4', 'win32'
120        assert actual == expected
121
122        actual = parse('reportlab-2.5.win32.exe')
123        expected = 'reportlab-2.5', None, 'win32'
124        assert actual == expected
125
126        actual = parse('reportlab-2.5.win-amd64-py2.7.exe')
127        expected = 'reportlab-2.5', '2.7', 'win-amd64'
128        assert actual == expected
129
130        actual = parse('reportlab-2.5.win-amd64.exe')
131        expected = 'reportlab-2.5', None, 'win-amd64'
132        assert actual == expected
133
134    def test__vcs_split_rev_from_url(self):
135        """
136        Test the basic usage of _vcs_split_rev_from_url
137        """
138        vsrfu = setuptools.package_index.PackageIndex._vcs_split_rev_from_url
139        url, rev = vsrfu('https://example.com/bar@2995')
140        assert url == 'https://example.com/bar'
141        assert rev == '2995'
142
143    def test_local_index(self, tmpdir):
144        """
145        local_open should be able to read an index from the file system.
146        """
147        index_file = tmpdir / 'index.html'
148        with index_file.open('w') as f:
149            f.write('<div>content</div>')
150        url = 'file:' + urllib.request.pathname2url(str(tmpdir)) + '/'
151        res = setuptools.package_index.local_open(url)
152        assert 'content' in res.read()
153
154    def test_egg_fragment(self):
155        """
156        EGG fragments must comply to PEP 440
157        """
158        epoch = [
159            '',
160            '1!',
161        ]
162        releases = [
163            '0',
164            '0.0',
165            '0.0.0',
166        ]
167        pre = [
168            'a0',
169            'b0',
170            'rc0',
171        ]
172        post = [
173            '.post0'
174        ]
175        dev = [
176            '.dev0',
177        ]
178        local = [
179            ('', ''),
180            ('+ubuntu.0', '+ubuntu.0'),
181            ('+ubuntu-0', '+ubuntu.0'),
182            ('+ubuntu_0', '+ubuntu.0'),
183        ]
184        versions = [
185            [''.join([e, r, p, loc]) for loc in locs]
186            for e in epoch
187            for r in releases
188            for p in sum([pre, post, dev], [''])
189            for locs in local]
190        for v, vc in versions:
191            dists = list(setuptools.package_index.distros_for_url(
192                'http://example.com/example.zip#egg=example-' + v))
193            assert dists[0].version == ''
194            assert dists[1].version == vc
195
196    def test_download_git_with_rev(self, tmpdir):
197        url = 'git+https://github.example/group/project@master#egg=foo'
198        index = setuptools.package_index.PackageIndex()
199
200        with mock.patch("os.system") as os_system_mock:
201            result = index.download(url, str(tmpdir))
202
203        os_system_mock.assert_called()
204
205        expected_dir = str(tmpdir / 'project@master')
206        expected = (
207            'git clone --quiet '
208            'https://github.example/group/project {expected_dir}'
209        ).format(**locals())
210        first_call_args = os_system_mock.call_args_list[0][0]
211        assert first_call_args == (expected,)
212
213        tmpl = 'git -C {expected_dir} checkout --quiet master'
214        expected = tmpl.format(**locals())
215        assert os_system_mock.call_args_list[1][0] == (expected,)
216        assert result == expected_dir
217
218    def test_download_git_no_rev(self, tmpdir):
219        url = 'git+https://github.example/group/project#egg=foo'
220        index = setuptools.package_index.PackageIndex()
221
222        with mock.patch("os.system") as os_system_mock:
223            result = index.download(url, str(tmpdir))
224
225        os_system_mock.assert_called()
226
227        expected_dir = str(tmpdir / 'project')
228        expected = (
229            'git clone --quiet '
230            'https://github.example/group/project {expected_dir}'
231        ).format(**locals())
232        os_system_mock.assert_called_once_with(expected)
233
234    def test_download_svn(self, tmpdir):
235        url = 'svn+https://svn.example/project#egg=foo'
236        index = setuptools.package_index.PackageIndex()
237
238        with pytest.warns(UserWarning):
239            with mock.patch("os.system") as os_system_mock:
240                result = index.download(url, str(tmpdir))
241
242        os_system_mock.assert_called()
243
244        expected_dir = str(tmpdir / 'project')
245        expected = (
246            'svn checkout -q '
247            'svn+https://svn.example/project {expected_dir}'
248        ).format(**locals())
249        os_system_mock.assert_called_once_with(expected)
250
251
252class TestContentCheckers:
253    def test_md5(self):
254        checker = setuptools.package_index.HashChecker.from_url(
255            'http://foo/bar#md5=f12895fdffbd45007040d2e44df98478')
256        checker.feed('You should probably not be using MD5'.encode('ascii'))
257        assert checker.hash.hexdigest() == 'f12895fdffbd45007040d2e44df98478'
258        assert checker.is_valid()
259
260    def test_other_fragment(self):
261        "Content checks should succeed silently if no hash is present"
262        checker = setuptools.package_index.HashChecker.from_url(
263            'http://foo/bar#something%20completely%20different')
264        checker.feed('anything'.encode('ascii'))
265        assert checker.is_valid()
266
267    def test_blank_md5(self):
268        "Content checks should succeed if a hash is empty"
269        checker = setuptools.package_index.HashChecker.from_url(
270            'http://foo/bar#md5=')
271        checker.feed('anything'.encode('ascii'))
272        assert checker.is_valid()
273
274    def test_get_hash_name_md5(self):
275        checker = setuptools.package_index.HashChecker.from_url(
276            'http://foo/bar#md5=f12895fdffbd45007040d2e44df98478')
277        assert checker.hash_name == 'md5'
278
279    def test_report(self):
280        checker = setuptools.package_index.HashChecker.from_url(
281            'http://foo/bar#md5=f12895fdffbd45007040d2e44df98478')
282        rep = checker.report(lambda x: x, 'My message about %s')
283        assert rep == 'My message about md5'
284
285
286@pytest.fixture
287def temp_home(tmpdir, monkeypatch):
288    key = (
289        'USERPROFILE'
290        if platform.system() == 'Windows' and sys.version_info > (3, 8) else
291        'HOME'
292    )
293
294    monkeypatch.setitem(os.environ, key, str(tmpdir))
295    return tmpdir
296
297
298class TestPyPIConfig:
299    def test_percent_in_password(self, temp_home):
300        pypirc = temp_home / '.pypirc'
301        pypirc.write(DALS("""
302            [pypi]
303            repository=https://pypi.org
304            username=jaraco
305            password=pity%
306        """))
307        cfg = setuptools.package_index.PyPIConfig()
308        cred = cfg.creds_by_repository['https://pypi.org']
309        assert cred.username == 'jaraco'
310        assert cred.password == 'pity%'
311