|
21 | 21 | from docker.errors import APIError as DockerAPIError
|
22 | 22 | from docker.errors import DockerException
|
23 | 23 | from mock import Mock, PropertyMock, mock_open, patch
|
| 24 | +from django_dynamic_fixture import get |
24 | 25 |
|
25 | 26 | from readthedocs.builds.constants import BUILD_STATE_CLONING
|
26 | 27 | from readthedocs.builds.models import Version
|
27 | 28 | from readthedocs.doc_builder.config import ConfigWrapper
|
28 | 29 | from readthedocs.doc_builder.environments import (
|
29 | 30 | BuildCommand, DockerBuildCommand, DockerBuildEnvironment, LocalBuildEnvironment)
|
30 | 31 | from readthedocs.doc_builder.exceptions import BuildEnvironmentError
|
31 |
| -from readthedocs.doc_builder.python_environments import Virtualenv |
| 32 | +from readthedocs.doc_builder.python_environments import Conda, Virtualenv |
32 | 33 | from readthedocs.projects.models import Project
|
33 | 34 | from readthedocs.rtd_tests.mocks.environment import EnvironmentMockGroup
|
| 35 | +from readthedocs.rtd_tests.mocks.paths import fake_paths_lookup |
34 | 36 | from readthedocs.rtd_tests.tests.test_config_wrapper import create_load
|
35 | 37 |
|
36 | 38 | DUMMY_BUILD_ID = 123
|
@@ -839,6 +841,241 @@ def test_command_oom_kill(self):
|
839 | 841 | u'Command killed due to excessive memory consumption\n')
|
840 | 842 |
|
841 | 843 |
|
| 844 | +class TestPythonEnvironment(TestCase): |
| 845 | + |
| 846 | + def setUp(self): |
| 847 | + self.project_sphinx = get(Project, documentation_type='sphinx') |
| 848 | + self.version_sphinx = get(Version, project=self.project_sphinx) |
| 849 | + |
| 850 | + self.project_mkdocs = get(Project, documentation_type='mkdocs') |
| 851 | + self.version_mkdocs = get(Version, project=self.project_mkdocs) |
| 852 | + |
| 853 | + self.build_env_mock = Mock() |
| 854 | + |
| 855 | + self.base_requirements = [ |
| 856 | + 'Pygments==2.2.0', |
| 857 | + 'setuptools==37.0.0', |
| 858 | + 'docutils==0.13.1', |
| 859 | + 'mock==1.0.1', |
| 860 | + 'pillow==2.6.1', |
| 861 | + 'alabaster>=0.7,<0.8,!=0.7.5', |
| 862 | + ] |
| 863 | + self.base_conda_requirements = [ |
| 864 | + 'mock', |
| 865 | + 'pillow', |
| 866 | + ] |
| 867 | + |
| 868 | + self.pip_install_args = [ |
| 869 | + 'python', |
| 870 | + mock.ANY, # pip path |
| 871 | + 'install', |
| 872 | + '--use-wheel', |
| 873 | + '--upgrade', |
| 874 | + '--cache-dir', |
| 875 | + mock.ANY, # cache path |
| 876 | + ] |
| 877 | + |
| 878 | + def test_install_core_requirements_sphinx(self): |
| 879 | + python_env = Virtualenv( |
| 880 | + version=self.version_sphinx, |
| 881 | + build_env=self.build_env_mock, |
| 882 | + ) |
| 883 | + python_env.install_core_requirements() |
| 884 | + requirements_sphinx = [ |
| 885 | + 'commonmark==0.5.4', |
| 886 | + 'recommonmark==0.4.0', |
| 887 | + 'sphinx==1.6.5', |
| 888 | + 'sphinx-rtd-theme<0.3', |
| 889 | + 'readthedocs-sphinx-ext<0.6', |
| 890 | + ] |
| 891 | + requirements = self.base_requirements + requirements_sphinx |
| 892 | + args = self.pip_install_args + requirements |
| 893 | + self.build_env_mock.run.assert_called_once_with( |
| 894 | + *args, bin_path=mock.ANY |
| 895 | + ) |
| 896 | + |
| 897 | + def test_install_core_requirements_mkdocs(self): |
| 898 | + python_env = Virtualenv( |
| 899 | + version=self.version_mkdocs, |
| 900 | + build_env=self.build_env_mock |
| 901 | + ) |
| 902 | + python_env.install_core_requirements() |
| 903 | + requirements_mkdocs = [ |
| 904 | + 'commonmark==0.5.4', |
| 905 | + 'recommonmark==0.4.0', |
| 906 | + 'mkdocs==0.15.0', |
| 907 | + ] |
| 908 | + requirements = self.base_requirements + requirements_mkdocs |
| 909 | + args = self.pip_install_args + requirements |
| 910 | + self.build_env_mock.run.assert_called_once_with( |
| 911 | + *args, bin_path=mock.ANY |
| 912 | + ) |
| 913 | + |
| 914 | + def test_install_user_requirements(self): |
| 915 | + """ |
| 916 | + If a projects does not specify a requirements file, |
| 917 | + RTD will choose one automatically. |
| 918 | +
|
| 919 | + First by searching under the docs/ directory and then under the root. |
| 920 | + The files can be named as: |
| 921 | +
|
| 922 | + - ``pip_requirements.txt`` |
| 923 | + - ``requirements.txt`` |
| 924 | + """ |
| 925 | + self.build_env_mock.project = self.project_sphinx |
| 926 | + self.build_env_mock.version = self.version_sphinx |
| 927 | + python_env = Virtualenv( |
| 928 | + version=self.version_sphinx, |
| 929 | + build_env=self.build_env_mock |
| 930 | + ) |
| 931 | + |
| 932 | + checkout_path = python_env.checkout_path |
| 933 | + docs_requirements = os.path.join( |
| 934 | + checkout_path, 'docs', 'requirements.txt' |
| 935 | + ) |
| 936 | + root_requirements = os.path.join( |
| 937 | + checkout_path, 'requirements.txt' |
| 938 | + ) |
| 939 | + paths = { |
| 940 | + os.path.join(checkout_path, 'docs'): True, |
| 941 | + } |
| 942 | + args = [ |
| 943 | + 'python', |
| 944 | + mock.ANY, # pip path |
| 945 | + 'install', |
| 946 | + '--exists-action=w', |
| 947 | + '--cache-dir', |
| 948 | + mock.ANY, # cache path |
| 949 | + 'requirements_file' |
| 950 | + ] |
| 951 | + |
| 952 | + # One requirements file on the docs/ dir |
| 953 | + # should be installed |
| 954 | + paths[docs_requirements] = True |
| 955 | + paths[root_requirements] = False |
| 956 | + with fake_paths_lookup(paths): |
| 957 | + python_env.install_user_requirements() |
| 958 | + args[-1] = '-r{}'.format(docs_requirements) |
| 959 | + self.build_env_mock.run.assert_called_with( |
| 960 | + *args, cwd=mock.ANY, bin_path=mock.ANY |
| 961 | + ) |
| 962 | + |
| 963 | + # One requirements file on the root dir |
| 964 | + # should be installed |
| 965 | + paths[docs_requirements] = False |
| 966 | + paths[root_requirements] = True |
| 967 | + with fake_paths_lookup(paths): |
| 968 | + python_env.install_user_requirements() |
| 969 | + args[-1] = '-r{}'.format(root_requirements) |
| 970 | + self.build_env_mock.run.assert_called_with( |
| 971 | + *args, cwd=mock.ANY, bin_path=mock.ANY |
| 972 | + ) |
| 973 | + |
| 974 | + # Two requirements files on the root and docs/ dirs |
| 975 | + # the one on docs/ should be installed |
| 976 | + paths[docs_requirements] = True |
| 977 | + paths[root_requirements] = True |
| 978 | + with fake_paths_lookup(paths): |
| 979 | + python_env.install_user_requirements() |
| 980 | + args[-1] = '-r{}'.format(docs_requirements) |
| 981 | + self.build_env_mock.run.assert_called_with( |
| 982 | + *args, cwd=mock.ANY, bin_path=mock.ANY |
| 983 | + ) |
| 984 | + |
| 985 | + # No requirements file |
| 986 | + # no requirements should be installed |
| 987 | + self.build_env_mock.run.reset_mock() |
| 988 | + paths[docs_requirements] = False |
| 989 | + paths[root_requirements] = False |
| 990 | + with fake_paths_lookup(paths): |
| 991 | + python_env.install_user_requirements() |
| 992 | + self.build_env_mock.run.assert_not_called() |
| 993 | + |
| 994 | + def test_install_core_requirements_sphinx_conda(self): |
| 995 | + python_env = Conda( |
| 996 | + version=self.version_sphinx, |
| 997 | + build_env=self.build_env_mock, |
| 998 | + ) |
| 999 | + python_env.install_core_requirements() |
| 1000 | + conda_sphinx = [ |
| 1001 | + 'sphinx', |
| 1002 | + 'sphinx_rtd_theme', |
| 1003 | + ] |
| 1004 | + conda_requirements = self.base_conda_requirements + conda_sphinx |
| 1005 | + pip_requirements = [ |
| 1006 | + 'recommonmark', |
| 1007 | + 'readthedocs-sphinx-ext', |
| 1008 | + ] |
| 1009 | + |
| 1010 | + args_pip = [ |
| 1011 | + 'python', |
| 1012 | + mock.ANY, # pip path |
| 1013 | + 'install', |
| 1014 | + '-U', |
| 1015 | + '--cache-dir', |
| 1016 | + mock.ANY, # cache path |
| 1017 | + ] |
| 1018 | + args_pip.extend(pip_requirements) |
| 1019 | + |
| 1020 | + args_conda = [ |
| 1021 | + 'conda', |
| 1022 | + 'install', |
| 1023 | + '--yes', |
| 1024 | + '--name', |
| 1025 | + self.version_sphinx.slug, |
| 1026 | + ] |
| 1027 | + args_conda.extend(conda_requirements) |
| 1028 | + |
| 1029 | + self.build_env_mock.run.assert_has_calls([ |
| 1030 | + mock.call(*args_conda), |
| 1031 | + mock.call(*args_pip, bin_path=mock.ANY) |
| 1032 | + ]) |
| 1033 | + |
| 1034 | + def test_install_core_requirements_mkdocs_conda(self): |
| 1035 | + python_env = Conda( |
| 1036 | + version=self.version_mkdocs, |
| 1037 | + build_env=self.build_env_mock, |
| 1038 | + ) |
| 1039 | + python_env.install_core_requirements() |
| 1040 | + conda_requirements = self.base_conda_requirements |
| 1041 | + pip_requirements = [ |
| 1042 | + 'recommonmark', |
| 1043 | + 'mkdocs', |
| 1044 | + ] |
| 1045 | + |
| 1046 | + args_pip = [ |
| 1047 | + 'python', |
| 1048 | + mock.ANY, # pip path |
| 1049 | + 'install', |
| 1050 | + '-U', |
| 1051 | + '--cache-dir', |
| 1052 | + mock.ANY, # cache path |
| 1053 | + ] |
| 1054 | + args_pip.extend(pip_requirements) |
| 1055 | + |
| 1056 | + args_conda = [ |
| 1057 | + 'conda', |
| 1058 | + 'install', |
| 1059 | + '--yes', |
| 1060 | + '--name', |
| 1061 | + self.version_mkdocs.slug, |
| 1062 | + ] |
| 1063 | + args_conda.extend(conda_requirements) |
| 1064 | + |
| 1065 | + self.build_env_mock.run.assert_has_calls([ |
| 1066 | + mock.call(*args_conda), |
| 1067 | + mock.call(*args_pip, bin_path=mock.ANY) |
| 1068 | + ]) |
| 1069 | + |
| 1070 | + def test_install_user_requirements_conda(self): |
| 1071 | + python_env = Conda( |
| 1072 | + version=self.version_sphinx, |
| 1073 | + build_env=self.build_env_mock, |
| 1074 | + ) |
| 1075 | + python_env.install_user_requirements() |
| 1076 | + self.build_env_mock.run.assert_not_called() |
| 1077 | + |
| 1078 | + |
842 | 1079 | class AutoWipeEnvironmentBase(object):
|
843 | 1080 | fixtures = ['test_data']
|
844 | 1081 | build_env_class = None
|
|
0 commit comments