Skip to content

Commit bf97bc5

Browse files
authored
Merge pull request #3800 from stsewd/fix-requirements-file-lookup
Fix requirements file lookup
2 parents 907bad3 + 98d47ec commit bf97bc5

File tree

2 files changed

+246
-7
lines changed

2 files changed

+246
-7
lines changed

readthedocs/doc_builder/python_environments.py

+8-6
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from __future__ import (
55
absolute_import, division, print_function, unicode_literals)
66

7+
import itertools
78
import json
89
import logging
910
import os
@@ -275,12 +276,13 @@ def install_user_requirements(self):
275276
builder_class = get_builder_class(self.project.documentation_type)
276277
docs_dir = (builder_class(build_env=self.build_env, python_env=self)
277278
.docs_dir())
278-
for path in [docs_dir, '']:
279-
for req_file in ['pip_requirements.txt', 'requirements.txt']:
280-
test_path = os.path.join(self.checkout_path, path, req_file)
281-
if os.path.exists(test_path):
282-
requirements_file_path = test_path
283-
break
279+
paths = [docs_dir, '']
280+
req_files = ['pip_requirements.txt', 'requirements.txt']
281+
for path, req_file in itertools.product(paths, req_files):
282+
test_path = os.path.join(self.checkout_path, path, req_file)
283+
if os.path.exists(test_path):
284+
requirements_file_path = test_path
285+
break
284286

285287
if requirements_file_path:
286288
args = [

readthedocs/rtd_tests/tests/test_doc_building.py

+238-1
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,18 @@
2121
from docker.errors import APIError as DockerAPIError
2222
from docker.errors import DockerException
2323
from mock import Mock, PropertyMock, mock_open, patch
24+
from django_dynamic_fixture import get
2425

2526
from readthedocs.builds.constants import BUILD_STATE_CLONING
2627
from readthedocs.builds.models import Version
2728
from readthedocs.doc_builder.config import ConfigWrapper
2829
from readthedocs.doc_builder.environments import (
2930
BuildCommand, DockerBuildCommand, DockerBuildEnvironment, LocalBuildEnvironment)
3031
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
3233
from readthedocs.projects.models import Project
3334
from readthedocs.rtd_tests.mocks.environment import EnvironmentMockGroup
35+
from readthedocs.rtd_tests.mocks.paths import fake_paths_lookup
3436
from readthedocs.rtd_tests.tests.test_config_wrapper import create_load
3537

3638
DUMMY_BUILD_ID = 123
@@ -839,6 +841,241 @@ def test_command_oom_kill(self):
839841
u'Command killed due to excessive memory consumption\n')
840842

841843

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+
8421079
class AutoWipeEnvironmentBase(object):
8431080
fixtures = ['test_data']
8441081
build_env_class = None

0 commit comments

Comments
 (0)