From 65b892d816eeb8b385416fde6ed7e5cd593cd029 Mon Sep 17 00:00:00 2001 From: Eduardo Enriquez Date: Thu, 9 May 2019 22:00:02 +0200 Subject: [PATCH 1/4] Changes test_cache, test_logger to pytest, updated requirements.txt and scripts --- .travis.yml | 2 +- requirements.txt | 7 +- test | 2 +- testdev | 2 +- testdev.bat | 2 +- tests/cache/test_cache.py | 41 ++ tests/cache/test_comparisons.py | 98 +++++ tests/cache/test_remove.py | 86 ++++ tests/cache/test_selection.py | 376 +++++++++++++++++ tests/cache/test_store.py | 35 ++ tests/conftest.py | 32 ++ tests/test_cache.py | 710 -------------------------------- tests/test_logger.py | 51 +-- 13 files changed, 703 insertions(+), 741 deletions(-) create mode 100644 tests/cache/test_cache.py create mode 100644 tests/cache/test_comparisons.py create mode 100644 tests/cache/test_remove.py create mode 100644 tests/cache/test_selection.py create mode 100644 tests/cache/test_store.py create mode 100644 tests/conftest.py delete mode 100644 tests/test_cache.py diff --git a/.travis.yml b/.travis.yml index ece67c5..aabe23d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,7 @@ install: - "pip install -r requirements.txt" - if [[ $TRAVIS_PYTHON_VERSION == '3.6' ]]; then pip install python-coveralls ; fi script: - - "nosetests --with-xcoverage --cover-package=fades -v fades tests" + - "pytest -n 4 --cov=fades" after_script: - "flake8 fades --max-line-length=99 --select=E,W,F,C,N" diff --git a/requirements.txt b/requirements.txt index 4891207..c1779f8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,6 @@ flake8==3.5.0 logassert==2 mccabe==0.6.1 -nose==1.3.7 -nosexcover==1.0.10 pep257==0.7.0 pep8==1.7.0 pycodestyle==2.3.1 @@ -12,3 +10,8 @@ pyxdg==0.25 rst2html5==1.9.3 setuptools>=5.5 wheel==0.26.0 +pytest==4.4.2 +pytest-xdist==1.28.0 +pytest-sugar==0.9.2 +pytest-cov==2.7.1 +pytest-mock==1.10.4 diff --git a/test b/test index f9712df..433668f 100755 --- a/test +++ b/test @@ -12,7 +12,7 @@ fi FADES='./bin/fades -r requirements.txt' -$FADES -x nosetests --with-xcoverage --cover-package=fades -v -s $TARGET_TESTS +$FADES -x pytest -n 4 --cov=fades # check if we are using exit() in the code. if grep -r -n ' exit(' --include="*.py" .; then echo 'Please use sys.exit() instead of exit(). https://github.com/PyAr/fades/issues/280'; fi diff --git a/testdev b/testdev index e45a255..26f360b 100755 --- a/testdev +++ b/testdev @@ -10,4 +10,4 @@ else TARGET_TESTS="fades tests" fi -./bin/fades -r requirements.txt -x nosetests -v -s $TARGET_TESTS +./bin/fades -r requirements.txt -x pytest -v -s $TARGET_TESTS diff --git a/testdev.bat b/testdev.bat index 56e0a62..035faf5 100644 --- a/testdev.bat +++ b/testdev.bat @@ -8,4 +8,4 @@ if not [%*] == [] ( set TARGET_TESTS=fades tests ) -bin\fades -r requirements.txt -x nosetests -v -s %TARGET_TESTS% +bin\fades -r requirements.txt -x pytest -v -s %TARGET_TESTS% diff --git a/tests/cache/test_cache.py b/tests/cache/test_cache.py new file mode 100644 index 0000000..043802c --- /dev/null +++ b/tests/cache/test_cache.py @@ -0,0 +1,41 @@ +"""Tests for the helpers.""" +from fades import cache + + +def test_missing_file_pytest(tmp_file, mocker): + venvscache = cache.VEnvsCache(str(tmp_file)) + mock = mocker.patch.object(venvscache, '_select') + mock.return_value = None + resp = venvscache.get_venv('requirements', 'interpreter', uuid='', options='options') + mock.assert_called_with([], 'requirements', 'interpreter', uuid='', options='options') + assert not resp + + +def test_empty_file_pytest(tmp_file, mocker): + open(tmp_file, 'wt', encoding='utf8').close() + venvscache = cache.VEnvsCache(tmp_file) + mock = mocker.patch.object(venvscache, '_select', return_value=None) + resp = venvscache.get_venv('requirements', 'interpreter') + mock.assert_called_with([], 'requirements', 'interpreter', uuid='', options=None) + assert not resp + + +def test_some_file_content_pytest(tmp_file, mocker): + with open(tmp_file, 'wt', encoding='utf8') as fh: + fh.write('foo\nbar\n') + venvscache = cache.VEnvsCache(tmp_file) + mock = mocker.patch.object(venvscache, '_select', return_value="resp") + resp = venvscache.get_venv('requirements', 'interpreter', uuid='', options='options') + mock.assert_called_with(['foo', 'bar'], 'requirements', 'interpreter', uuid='', + options='options') + assert resp == 'resp' + + +def test_get_by_uuid_pytest(tmp_file, mocker): + with open(tmp_file, 'wt', encoding='utf8') as fh: + fh.write('foo\nbar\n') + venvscache = cache.VEnvsCache(tmp_file) + mock = mocker.patch.object(venvscache, '_select', return_value='resp') + resp = venvscache.get_venv(uuid='uuid') + mock.assert_called_with(['foo', 'bar'], None, '', uuid='uuid', options=None) + assert resp == 'resp' diff --git a/tests/cache/test_comparisons.py b/tests/cache/test_comparisons.py new file mode 100644 index 0000000..81ec1c4 --- /dev/null +++ b/tests/cache/test_comparisons.py @@ -0,0 +1,98 @@ +import json +import pytest + +from fades import parsing + +from tests.conftest import get_req, get_distrib + + +@pytest.mark.parametrize("req,installed,expected", [ + ("==5", "5", "ok"), + ("==5", "2", None), + (">5", "4", None), + (">5", "5", None), + (">5", "6", "ok"), + (">=5", "4", None), + (">=5", "5", "ok"), + (">=5", "6", "ok"), + ("<5", "4", "ok"), + ("<5", "5", None), + ("<5", "6", None), + ("<=5", "4", "ok"), + ("<=5", "5", "ok"), + ("<=5", "6", None), + ("== 2.5", "2.5.0", "ok"), + ("> 2.7", "2.12", "ok"), + ("> 2.7a0", "2.7", "ok"), + ("> 2.7", "2.7a0", None), + (">1.6,<1.9,!=1.9.6", "1.5.0", None), + (">1.6,<1.9,!=1.9.6", "1.6.7", "ok"), + (">1.6,<1.9,!=1.8.6", "1.8.7", "ok"), + (">1.6,<1.9,!=1.9.6", "1.9.6", None), +]) +def test_check_versions(venvscache, req, installed, expected): + """The comparison in the selection.""" + reqs = {"pypi": get_req("dep" + req)} + interpreter = "pythonX.Y" + options = {"foo": "bar"} + venv = json.dumps({ + "metadata": "ok", + "installed": {"pypi": {"dep": installed}}, + "interpreter": "pythonX.Y", + "options": {"foo": "bar"} + }) + resp = venvscache._select([venv], reqs, interpreter, uuid="", options=options) + assert resp == expected + + +@pytest.mark.parametrize("possible_venvs", [ + [ + (get_distrib(('dep', '3')), 'venv_best_fit'), + ], + [ + (get_distrib(('dep1', '3'), ('dep2', '3')), 'venv_best_fit'), + ], + [ + (get_distrib(('dep', '5')), 'venv_best_fit'), + (get_distrib(('dep', '3')), 'venv_1'), + ], + [ + (get_distrib(('dep1', '5'), ('dep2', '7')), 'venv_best_fit'), + (get_distrib(('dep1', '3'), ('dep2', '6')), 'venv_1'), + ], + [ + (get_distrib(('dep1', '3'), ('dep2', '9')), 'venv_1'), + (get_distrib(('dep1', '5'), ('dep2', '7')), 'venv_best_fit'), + ], + [ + (get_distrib(('dep1', '5'), ('dep2', '7')), 'venv_1'), + (get_distrib(('dep1', '3'), ('dep2', '9')), 'venv_best_fit'), + ], + [ + (get_distrib(('dep1', '3'), ('dep2', '9'), ('dep3', '4')), 'venv_best_fit'), + (get_distrib(('dep1', '5'), ('dep2', '7'), ('dep3', '2')), 'venv_1'), + ], + [ + (get_distrib(('dep2', '3'), ('dep1', '2'), ('dep3', '8')), 'venv_best_fit'), + (get_distrib(('dep1', '7'), ('dep3', '5'), ('dep2', '2')), 'venv_1'), + ], + [ + (get_distrib(('dep1', '3'), ('dep2', '2')), 'venv_1'), + (get_distrib(('dep1', '4'), ('dep2', '2')), 'venv_2'), + (get_distrib(('dep1', '5'), ('dep2', '7')), 'venv_best_fit'), + (get_distrib(('dep1', '5'), ('dep2', '6')), 'venv_3'), + ], + [ + ([parsing.VCSDependency('someurl')], 'venv_best_fit'), + ], + [ + ([parsing.VCSDependency('someurl')] + get_distrib(('dep', '3')), 'venv_best_fit'), + ], + [ + ([parsing.VCSDependency('someurl')] + get_distrib(('dep', '3')), 'venv_best_fit'), + ([parsing.VCSDependency('someurl')] + get_distrib(('dep', '1')), 'venv_1'), + ], +]) +def test_best_fit(venvscache, possible_venvs): + """Check the venv best fitting decissor.""" + assert venvscache._select_better_fit(possible_venvs) == 'venv_best_fit' diff --git a/tests/cache/test_remove.py b/tests/cache/test_remove.py new file mode 100644 index 0000000..db7949c --- /dev/null +++ b/tests/cache/test_remove.py @@ -0,0 +1,86 @@ +import json +import os +import time +import pytest + +from threading import Thread + +from fades import cache + + +def test_missing_file(tmp_file): + venvscache = cache.VEnvsCache(tmp_file) + venvscache.remove('missing/path') + + lines = venvscache._read_cache() + assert lines == [] + + +def test_missing_env_in_cache(tmp_file): + venvscache = cache.VEnvsCache(tmp_file) + options = {'foo': 'bar'} + venvscache.store('installed', {'env_path': 'some/path'}, 'interpreter', options=options) + lines = venvscache._read_cache() + assert len(lines) == 1 + + venvscache.remove('some/path') + + lines = venvscache._read_cache() + assert lines == [] + + +def test_preserve_cache_data_ordering(tmp_file): + venvscache = cache.VEnvsCache(tmp_file) + # store 3 venvs + options = {'foo': 'bar'} + venvscache.store('installed1', {'env_path': 'path/env1'}, 'interpreter', options=options) + venvscache.store('installed2', {'env_path': 'path/env2'}, 'interpreter', options=options) + venvscache.store('installed3', {'env_path': 'path/env3'}, 'interpreter', options=options) + + venvscache.remove('path/env2') + + lines = venvscache._read_cache() + assert len(lines) == 2 + assert json.loads(lines[0]).get('metadata').get('env_path') == 'path/env1' + assert json.loads(lines[1]).get('metadata').get('env_path') == 'path/env3' + + +@pytest.mark.skip(reason="I dont know why is not working with pytest") +def test_lock_cache_for_remove(tmp_file, mocker): + venvscache = cache.VEnvsCache(tmp_file) + # store 3 venvs + options = {'foo': 'bar'} + venvscache.store('installed1', {'env_path': 'path/env1'}, 'interpreter', options=options) + venvscache.store('installed2', {'env_path': 'path/env2'}, 'interpreter', options=options) + venvscache.store('installed3', {'env_path': 'path/env3'}, 'interpreter', options=options) + + # patch _write_cache so it emulates a slow write during which + # another process managed to modify the cache file before the + # first process finished writing the modified cache data + original_write_cache = venvscache._write_cache + p = mocker.patch('fades.cache.VEnvsCache._write_cache') + mock_write_cache = p.start() + + t1 = Thread(target=venvscache.remove, args=('path/env1',)) + + def slow_write_cache(*args, **kwargs): + p.stop() + t1.start() + # wait to ensure t1 thread must wait for lock to be released + time.sleep(0.01) + original_write_cache(*args, **kwargs) + + mock_write_cache.side_effect = slow_write_cache + + # just a sanity check + assert not os.path.exists(venvscache.filepath + '.lock') + # remove a virtualenv from the cache + venvscache.remove('path/env2') + t1.join() + + # when cache file is properly locked both virtualenvs + # will have been removed from the cache + lines = venvscache._read_cache() + assert len(lines) == 1 + assert json.loads(lines[0]).get('metadata').get('env_path') == 'path/env3' + assert not os.path.exists(venvscache.filepath + '.lock') diff --git a/tests/cache/test_selection.py b/tests/cache/test_selection.py new file mode 100644 index 0000000..e3ace0b --- /dev/null +++ b/tests/cache/test_selection.py @@ -0,0 +1,376 @@ +import os +import json +import uuid + +from fades import helpers, parsing +from tests.conftest import get_req + + +def test_empty(venvscache): + resp = venvscache._select([], {}, 'pythonX.Y', 'options') + assert resp is None + + +def test_nomatch_repo_dependency(venvscache): + reqs = {"repoloco": get_req('dep == 5')} + interpreter = 'pythonX.Y' + options = {'foo': 'bar'} + venv = json.dumps({ + 'metadata': 'foobar', + 'installed': {'pypi': {'dep': '5'}}, + 'interpreter': 'pythonX.Y', + 'options': {'foo': 'bar'} + }) + resp = venvscache._select([venv], reqs, interpreter, uuid='', options=options) + assert resp is None + + +def test_nomatch_pypi_dependency(venvscache): + reqs = {'pypi': get_req('dep1 == 5')} + interpreter = 'pythonX.Y' + options = {'foo': 'bar'} + venv = json.dumps({ + 'metadata': 'foobar', + 'installed': {'pypi': {'dep2': '5'}}, + 'interpreter': 'pythonX.Y', + 'options': {'foo': 'bar'} + }) + resp = venvscache._select([venv], reqs, interpreter, uuid='', options=options) + resp is None + + +def test_nomatch_vcs_dependency(venvscache): + reqs = {'vcs': [parsing.VCSDependency('someurl')]} + interpreter = 'pythonX.Y' + options = {'foo': 'bar'} + venv = json.dumps({ + 'metadata': 'foobar', + 'installed': {'vcs': {'otherurl': None}}, + 'interpreter': 'pythonX.Y', + 'options': {'foo': 'bar'} + }) + resp = venvscache._select([venv], reqs, interpreter, uuid='', options=options) + assert resp is None + + +def test_nomatch_version(venvscache): + reqs = {'pypi': get_req('dep == 5')} + interpreter = 'pythonX.Y' + options = {'foo': 'bar'} + venv = json.dumps({ + 'metadata': 'foobar', + 'installed': {'pypi': {'dep': '7'}}, + 'interpreter': 'pythonX.Y', + 'options': {'foo': 'bar'} + }) + resp = venvscache._select([venv], reqs, interpreter, uuid='', options=options) + assert resp is None + + +def test_simple_pypi_match(venvscache): + reqs = {'pypi': get_req('dep == 5')} + interpreter = 'pythonX.Y' + options = {'foo': 'bar'} + venv = json.dumps({ + 'metadata': 'foobar', + 'installed': {'pypi': {'dep': '5'}}, + 'interpreter': 'pythonX.Y', + 'options': {'foo': 'bar'} + }) + resp = venvscache._select([venv], reqs, interpreter, uuid='', options=options) + assert resp == 'foobar' + + +def test_simple_vcs_match(venvscache): + reqs = {'vcs': [parsing.VCSDependency('someurl')]} + interpreter = 'pythonX.Y' + options = {'foo': 'bar'} + venv = json.dumps({ + 'metadata': 'foobar', + 'installed': {'vcs': {'someurl': None}}, + 'interpreter': 'pythonX.Y', + 'options': {'foo': 'bar'} + }) + resp = venvscache ._select([venv], reqs, interpreter, uuid='', options=options) + assert resp == 'foobar' + + +def test_match_mixed_single(venvscache): + reqs = {'vcs': [parsing.VCSDependency('someurl')], 'pypi': get_req('dep == 5')} + interpreter = 'pythonX.Y' + options = {'foo': 'bar'} + venv1 = json.dumps({ + 'metadata': 'foobar1', + 'installed': {'vcs': {'someurl': None}, 'pypi': {'dep': '5'}}, + 'interpreter': 'pythonX.Y', + 'options': {'foo': 'bar'} + }) + venv2 = json.dumps({ + 'metadata': 'foobar2', + 'installed': {'pypi': {'dep': '5'}}, + 'interpreter': 'pythonX.Y', + 'options': {'foo': 'bar'} + }) + venv3 = json.dumps({ + 'metadata': 'foobar3', + 'installed': {'vcs': {'someurl': None}}, + 'interpreter': 'pythonX.Y', + 'options': {'foo': 'bar'} + }) + resp = venvscache ._select( + [venv1, venv2, venv3], reqs, interpreter, uuid='', options=options) + assert resp == 'foobar1' + + +def test_match_mixed_multiple(venvscache): + reqs = {'vcs': [parsing.VCSDependency('url1'), parsing.VCSDependency('url2')], + 'pypi': get_req(['dep1 == 5', 'dep2'])} + interpreter = 'pythonX.Y' + options = {'foo': 'bar'} + venv = json.dumps({ + 'metadata': 'foobar', + 'installed': { + 'vcs': {'url1': None, 'url2': None}, + 'pypi': {'dep1': '5', 'dep2': '7'}}, + 'interpreter': 'pythonX.Y', + 'options': {'foo': 'bar'} + }) + resp = venvscache ._select([venv], reqs, interpreter, uuid='', options=options) + assert resp == 'foobar' + + +def test_match_noversion(venvscache): + reqs = {'pypi': get_req('dep')} + interpreter = 'pythonX.Y' + options = {'foo': 'bar'} + venv = json.dumps({ + 'metadata': 'foobar', + 'installed': {'pypi': {'dep': '5'}}, + 'interpreter': 'pythonX.Y', + 'options': {'foo': 'bar'} + }) + resp = venvscache ._select([venv], reqs, interpreter, uuid='', options=options) + assert resp == 'foobar' + + +def test_middle_match(venvscache): + reqs = {'pypi': get_req('dep == 5')} + interpreter = 'pythonX.Y' + options = {'foo': 'bar'} + venv1 = json.dumps({ + 'metadata': 'venv1', + 'installed': {'pypi': {'dep': '3'}}, + 'interpreter': 'pythonX.Y', + 'options': {'foo': 'bar'} + }) + venv2 = json.dumps({ + 'metadata': 'venv2', + 'installed': {'pypi': {'dep': '5'}}, + 'interpreter': 'pythonX.Y', + 'options': {'foo': 'bar'} + }) + venv3 = json.dumps({ + 'metadata': 'venv3', + 'installed': {'pypi': {'dep': '7'}}, + 'interpreter': 'pythonX.Y', + 'options': {'foo': 'bar'} + }) + resp = venvscache ._select([venv1, venv2, venv3], reqs, interpreter, uuid='', + options=options) + assert resp == 'venv2' + + +def test_multiple_match_bigger_version(venvscache): + reqs = {'pypi': get_req('dep')} + interpreter = 'pythonX.Y' + options = {'foo': 'bar'} + venv1 = json.dumps({ + 'metadata': 'venv1', + 'installed': {'pypi': {'dep': '3'}}, + 'interpreter': 'pythonX.Y', + 'options': {'foo': 'bar'} + }) + venv2 = json.dumps({ + 'metadata': 'venv2', + 'installed': {'pypi': {'dep': '7'}}, + 'interpreter': 'pythonX.Y', + 'options': {'foo': 'bar'} + }) + venv3 = json.dumps({ + 'metadata': 'venv3', + 'installed': {'pypi': {'dep': '5'}}, + 'interpreter': 'pythonX.Y', + 'options': {'foo': 'bar'} + }) + resp = venvscache ._select([venv1, venv2, venv3], reqs, interpreter, uuid='', + options=options) + # matches venv2 because it has the bigger version for 'dep' (even if it's not the + # latest virtualenv created) + assert resp == 'venv2' + + +def test_multiple_deps_ok(venvscache): + reqs = {'pypi': get_req(['dep1 == 5', 'dep2 == 7'])} + interpreter = 'pythonX.Y' + options = {'foo': 'bar'} + venv = json.dumps({ + 'metadata': 'foobar', + 'installed': {'pypi': {'dep1': '5', 'dep2': '7'}}, + 'interpreter': 'pythonX.Y', + 'options': {'foo': 'bar'} + }) + resp = venvscache ._select([venv], reqs, interpreter, uuid='', options=options) + assert resp == 'foobar' + + +def test_multiple_deps_just_one(venvscache): + reqs = {'pypi': get_req(['dep1 == 5', 'dep2 == 7'])} + interpreter = 'pythonX.Y' + options = {'foo': 'bar'} + venv = json.dumps({ + 'metadata': 'foobar', + 'installed': {'pypi': {'dep1': '5', 'dep2': '2'}}, + 'interpreter': 'pythonX.Y', + 'options': {'foo': 'bar'} + }) + resp = venvscache ._select([venv], reqs, interpreter, uuid='', options=options) + assert resp is None + + +def test_not_too_crowded(venvscache): + reqs = {'pypi': get_req(['dep1'])} + interpreter = 'pythonX.Y' + options = {'foo': 'bar'} + venv = json.dumps({ + 'metadata': 'foobar', + 'installed': {'pypi': {'dep1': '5', 'dep2': '2'}}, + 'interpreter': 'pythonX.Y', + 'options': {'foo': 'bar'} + }) + resp = venvscache ._select([venv], reqs, interpreter, uuid='', options=options) + assert resp is None + + +def test_same_quantity_different_deps(venvscache): + reqs = {'pypi': get_req(['dep1', 'dep2'])} + interpreter = 'pythonX.Y' + options = {'foo': 'bar'} + venv = json.dumps({ + 'metadata': 'foobar', + 'installed': {'pypi': {'dep1': '5', 'dep3': '2'}}, + 'interpreter': 'pythonX.Y', + 'options': {'foo': 'bar'} + }) + resp = venvscache ._select([venv], reqs, interpreter, uuid='', options=options) + assert resp is None + + +def test_no_requirements_some_installed(venvscache): + reqs = {} + interpreter = 'pythonX.Y' + options = {'foo': 'bar'} + venv = json.dumps({ + 'metadata': 'foobar', + 'installed': {'pypi': {'dep1': '5', 'dep3': '2'}}, + 'interpreter': 'pythonX.Y', + 'options': {'foo': 'bar'} + }) + resp = venvscache ._select([venv], reqs, interpreter, uuid='', options=options) + assert resp is None + + +def test_no_requirements_empty_venv(venvscache): + reqs = {} + interpreter = 'pythonX.Y' + options = {'foo': 'bar'} + venv = json.dumps({ + 'metadata': 'foobar', + 'installed': {}, + 'interpreter': 'pythonX.Y', + 'options': {'foo': 'bar'} + }) + resp = venvscache ._select([venv], reqs, interpreter, uuid='', options=options) + assert resp == "foobar" + + +def test_simple_match_empty_options(venvscache): + reqs = {'pypi': get_req('dep == 5')} + interpreter = 'pythonX.Y' + options = {} + venv = json.dumps({ + 'metadata': 'foobar', + 'installed': {'pypi': {'dep': '5'}}, + 'interpreter': 'pythonX.Y', + 'options': {} + }) + resp = venvscache ._select([venv], reqs, interpreter, uuid='', options=options) + assert resp == "foobar" + + +def test_no_match_due_to_options(venvscache): + reqs = {'pypi': get_req('dep == 5')} + interpreter = 'pythonX.Y' + options = {'foo': 'bar'} + venv = json.dumps({ + 'metadata': 'foobar', + 'installed': {'pypi': {'dep': '5'}}, + 'interpreter': 'pythonX.Y', + 'options': {} + }) + resp = venvscache ._select([venv], reqs, interpreter, uuid='', options=options) + assert resp is None + + +def test_match_due_to_options(venvscache): + reqs = {'pypi': get_req('dep == 5')} + interpreter = 'pythonX.Y' + options = {'foo': 'bar'} + venv1 = json.dumps({ + 'metadata': 'venv1', + 'installed': {'pypi': {'dep': '5'}}, + 'interpreter': 'pythonX.Y', + 'options': {} + }) + venv2 = json.dumps({ + 'metadata': 'venv2', + 'installed': {'pypi': {'dep': '5'}}, + 'interpreter': 'pythonX.Y', + 'options': {'foo': 'bar'} + }) + resp = venvscache ._select([venv1, venv2], reqs, interpreter, uuid='', options=options) + assert resp == "venv2" + + +def test_no_deps_but_options(venvscache): + reqs = {} + interpreter = 'pythonX.Y' + options = {'foo': 'bar'} + venv1 = json.dumps({ + 'metadata': 'venv1', + 'installed': {}, + 'interpreter': 'pythonX.Y', + 'options': {} + }) + venv2 = json.dumps({ + 'metadata': 'venv2', + 'installed': {}, + 'interpreter': 'pythonX.Y', + 'options': {'foo': 'bar'} + }) + resp = venvscache ._select([venv1, venv2], reqs, interpreter, uuid='', options=options) + assert resp == "venv2" + + +def test_match_uuid(venvscache): + venv_uuid = str(uuid.uuid4()) + metadata = { + 'env_path': os.path.join(helpers.get_basedir(), venv_uuid), + } + venv = json.dumps({ + 'metadata': metadata, + 'installed': {}, + 'interpreter': 'pythonX.Y', + 'options': {'foo': 'bar'} + }) + resp = venvscache ._select([venv], uuid=venv_uuid) + assert resp == metadata diff --git a/tests/cache/test_store.py b/tests/cache/test_store.py new file mode 100644 index 0000000..74c61c8 --- /dev/null +++ b/tests/cache/test_store.py @@ -0,0 +1,35 @@ +import json + +from fades import cache + + +def test_missing_file(tmp_file): + venvscache = cache.VEnvsCache(tmp_file) + venvscache.store('installed', 'metadata', 'interpreter', 'options') + + with open(tmp_file, 'rt', encoding='utf8') as fh: + data = json.loads(fh.readline()) + assert 'timestamp' in data + assert data['installed'], 'installed' + assert data['metadata'], 'metadata' + assert data['interpreter'], 'interpreter' + assert data['options'], 'options' + + +def test_with_previous_content(tmp_file): + with open(tmp_file, 'wt', encoding='utf8') as fh: + fh.write(json.dumps({'foo': 'bar'}) + '\n') + + venvscache = cache.VEnvsCache(tmp_file) + venvscache.store('installed', 'metadata', 'interpreter', 'options') + + with open(tmp_file, 'rt', encoding='utf8') as fh: + data = json.loads(fh.readline()) + assert data, {'foo': 'bar'} + + data = json.loads(fh.readline()) + assert 'timestamp' in data + assert data['installed'], 'installed' + assert data['metadata'], 'metadata' + assert data['interpreter'], 'interpreter' + assert data['options'], 'options' diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..495f7c4 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,32 @@ +import os +import pytest + +from pkg_resources import parse_requirements, Distribution + +from fades import cache + + +@pytest.fixture(scope="function") +def tmp_file(tmpdir_factory): + path = tmpdir_factory.mktemp("test").join("foo.bar") + yield tmpdir_factory.mktemp("test").join("foo.bar") + if os.path.isfile(path): + os.remove(path) + + +@pytest.fixture(scope="function") +def venvscache(tmpdir_factory): + path = tmpdir_factory.mktemp("test").join("foo.bar") + venvs_cache = cache.VEnvsCache(path) + yield venvs_cache + del venvs_cache + + +def get_req(text): + """Transform a text requirement into the pkg_resources object.""" + return list(parse_requirements(text)) + + +def get_distrib(*dep_ver_pairs): + """Build some Distributions with indicated info.""" + return [Distribution(project_name=dep, version=ver) for dep, ver in dep_ver_pairs] diff --git a/tests/test_cache.py b/tests/test_cache.py deleted file mode 100644 index 3afc8af..0000000 --- a/tests/test_cache.py +++ /dev/null @@ -1,710 +0,0 @@ -# Copyright 2015-2017 Facundo Batista, Nicolás Demarchi -# -# This program is free software: you can redistribute it and/or modify it -# under the terms of the GNU General Public License version 3, as published -# by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranties of -# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR -# PURPOSE. See the GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License along -# with this program. If not, see . -# -# For further info, check https://github.com/PyAr/fades - -"""Tests for the helpers.""" - -import json -import os -import time -import unittest -import uuid - -from threading import Thread -from unittest.mock import patch - -from pkg_resources import parse_requirements, Distribution - -from fades import cache, helpers, parsing -from tests import get_tempfile - - -def get_req(text): - """Transform a text requirement into the pkg_resources object.""" - return list(parse_requirements(text)) - - -def get_distrib(*dep_ver_pairs): - """Build some Distributions with indicated info.""" - return [Distribution(project_name=dep, version=ver) for dep, ver in dep_ver_pairs] - - -class GetTestCase(unittest.TestCase): - """A shallow 'get'.""" - - def setUp(self): - super().setUp() - self.tempfile = get_tempfile(self) - - def test_missing_file(self): - os.remove(self.tempfile) - venvscache = cache.VEnvsCache(self.tempfile) - with patch.object(venvscache, '_select') as mock: - mock.return_value = None - resp = venvscache.get_venv('requirements', 'interpreter', uuid='', options='options') - mock.assert_called_with([], 'requirements', 'interpreter', uuid='', options='options') - self.assertEqual(resp, None) - - def test_empty_file(self): - open(self.tempfile, 'wt', encoding='utf8').close() - venvscache = cache.VEnvsCache(self.tempfile) - with patch.object(venvscache, '_select') as mock: - mock.return_value = None - resp = venvscache.get_venv('requirements', 'interpreter') - mock.assert_called_with([], 'requirements', 'interpreter', uuid='', options=None) - self.assertEqual(resp, None) - - def test_some_file_content(self): - with open(self.tempfile, 'wt', encoding='utf8') as fh: - fh.write('foo\nbar\n') - venvscache = cache.VEnvsCache(self.tempfile) - with patch.object(venvscache, '_select') as mock: - mock.return_value = 'resp' - resp = venvscache.get_venv('requirements', 'interpreter', uuid='', options='options') - mock.assert_called_with(['foo', 'bar'], 'requirements', 'interpreter', uuid='', - options='options') - self.assertEqual(resp, 'resp') - - def test_get_by_uuid(self): - with open(self.tempfile, 'wt', encoding='utf8') as fh: - fh.write('foo\nbar\n') - venvscache = cache.VEnvsCache(self.tempfile) - with patch.object(venvscache, '_select') as mock: - mock.return_value = 'resp' - resp = venvscache.get_venv(uuid='uuid') - mock.assert_called_with(['foo', 'bar'], None, '', uuid='uuid', options=None) - self.assertEqual(resp, 'resp') - - -class StoreTestCase(unittest.TestCase): - """Store what received.""" - - def setUp(self): - super().setUp() - self.tempfile = get_tempfile(self) - - def test_missing_file(self): - venvscache = cache.VEnvsCache(self.tempfile) - venvscache.store('installed', 'metadata', 'interpreter', 'options') - - with open(self.tempfile, 'rt', encoding='utf8') as fh: - data = json.loads(fh.readline()) - self.assertTrue('timestamp' in data) - self.assertEqual(data['installed'], 'installed') - self.assertEqual(data['metadata'], 'metadata') - self.assertEqual(data['interpreter'], 'interpreter') - self.assertEqual(data['options'], 'options') - - def test_with_previous_content(self): - with open(self.tempfile, 'wt', encoding='utf8') as fh: - fh.write(json.dumps({'foo': 'bar'}) + '\n') - - venvscache = cache.VEnvsCache(self.tempfile) - venvscache.store('installed', 'metadata', 'interpreter', 'options') - - with open(self.tempfile, 'rt', encoding='utf8') as fh: - data = json.loads(fh.readline()) - self.assertEqual(data, {'foo': 'bar'}) - - data = json.loads(fh.readline()) - self.assertTrue('timestamp' in data) - self.assertEqual(data['installed'], 'installed') - self.assertEqual(data['metadata'], 'metadata') - self.assertEqual(data['interpreter'], 'interpreter') - self.assertEqual(data['options'], 'options') - - -class RemoveTestCase(unittest.TestCase): - """Remove virtualenv from cache.""" - - def setUp(self): - super().setUp() - self.tempfile = get_tempfile(self) - - def test_missing_file(self): - venvscache = cache.VEnvsCache(self.tempfile) - venvscache.remove('missing/path') - - lines = venvscache._read_cache() - self.assertEqual(lines, []) - - def test_missing_env_in_cache(self): - venvscache = cache.VEnvsCache(self.tempfile) - options = {'foo': 'bar'} - venvscache.store('installed', {'env_path': 'some/path'}, 'interpreter', options=options) - lines = venvscache._read_cache() - assert len(lines) == 1 - - venvscache.remove('some/path') - - lines = venvscache._read_cache() - self.assertEqual(lines, []) - - def test_preserve_cache_data_ordering(self): - venvscache = cache.VEnvsCache(self.tempfile) - # store 3 venvs - options = {'foo': 'bar'} - venvscache.store('installed1', {'env_path': 'path/env1'}, 'interpreter', options=options) - venvscache.store('installed2', {'env_path': 'path/env2'}, 'interpreter', options=options) - venvscache.store('installed3', {'env_path': 'path/env3'}, 'interpreter', options=options) - - venvscache.remove('path/env2') - - lines = venvscache._read_cache() - self.assertEqual(len(lines), 2) - self.assertEqual( - json.loads(lines[0]).get('metadata').get('env_path'), 'path/env1') - self.assertEqual( - json.loads(lines[1]).get('metadata').get('env_path'), 'path/env3') - - def test_lock_cache_for_remove(self): - venvscache = cache.VEnvsCache(self.tempfile) - # store 3 venvs - options = {'foo': 'bar'} - venvscache.store('installed1', {'env_path': 'path/env1'}, 'interpreter', options=options) - venvscache.store('installed2', {'env_path': 'path/env2'}, 'interpreter', options=options) - venvscache.store('installed3', {'env_path': 'path/env3'}, 'interpreter', options=options) - - # patch _write_cache so it emulates a slow write during which - # another process managed to modify the cache file before the - # first process finished writing the modified cache data - original_write_cache = venvscache._write_cache - p = patch('fades.cache.VEnvsCache._write_cache') - mock_write_cache = p.start() - - t1 = Thread(target=venvscache.remove, args=('path/env1',)) - - def slow_write_cache(*args, **kwargs): - p.stop() - t1.start() - # wait to ensure t1 thread must wait for lock to be released - time.sleep(0.01) - original_write_cache(*args, **kwargs) - - mock_write_cache.side_effect = slow_write_cache - - # just a sanity check - assert not os.path.exists(venvscache.filepath + '.lock') - # remove a virtualenv from the cache - venvscache.remove('path/env2') - t1.join() - - # when cache file is properly locked both virtualenvs - # will have been removed from the cache - lines = venvscache._read_cache() - self.assertEqual(len(lines), 1) - self.assertEqual( - json.loads(lines[0]).get('metadata').get('env_path'), 'path/env3') - self.assertFalse(os.path.exists(venvscache.filepath + '.lock')) - - -class SelectionTestCase(unittest.TestCase): - """The venv selection.""" - - def setUp(self): - super().setUp() - tempfile = get_tempfile(self) - self.venvscache = cache.VEnvsCache(tempfile) - - def test_empty(self): - resp = self.venvscache._select([], {}, 'pythonX.Y', 'options') - self.assertEqual(resp, None) - - def test_nomatch_repo(self): - reqs = {'repoloco': get_req('dep == 5')} - interpreter = 'pythonX.Y' - options = {'foo': 'bar'} - venv = json.dumps({ - 'metadata': 'foobar', - 'installed': {'pypi': {'dep': '5'}}, - 'interpreter': 'pythonX.Y', - 'options': {'foo': 'bar'} - }) - resp = self.venvscache._select([venv], reqs, interpreter, uuid='', options=options) - self.assertEqual(resp, None) - - def test_nomatch_pypi_dependency(self): - reqs = {'pypi': get_req('dep1 == 5')} - interpreter = 'pythonX.Y' - options = {'foo': 'bar'} - venv = json.dumps({ - 'metadata': 'foobar', - 'installed': {'pypi': {'dep2': '5'}}, - 'interpreter': 'pythonX.Y', - 'options': {'foo': 'bar'} - }) - resp = self.venvscache._select([venv], reqs, interpreter, uuid='', options=options) - self.assertEqual(resp, None) - - def test_nomatch_vcs_dependency(self): - reqs = {'vcs': [parsing.VCSDependency('someurl')]} - interpreter = 'pythonX.Y' - options = {'foo': 'bar'} - venv = json.dumps({ - 'metadata': 'foobar', - 'installed': {'vcs': {'otherurl': None}}, - 'interpreter': 'pythonX.Y', - 'options': {'foo': 'bar'} - }) - resp = self.venvscache._select([venv], reqs, interpreter, uuid='', options=options) - self.assertEqual(resp, None) - - def test_nomatch_version(self): - reqs = {'pypi': get_req('dep == 5')} - interpreter = 'pythonX.Y' - options = {'foo': 'bar'} - venv = json.dumps({ - 'metadata': 'foobar', - 'installed': {'pypi': {'dep': '7'}}, - 'interpreter': 'pythonX.Y', - 'options': {'foo': 'bar'} - }) - resp = self.venvscache._select([venv], reqs, interpreter, uuid='', options=options) - self.assertEqual(resp, None) - - def test_simple_pypi_match(self): - reqs = {'pypi': get_req('dep == 5')} - interpreter = 'pythonX.Y' - options = {'foo': 'bar'} - venv = json.dumps({ - 'metadata': 'foobar', - 'installed': {'pypi': {'dep': '5'}}, - 'interpreter': 'pythonX.Y', - 'options': {'foo': 'bar'} - }) - resp = self.venvscache._select([venv], reqs, interpreter, uuid='', options=options) - self.assertEqual(resp, 'foobar') - - def test_simple_vcs_match(self): - reqs = {'vcs': [parsing.VCSDependency('someurl')]} - interpreter = 'pythonX.Y' - options = {'foo': 'bar'} - venv = json.dumps({ - 'metadata': 'foobar', - 'installed': {'vcs': {'someurl': None}}, - 'interpreter': 'pythonX.Y', - 'options': {'foo': 'bar'} - }) - resp = self.venvscache._select([venv], reqs, interpreter, uuid='', options=options) - self.assertEqual(resp, 'foobar') - - def test_match_mixed_single(self): - reqs = {'vcs': [parsing.VCSDependency('someurl')], 'pypi': get_req('dep == 5')} - interpreter = 'pythonX.Y' - options = {'foo': 'bar'} - venv1 = json.dumps({ - 'metadata': 'foobar1', - 'installed': {'vcs': {'someurl': None}, 'pypi': {'dep': '5'}}, - 'interpreter': 'pythonX.Y', - 'options': {'foo': 'bar'} - }) - venv2 = json.dumps({ - 'metadata': 'foobar2', - 'installed': {'pypi': {'dep': '5'}}, - 'interpreter': 'pythonX.Y', - 'options': {'foo': 'bar'} - }) - venv3 = json.dumps({ - 'metadata': 'foobar3', - 'installed': {'vcs': {'someurl': None}}, - 'interpreter': 'pythonX.Y', - 'options': {'foo': 'bar'} - }) - resp = self.venvscache._select( - [venv1, venv2, venv3], reqs, interpreter, uuid='', options=options) - self.assertEqual(resp, 'foobar1') - - def test_match_mixed_multiple(self): - reqs = {'vcs': [parsing.VCSDependency('url1'), parsing.VCSDependency('url2')], - 'pypi': get_req(['dep1 == 5', 'dep2'])} - interpreter = 'pythonX.Y' - options = {'foo': 'bar'} - venv = json.dumps({ - 'metadata': 'foobar', - 'installed': {'vcs': {'url1': None, 'url2': None}, - 'pypi': {'dep1': '5', 'dep2': '7'}}, - 'interpreter': 'pythonX.Y', - 'options': {'foo': 'bar'} - }) - resp = self.venvscache._select([venv], reqs, interpreter, uuid='', options=options) - self.assertEqual(resp, 'foobar') - - def test_match_noversion(self): - reqs = {'pypi': get_req('dep')} - interpreter = 'pythonX.Y' - options = {'foo': 'bar'} - venv = json.dumps({ - 'metadata': 'foobar', - 'installed': {'pypi': {'dep': '5'}}, - 'interpreter': 'pythonX.Y', - 'options': {'foo': 'bar'} - }) - resp = self.venvscache._select([venv], reqs, interpreter, uuid='', options=options) - self.assertEqual(resp, 'foobar') - - def test_middle_match(self): - reqs = {'pypi': get_req('dep == 5')} - interpreter = 'pythonX.Y' - options = {'foo': 'bar'} - venv1 = json.dumps({ - 'metadata': 'venv1', - 'installed': {'pypi': {'dep': '3'}}, - 'interpreter': 'pythonX.Y', - 'options': {'foo': 'bar'} - }) - venv2 = json.dumps({ - 'metadata': 'venv2', - 'installed': {'pypi': {'dep': '5'}}, - 'interpreter': 'pythonX.Y', - 'options': {'foo': 'bar'} - }) - venv3 = json.dumps({ - 'metadata': 'venv3', - 'installed': {'pypi': {'dep': '7'}}, - 'interpreter': 'pythonX.Y', - 'options': {'foo': 'bar'} - }) - resp = self.venvscache._select([venv1, venv2, venv3], reqs, interpreter, uuid='', - options=options) - self.assertEqual(resp, 'venv2') - - def test_multiple_match_bigger_version(self): - reqs = {'pypi': get_req('dep')} - interpreter = 'pythonX.Y' - options = {'foo': 'bar'} - venv1 = json.dumps({ - 'metadata': 'venv1', - 'installed': {'pypi': {'dep': '3'}}, - 'interpreter': 'pythonX.Y', - 'options': {'foo': 'bar'} - }) - venv2 = json.dumps({ - 'metadata': 'venv2', - 'installed': {'pypi': {'dep': '7'}}, - 'interpreter': 'pythonX.Y', - 'options': {'foo': 'bar'} - }) - venv3 = json.dumps({ - 'metadata': 'venv3', - 'installed': {'pypi': {'dep': '5'}}, - 'interpreter': 'pythonX.Y', - 'options': {'foo': 'bar'} - }) - resp = self.venvscache._select([venv1, venv2, venv3], reqs, interpreter, uuid='', - options=options) - # matches venv2 because it has the bigger version for 'dep' (even if it's not the - # latest virtualenv created) - self.assertEqual(resp, 'venv2') - - def test_multiple_deps_ok(self): - reqs = {'pypi': get_req(['dep1 == 5', 'dep2 == 7'])} - interpreter = 'pythonX.Y' - options = {'foo': 'bar'} - venv = json.dumps({ - 'metadata': 'foobar', - 'installed': {'pypi': {'dep1': '5', 'dep2': '7'}}, - 'interpreter': 'pythonX.Y', - 'options': {'foo': 'bar'} - }) - resp = self.venvscache._select([venv], reqs, interpreter, uuid='', options=options) - self.assertEqual(resp, 'foobar') - - def test_multiple_deps_just_one(self): - reqs = {'pypi': get_req(['dep1 == 5', 'dep2 == 7'])} - interpreter = 'pythonX.Y' - options = {'foo': 'bar'} - venv = json.dumps({ - 'metadata': 'foobar', - 'installed': {'pypi': {'dep1': '5', 'dep2': '2'}}, - 'interpreter': 'pythonX.Y', - 'options': {'foo': 'bar'} - }) - resp = self.venvscache._select([venv], reqs, interpreter, uuid='', options=options) - self.assertEqual(resp, None) - - def test_not_too_crowded(self): - reqs = {'pypi': get_req(['dep1'])} - interpreter = 'pythonX.Y' - options = {'foo': 'bar'} - venv = json.dumps({ - 'metadata': 'foobar', - 'installed': {'pypi': {'dep1': '5', 'dep2': '2'}}, - 'interpreter': 'pythonX.Y', - 'options': {'foo': 'bar'} - }) - resp = self.venvscache._select([venv], reqs, interpreter, uuid='', options=options) - self.assertEqual(resp, None) - - def test_same_quantity_different_deps(self): - reqs = {'pypi': get_req(['dep1', 'dep2'])} - interpreter = 'pythonX.Y' - options = {'foo': 'bar'} - venv = json.dumps({ - 'metadata': 'foobar', - 'installed': {'pypi': {'dep1': '5', 'dep3': '2'}}, - 'interpreter': 'pythonX.Y', - 'options': {'foo': 'bar'} - }) - resp = self.venvscache._select([venv], reqs, interpreter, uuid='', options=options) - self.assertEqual(resp, None) - - def test_no_requirements_some_installed(self): - reqs = {} - interpreter = 'pythonX.Y' - options = {'foo': 'bar'} - venv = json.dumps({ - 'metadata': 'foobar', - 'installed': {'pypi': {'dep1': '5', 'dep3': '2'}}, - 'interpreter': 'pythonX.Y', - 'options': {'foo': 'bar'} - }) - resp = self.venvscache._select([venv], reqs, interpreter, uuid='', options=options) - self.assertEqual(resp, None) - - def test_no_requirements_empty_venv(self): - reqs = {} - interpreter = 'pythonX.Y' - options = {'foo': 'bar'} - venv = json.dumps({ - 'metadata': 'foobar', - 'installed': {}, - 'interpreter': 'pythonX.Y', - 'options': {'foo': 'bar'} - }) - resp = self.venvscache._select([venv], reqs, interpreter, uuid='', options=options) - self.assertEqual(resp, 'foobar') - - def test_simple_match_empty_options(self): - reqs = {'pypi': get_req('dep == 5')} - interpreter = 'pythonX.Y' - options = {} - venv = json.dumps({ - 'metadata': 'foobar', - 'installed': {'pypi': {'dep': '5'}}, - 'interpreter': 'pythonX.Y', - 'options': {} - }) - resp = self.venvscache._select([venv], reqs, interpreter, uuid='', options=options) - self.assertEqual(resp, 'foobar') - - def test_no_match_due_to_options(self): - reqs = {'pypi': get_req('dep == 5')} - interpreter = 'pythonX.Y' - options = {'foo': 'bar'} - venv = json.dumps({ - 'metadata': 'foobar', - 'installed': {'pypi': {'dep': '5'}}, - 'interpreter': 'pythonX.Y', - 'options': {} - }) - resp = self.venvscache._select([venv], reqs, interpreter, uuid='', options=options) - self.assertEqual(resp, None) - - def test_match_due_to_options(self): - reqs = {'pypi': get_req('dep == 5')} - interpreter = 'pythonX.Y' - options = {'foo': 'bar'} - venv1 = json.dumps({ - 'metadata': 'venv1', - 'installed': {'pypi': {'dep': '5'}}, - 'interpreter': 'pythonX.Y', - 'options': {} - }) - venv2 = json.dumps({ - 'metadata': 'venv2', - 'installed': {'pypi': {'dep': '5'}}, - 'interpreter': 'pythonX.Y', - 'options': {'foo': 'bar'} - }) - resp = self.venvscache._select([venv1, venv2], reqs, interpreter, uuid='', options=options) - self.assertEqual(resp, 'venv2') - - def test_no_deps_but_options(self): - reqs = {} - interpreter = 'pythonX.Y' - options = {'foo': 'bar'} - venv1 = json.dumps({ - 'metadata': 'venv1', - 'installed': {}, - 'interpreter': 'pythonX.Y', - 'options': {} - }) - venv2 = json.dumps({ - 'metadata': 'venv2', - 'installed': {}, - 'interpreter': 'pythonX.Y', - 'options': {'foo': 'bar'} - }) - resp = self.venvscache._select([venv1, venv2], reqs, interpreter, uuid='', options=options) - self.assertEqual(resp, 'venv2') - - def test_match_uuid(self): - venv_uuid = str(uuid.uuid4()) - metadata = { - 'env_path': os.path.join(helpers.get_basedir(), venv_uuid), - } - venv = json.dumps({ - 'metadata': metadata, - 'installed': {}, - 'interpreter': 'pythonX.Y', - 'options': {'foo': 'bar'} - }) - resp = self.venvscache._select([venv], uuid=venv_uuid) - self.assertEqual(resp, metadata) - - -class ComparisonsTestCase(unittest.TestCase): - """The comparison in the selection.""" - - def setUp(self): - super().setUp() - tempfile = get_tempfile(self) - self.venvscache = cache.VEnvsCache(tempfile) - - def check(self, req, installed): - """Check if the requirement is satisfied with what is installed.""" - reqs = {'pypi': get_req('dep' + req)} - interpreter = 'pythonX.Y' - options = {'foo': 'bar'} - venv = json.dumps({ - 'metadata': 'ok', - 'installed': {'pypi': {'dep': installed}}, - 'interpreter': 'pythonX.Y', - 'options': {'foo': 'bar'} - }) - resp = self.venvscache._select([venv], reqs, interpreter, uuid='', options=options) - return resp - - def test_comp_eq(self): - self.assertEqual(self.check('==5', '5'), 'ok') - self.assertEqual(self.check('==5', '2'), None) - - def test_comp_gt(self): - self.assertEqual(self.check('>5', '4'), None) - self.assertEqual(self.check('>5', '5'), None) - self.assertEqual(self.check('>5', '6'), 'ok') - - def test_comp_ge(self): - self.assertEqual(self.check('>=5', '4'), None) - self.assertEqual(self.check('>=5', '5'), 'ok') - self.assertEqual(self.check('>=5', '6'), 'ok') - - def test_comp_lt(self): - self.assertEqual(self.check('<5', '4'), 'ok') - self.assertEqual(self.check('<5', '5'), None) - self.assertEqual(self.check('<5', '6'), None) - - def test_comp_le(self): - self.assertEqual(self.check('<=5', '4'), 'ok') - self.assertEqual(self.check('<=5', '5'), 'ok') - self.assertEqual(self.check('<=5', '6'), None) - - def test_complex_cases(self): - self.assertEqual(self.check('== 2.5', '2.5.0'), 'ok') - self.assertEqual(self.check('> 2.7', '2.12'), 'ok') - self.assertEqual(self.check('> 2.7a0', '2.7'), 'ok') - self.assertEqual(self.check('> 2.7', '2.7a0'), None) - - def test_crazy_picky(self): - self.assertEqual(self.check('>1.6,<1.9,!=1.9.6', '1.5.0'), None) - self.assertEqual(self.check('>1.6,<1.9,!=1.9.6', '1.6.7'), 'ok') - self.assertEqual(self.check('>1.6,<1.9,!=1.8.6', '1.8.7'), 'ok') - self.assertEqual(self.check('>1.6,<1.9,!=1.9.6', '1.9.6'), None) - - -class BestFitTestCase(unittest.TestCase): - """Check the venv best fitting decissor.""" - - def setUp(self): - super().setUp() - tempfile = get_tempfile(self) - self.venvscache = cache.VEnvsCache(tempfile) - - def check(self, possible_venvs): - """Assert that the selected venv is the best fit one.""" - self.assertEqual(self.venvscache._select_better_fit(possible_venvs), 'venv_best_fit') - - def test_one_simple(self): - self.check([ - (get_distrib(('dep', '3')), 'venv_best_fit'), - ]) - - def test_one_double(self): - self.check([ - (get_distrib(('dep1', '3'), ('dep2', '3')), 'venv_best_fit'), - ]) - - def test_two_simple(self): - self.check([ - (get_distrib(('dep', '5')), 'venv_best_fit'), - (get_distrib(('dep', '3')), 'venv_1'), - ]) - - def test_two_double_evident(self): - self.check([ - (get_distrib(('dep1', '5'), ('dep2', '7')), 'venv_best_fit'), - (get_distrib(('dep1', '3'), ('dep2', '6')), 'venv_1'), - ]) - - def test_two_double_mixed_1(self): - # tie! the one chosen is the last one - self.check([ - (get_distrib(('dep1', '3'), ('dep2', '9')), 'venv_1'), - (get_distrib(('dep1', '5'), ('dep2', '7')), 'venv_best_fit'), - ]) - - def test_two_double_mixed_2(self): - # tie! the one chosen is the last one - self.check([ - (get_distrib(('dep1', '5'), ('dep2', '7')), 'venv_1'), - (get_distrib(('dep1', '3'), ('dep2', '9')), 'venv_best_fit'), - ]) - - def test_two_triple(self): - self.check([ - (get_distrib(('dep1', '3'), ('dep2', '9'), ('dep3', '4')), 'venv_best_fit'), - (get_distrib(('dep1', '5'), ('dep2', '7'), ('dep3', '2')), 'venv_1'), - ]) - - def test_unordered(self): - # assert it compares each dependency with its equivalent - self.check([ - (get_distrib(('dep2', '3'), ('dep1', '2'), ('dep3', '8')), 'venv_best_fit'), - (get_distrib(('dep1', '7'), ('dep3', '5'), ('dep2', '2')), 'venv_1'), - ]) - - def test_big(self): - self.check([ - (get_distrib(('dep1', '3'), ('dep2', '2')), 'venv_1'), - (get_distrib(('dep1', '4'), ('dep2', '2')), 'venv_2'), - (get_distrib(('dep1', '5'), ('dep2', '7')), 'venv_best_fit'), - (get_distrib(('dep1', '5'), ('dep2', '6')), 'venv_3'), - ]) - - def test_vcs_alone(self): - self.check([ - ([parsing.VCSDependency('someurl')], 'venv_best_fit'), - ]) - - def test_vcs_mixed_simple(self): - self.check([ - ([parsing.VCSDependency('someurl')] + get_distrib(('dep', '3')), 'venv_best_fit'), - ]) - - def test_vcs_mixed_multiple(self): - self.check([ - ([parsing.VCSDependency('someurl')] + get_distrib(('dep', '3')), 'venv_best_fit'), - ([parsing.VCSDependency('someurl')] + get_distrib(('dep', '1')), 'venv_1'), - ]) diff --git a/tests/test_logger.py b/tests/test_logger.py index e00114f..60ed33d 100644 --- a/tests/test_logger.py +++ b/tests/test_logger.py @@ -15,34 +15,35 @@ # For further info, check https://github.com/PyAr/fades """Tests for logger related code.""" +import logging +from fades.logger import set_up as log_set_up -import unittest -import logassert +def test_salutes_info(caplog): + """Check saluting handler.""" + logger = log_set_up(verbose=False, quiet=True) + logger.warning("test foobar") -from fades.logger import set_up as log_set_up + assert "INFO" == caplog.records[0].levelname + assert "Hi! This is fades" in caplog.records[0].msg + assert "WARNING" == caplog.records[1].levelname + assert 'test foobar' in caplog.records[1].msg -class SalutingHandlerTestCase(unittest.TestCase): - """Check saluting handler.""" - def setUp(self): - logassert.setup(self, 'fades') - - def test_salutes_info(self): - logger = log_set_up(verbose=False, quiet=True) - logger.warning("test foobar") - self.assertLoggedInfo("Hi! This is fades") - self.assertLoggedWarning("test foobar") - - def test_salutes_once(self): - logger = log_set_up(verbose=False, quiet=False) - logger.info("test foobar") - self.assertLoggedInfo("Hi! This is fades") - self.assertLoggedInfo("test foobar") - - # again, check this time it didn't salute, but original log message is ok - logassert.setup(self, 'fades') - logger.info("test barbarroja") - self.assertNotLoggedInfo("Hi! This is fades") - self.assertLoggedInfo("test barbarroja") +def test_salutes_once(caplog): + logger = log_set_up(verbose=False, quiet=False) + logger.info("test foobar") + + assert "INFO" == caplog.records[0].levelname + assert "Hi! This is fades" in caplog.records[0].msg + assert "INFO" == caplog.records[1].levelname + assert 'test foobar' in caplog.records[1].msg + + # again, check this time it didn't salute, but original log message is ok + caplog.set_level(logging.INFO, logger='fades') + logger.info("test barbarroja") + + assert "Hi! This is fades" not in caplog.records[2].msg + assert "test barbarroja" in caplog.records[2].msg + assert "INFO" == caplog.records[2].levelname From c7c805fbc76f6f913df3755be29a9037dc490699 Mon Sep 17 00:00:00 2001 From: Eduardo Enriquez Date: Sat, 11 May 2019 14:23:20 +0200 Subject: [PATCH 2/4] Removed travis support to py3.3 and 3.4 --- .travis.yml | 4 +--- requirements.txt | 3 ++- setup.py | 2 +- tests/conftest.py | 5 +++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index aabe23d..09887be 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,6 @@ language: python python: - - "3.3" - - "3.4" - "3.5" - "3.6" - "3.7-dev" @@ -10,7 +8,7 @@ install: - "pip install -r requirements.txt" - if [[ $TRAVIS_PYTHON_VERSION == '3.6' ]]; then pip install python-coveralls ; fi script: - - "pytest -n 4 --cov=fades" + - "pytest --cov=fades" after_script: - "flake8 fades --max-line-length=99 --select=E,W,F,C,N" diff --git a/requirements.txt b/requirements.txt index c1779f8..4b67756 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,8 +10,9 @@ pyxdg==0.25 rst2html5==1.9.3 setuptools>=5.5 wheel==0.26.0 +coverage==4.0.3 pytest==4.4.2 pytest-xdist==1.28.0 pytest-sugar==0.9.2 -pytest-cov==2.7.1 +pytest-cov==2.5.1 pytest-mock==1.10.4 diff --git a/setup.py b/setup.py index 1c8cdd5..3e5a430 100755 --- a/setup.py +++ b/setup.py @@ -121,7 +121,7 @@ def finalize_options(self): 'install': CustomInstall, }, install_requires=['setuptools'], - tests_require=['logassert', 'pyxdg', 'pyuca', 'nose', 'flake8', + tests_require=['logassert', 'pyxdg', 'pyuca', 'pytest', 'flake8', 'pep257', 'rst2html5'], # what unittests require python_requires='>=3.3', # Minimum Python version supported. extras_require={ diff --git a/tests/conftest.py b/tests/conftest.py index 495f7c4..837de2a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -8,8 +8,9 @@ @pytest.fixture(scope="function") def tmp_file(tmpdir_factory): - path = tmpdir_factory.mktemp("test").join("foo.bar") - yield tmpdir_factory.mktemp("test").join("foo.bar") + # Converted to str to support python <3.6 versions + path = str(tmpdir_factory.mktemp("test").join("foo.bar")) + yield path if os.path.isfile(path): os.remove(path) From cc3d84ab92b5bc38f02f09fbc8860143fc651874 Mon Sep 17 00:00:00 2001 From: Eduardo Enriquez Date: Sun, 12 May 2019 00:19:06 +0200 Subject: [PATCH 3/4] Migrated to pytest test_infra test_pipmanager --- test | 2 +- testdev | 2 +- tests/conftest.py | 20 ++- tests/test_infra.py | 68 ++++------ tests/test_pipmanager.py | 272 +++++++++++++++++++-------------------- 5 files changed, 179 insertions(+), 185 deletions(-) diff --git a/test b/test index 433668f..f552640 100755 --- a/test +++ b/test @@ -12,7 +12,7 @@ fi FADES='./bin/fades -r requirements.txt' -$FADES -x pytest -n 4 --cov=fades +$FADES -x pytest --cov=fades # check if we are using exit() in the code. if grep -r -n ' exit(' --include="*.py" .; then echo 'Please use sys.exit() instead of exit(). https://github.com/PyAr/fades/issues/280'; fi diff --git a/testdev b/testdev index 26f360b..0168951 100755 --- a/testdev +++ b/testdev @@ -10,4 +10,4 @@ else TARGET_TESTS="fades tests" fi -./bin/fades -r requirements.txt -x pytest -v -s $TARGET_TESTS +./bin/fades -r requirements.txt -x pytest diff --git a/tests/conftest.py b/tests/conftest.py index 837de2a..9188178 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,5 @@ import os +import shutil import pytest from pkg_resources import parse_requirements, Distribution @@ -8,11 +9,9 @@ @pytest.fixture(scope="function") def tmp_file(tmpdir_factory): - # Converted to str to support python <3.6 versions - path = str(tmpdir_factory.mktemp("test").join("foo.bar")) - yield path - if os.path.isfile(path): - os.remove(path) + dir_path = tmpdir_factory.mktemp("test") + yield str(dir_path.join("foo.bar")) # Converted to str to support python <3.6 versions + shutil.rmtree(str(dir_path)) @pytest.fixture(scope="function") @@ -31,3 +30,14 @@ def get_req(text): def get_distrib(*dep_ver_pairs): """Build some Distributions with indicated info.""" return [Distribution(project_name=dep, version=ver) for dep, ver in dep_ver_pairs] + + +def _get_python_filepaths(roots): + """Helper to retrieve paths of Python files.""" + python_paths = [] + for root in roots: + for dirpath, dirnames, filenames in os.walk(root): + for filename in filenames: + if filename.endswith(".py"): + python_paths.append(os.path.join(dirpath, filename)) + return python_paths diff --git a/tests/test_infra.py b/tests/test_infra.py index d1bfa20..713d2c7 100644 --- a/tests/test_infra.py +++ b/tests/test_infra.py @@ -18,11 +18,6 @@ import io import logging -import os -import unittest - -from unittest.mock import patch - import docutils.core import pep257 import rst2html5_ @@ -30,6 +25,8 @@ from flake8.api.legacy import get_style_guide from pyuca import Collator +from tests.conftest import _get_python_filepaths + FLAKE8_ROOTS = ['fades', 'tests'] FLAKE8_OPTIONS = ['--max-line-length=99', '--select=E,W,F,C,N'] PEP257_ROOTS = ['fades'] @@ -40,44 +37,35 @@ logging.getLogger(logger_name).setLevel(logging.CRITICAL) -class InfrastructureTestCase(unittest.TestCase): +def test_flake8_pytest(mocker): + python_filepaths = _get_python_filepaths(FLAKE8_ROOTS) + style_guide = get_style_guide(paths=FLAKE8_OPTIONS) + fake_stdout = io.StringIO() + mocker.patch('sys.stdout', fake_stdout) + report = style_guide.check_files(python_filepaths) + assert report.total_errors == 0, "There are issues!\n" + fake_stdout.getvalue() + - def _get_python_filepaths(self, roots): - """Helper to retrieve paths of Python files.""" - python_paths = [] - for root in roots: - for dirpath, dirnames, filenames in os.walk(root): - for filename in filenames: - if filename.endswith(".py"): - python_paths.append(os.path.join(dirpath, filename)) - return python_paths +def test_pep257_pytest(): + python_filepaths = _get_python_filepaths(PEP257_ROOTS) + result = list(pep257.check(python_filepaths)) + assert len(result) == 0, "There are issues!\n" + '\n'.join(map(str, result)) - def test_flake8(self): - python_filepaths = self._get_python_filepaths(FLAKE8_ROOTS) - style_guide = get_style_guide(paths=FLAKE8_OPTIONS) - fake_stdout = io.StringIO() - with patch('sys.stdout', fake_stdout): - report = style_guide.check_files(python_filepaths) - self.assertEqual(report.total_errors, 0, "There are issues!\n" + fake_stdout.getvalue()) - def test_pep257(self): - python_filepaths = self._get_python_filepaths(PEP257_ROOTS) - result = list(pep257.check(python_filepaths)) - self.assertEqual(len(result), 0, "There are issues!\n" + '\n'.join(map(str, result))) +def test_readme_sanity(mocker): + fake_stdout = io.StringIO() # just to ignore the output + fake_stderr = io.StringIO() # will have content if there are problems + with open('README.rst', 'rt', encoding='utf8') as fh: + mocker.patch('sys.stdout', fake_stdout) + mocker.patch('sys.stderr', fake_stderr) + docutils.core.publish_file(source=fh, writer=rst2html5_.HTML5Writer()) - def test_readme_sanity(self): - fake_stdout = io.StringIO() # just to ignore the output - fake_stderr = io.StringIO() # will have content if there are problems - with open('README.rst', 'rt', encoding='utf8') as fh: - with patch('sys.stdout', fake_stdout): - with patch('sys.stderr', fake_stderr): - docutils.core.publish_file(source=fh, writer=rst2html5_.HTML5Writer()) + errors = fake_stderr.getvalue() + assert not bool(errors), "There are issues!\n" + errors - errors = fake_stderr.getvalue() - self.assertFalse(bool(errors), "There are issues!\n" + errors) - def test_authors_ordering(self): - with open('AUTHORS', 'rt', encoding='utf8') as fh: - authors = fh.readlines() - ordered_authors = sorted(authors, key=Collator().sort_key) - self.assertEqual(authors, ordered_authors) +def test_authors_ordering(): + with open('AUTHORS', 'rt', encoding='utf8') as fh: + authors = fh.readlines() + ordered_authors = sorted(authors, key=Collator().sort_key) + assert authors == ordered_authors diff --git a/tests/test_pipmanager.py b/tests/test_pipmanager.py index 98479fc..84475d7 100644 --- a/tests/test_pipmanager.py +++ b/tests/test_pipmanager.py @@ -18,149 +18,145 @@ import os import io -import unittest - -from unittest.mock import patch - -import logassert +import pytest from fades.pipmanager import PipManager from fades import pipmanager from fades import helpers -from tests import get_tempfile BIN_PATH = "somepath" -class PipManagerTestCase(unittest.TestCase): - """Check parsing for `pip show`.""" - - def setUp(self): - logassert.setup(self, 'fades.pipmanager') - - def test_get_parsing_ok(self): - mocked_stdout = ['Name: foo', - 'Version: 2.0.0', - 'Location: ~/.local/share/fades/86cc492/lib/python3.4/site-packages', - 'Requires: '] - mgr = PipManager(BIN_PATH, pip_installed=True) - with patch.object(helpers, 'logged_exec') as mock: - mock.return_value = mocked_stdout - version = mgr.get_version('foo') - self.assertEqual(version, '2.0.0') - - def test_get_parsing_error(self): - mocked_stdout = ['Name: foo', - 'Release: 2.0.0', - 'Location: ~/.local/share/fades/86cc492/lib/python3.4/site-packages', - 'Requires: '] - mgr = PipManager(BIN_PATH, pip_installed=True) - with patch.object(helpers, 'logged_exec') as mock: - version = mgr.get_version('foo') - mock.return_value = mocked_stdout - self.assertEqual(version, '') - self.assertLoggedError('Fades is having problems getting the installed version. ' - 'Run with -v or check the logs for details') - - def test_real_case_levenshtein(self): - mocked_stdout = [ - 'Metadata-Version: 1.1', - 'Name: python-Levenshtein', - 'Version: 0.12.0', - 'License: GPL', - ] - mgr = PipManager(BIN_PATH, pip_installed=True) - with patch.object(helpers, 'logged_exec') as mock: - mock.return_value = mocked_stdout - version = mgr.get_version('foo') - self.assertEqual(version, '0.12.0') - - def test_install(self): - mgr = PipManager(BIN_PATH, pip_installed=True) - pip_path = os.path.join(BIN_PATH, 'pip') - with patch.object(helpers, 'logged_exec') as mock: - mgr.install('foo') - mock.assert_called_with([pip_path, 'install', 'foo']) - - def test_install_multiword_dependency(self): - mgr = PipManager(BIN_PATH, pip_installed=True) - pip_path = os.path.join(BIN_PATH, 'pip') - with patch.object(helpers, 'logged_exec') as mock: - mgr.install('foo bar') - mock.assert_called_with([pip_path, 'install', 'foo', 'bar']) - - def test_install_with_options(self): - mgr = PipManager(BIN_PATH, pip_installed=True, options=['--bar baz']) - pip_path = os.path.join(BIN_PATH, 'pip') - with patch.object(helpers, 'logged_exec') as mock: - mgr.install('foo') - mock.assert_called_with([pip_path, 'install', 'foo', '--bar', 'baz']) - - def test_install_with_options_using_equal(self): - mgr = PipManager(BIN_PATH, pip_installed=True, options=['--bar=baz']) - pip_path = os.path.join(BIN_PATH, 'pip') - with patch.object(helpers, 'logged_exec') as mock: - mgr.install('foo') - mock.assert_called_with([pip_path, 'install', 'foo', '--bar=baz']) - - def test_install_raise_error(self): - mgr = PipManager(BIN_PATH, pip_installed=True) - with patch.object(helpers, 'logged_exec') as mock: - mock.side_effect = Exception("Kapow!") - with self.assertRaises(Exception): - mgr.install('foo') - self.assertLoggedError("Error installing foo: Kapow!") - - def test_install_without_pip(self): - mgr = PipManager(BIN_PATH, pip_installed=False) - pip_path = os.path.join(BIN_PATH, 'pip') - with patch.object(helpers, 'logged_exec') as mocked_exec: - with patch.object(mgr, '_brute_force_install_pip') as mocked_install_pip: - mgr.install('foo') - self.assertEqual(mocked_install_pip.call_count, 1) - mocked_exec.assert_called_with([pip_path, 'install', 'foo']) - - def test_brute_force_install_pip_installer_exists(self): - mgr = PipManager(BIN_PATH, pip_installed=False) - python_path = os.path.join(BIN_PATH, 'python') - with patch.object(helpers, 'logged_exec') as mocked_exec, \ - patch.object(mgr, '_download_pip_installer') as download_installer: - - # get the tempfile but leave it there to be found - mgr.pip_installer_fname = get_tempfile(self) - mgr._brute_force_install_pip() - - self.assertEqual(download_installer.call_count, 0) - mocked_exec.assert_called_with([python_path, mgr.pip_installer_fname, '-I']) - self.assertTrue(mgr.pip_installed) - - def test_brute_force_install_pip_no_installer(self): - mgr = PipManager(BIN_PATH, pip_installed=False) - python_path = os.path.join(BIN_PATH, 'python') - with patch.object(helpers, 'logged_exec') as mocked_exec, \ - patch.object(mgr, '_download_pip_installer') as download_installer: - - # get the tempfile and remove it so then it's not found - tempfile = get_tempfile(self) - os.remove(tempfile) - - mgr.pip_installer_fname = tempfile - mgr._brute_force_install_pip() - - download_installer.assert_called_once_with() - mocked_exec.assert_called_with([python_path, mgr.pip_installer_fname, '-I']) - self.assertTrue(mgr.pip_installed) - - def test_download_pip_installer(self): - mgr = PipManager(BIN_PATH, pip_installed=False) - - # get a tempfile and remove it, so later the installer is downloaded there - tempfile = get_tempfile(self) - os.remove(tempfile) - - mgr.pip_installer_fname = tempfile - with patch('fades.pipmanager.request.urlopen') as urlopen: - urlopen.return_value = io.BytesIO(b'hola') - mgr._download_pip_installer() - self.assertTrue(os.path.exists(mgr.pip_installer_fname)) - urlopen.assert_called_once_with(pipmanager.PIP_INSTALLER) +def test_get_parsing_ok_pytest(mocker): + mocked_stdout = [ + "Name: foo", + "Version: 2.0.0", + "Location: ~/.local/share/fades/86cc492/lib/python3.4/site-packages", + "Requires: ", + ] + mgr = PipManager(BIN_PATH, pip_installed=True) + mocker.patch.object(helpers, "logged_exec", return_value=mocked_stdout) + version = mgr.get_version("foo") + assert version, "2.0.0" + + +def test_get_parsing_error(mocker, caplog): + mocked_stdout = [ + "Name: foo", + "Release: 2.0.0", + "Location: ~/.local/share/fades/86cc492/lib/python3.4/site-packages", + "Requires: ", + ] + mgr = PipManager(BIN_PATH, pip_installed=True) + mocker.patch.object(helpers, "logged_exec", return_value=mocked_stdout) + version = mgr.get_version("foo") + assert version == "" + msg = ( + "Fades is having problems getting the installed version. " + "Run with -v or check the logs for details" + ) + assert msg in caplog.messages + + +def test_real_case_levenshtein(mocker): + mocked_stdout = [ + "Metadata-Version: 1.1", + "Name: python-Levenshtein", + "Version: 0.12.0", + "License: GPL", + ] + mgr = PipManager(BIN_PATH, pip_installed=True) + mocker.patch.object(helpers, "logged_exec", return_value=mocked_stdout) + version = mgr.get_version("foo") + assert version == "0.12.0" + + +def test_install(mocker): + mgr = PipManager(BIN_PATH, pip_installed=True) + pip_path = os.path.join(BIN_PATH, "pip") + mock = mocker.patch.object(helpers, "logged_exec") + mgr.install("foo") + mock.assert_called_with([pip_path, "install", "foo"]) + + +def test_install_multiword_dependency(mocker): + mgr = PipManager(BIN_PATH, pip_installed=True) + pip_path = os.path.join(BIN_PATH, "pip") + mock = mocker.patch.object(helpers, "logged_exec") + mgr.install("foo bar") + mock.assert_called_with([pip_path, "install", "foo", "bar"]) + + +def test_install_with_options(mocker): + mgr = PipManager(BIN_PATH, pip_installed=True, options=["--bar baz"]) + pip_path = os.path.join(BIN_PATH, "pip") + mock = mocker.patch.object(helpers, "logged_exec") + mgr.install("foo") + mock.assert_called_with([pip_path, "install", "foo", "--bar", "baz"]) + + +def test_install_with_options_using_equal(mocker): + mgr = PipManager(BIN_PATH, pip_installed=True, options=["--bar=baz"]) + pip_path = os.path.join(BIN_PATH, "pip") + mock = mocker.patch.object(helpers, "logged_exec") + mgr.install("foo") + mock.assert_called_with([pip_path, "install", "foo", "--bar=baz"]) + + +def test_install_raise_error(mocker, caplog): + mgr = PipManager(BIN_PATH, pip_installed=True) + mocker.patch.object(helpers, "logged_exec", side_effect=Exception("Kapow!")) + with pytest.raises(Exception): + mgr.install("foo") + + assert "Error installing foo: Kapow!" in caplog.messages + + +def test_install_without_pip(mocker): + mgr = PipManager(BIN_PATH, pip_installed=False) + pip_path = os.path.join(BIN_PATH, "pip") + mocked_exec = mocker.patch.object(helpers, "logged_exec") + mocked_install_pip = mocker.patch.object(mgr, "_brute_force_install_pip") + mgr.install("foo") + assert mocked_install_pip.call_count == 1 + mocked_exec.assert_called_with([pip_path, "install", "foo"]) + + +def test_brute_force_install_pip_installer_exists(mocker, tmp_file): + mgr = PipManager(BIN_PATH, pip_installed=False) + python_path = os.path.join(BIN_PATH, "python") + mocked_exec = mocker.patch.object(helpers, "logged_exec") + download_installer = mocker.patch.object(mgr, "_download_pip_installer") + + # get the tempfile but leave it there to be found + open(tmp_file, 'wt', encoding='utf8').close() + mgr.pip_installer_fname = tmp_file + mgr._brute_force_install_pip() + + assert not download_installer.called + mocked_exec.assert_called_with([python_path, mgr.pip_installer_fname, "-I"]) + assert mgr.pip_installed + + +def test_brute_force_install_pip_no_installer(mocker, tmp_file): + mgr = PipManager(BIN_PATH, pip_installed=False) + python_path = os.path.join(BIN_PATH, "python") + mocked_exec = mocker.patch.object(helpers, "logged_exec") + download_installer = mocker.patch.object(mgr, "_download_pip_installer") + + mgr.pip_installer_fname = tmp_file + mgr._brute_force_install_pip() + + download_installer.assert_called_once_with() + mocked_exec.assert_called_with([python_path, mgr.pip_installer_fname, "-I"]) + assert mgr.pip_installed + + +def test_download_pip_installer(mocker, tmp_file): + mgr = PipManager(BIN_PATH, pip_installed=False) + + mgr.pip_installer_fname = tmp_file + urlopen = mocker.patch("fades.pipmanager.request.urlopen", return_value=io.BytesIO(b"hola")) + mgr._download_pip_installer() + assert os.path.exists(mgr.pip_installer_fname) + urlopen.assert_called_once_with(pipmanager.PIP_INSTALLER) From a4c5577c0aa6be8f783ca386be37901e91b90ef7 Mon Sep 17 00:00:00 2001 From: Eduardo Enriquez Date: Sun, 12 May 2019 00:24:18 +0200 Subject: [PATCH 4/4] Updated AppVeyor --- .appveyor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 76a833f..4a48cff 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -25,10 +25,10 @@ install: # Check that we have the expected version and architecture for Python - "python --version" - - "python -c \"import struct; print(struct.calcsize('P') * 8)\"" + - "python -c \"import struct; print(struct.calcsize('P') * 8)\"" build: off test_script: # Run the project tests - - "%CMD_IN_ENV% python bin/fades -v -r requirements.txt -x nosetests -v -s tests" + - "%CMD_IN_ENV% python bin/fades -v -r requirements.txt -x pytest"